kjs_proxy.cpp

00001 // -*- c-basic-offset: 2 -*-
00002 /*
00003  *  This file is part of the KDE libraries
00004  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
00005  *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
00006  *  Copyright (C) 2001-2003 David Faure (faure@kde.org)
00007  *
00008  *  This library is free software; you can redistribute it and/or
00009  *  modify it under the terms of the GNU Library General Public
00010  *  License as published by the Free Software Foundation; either
00011  *  version 2 of the License, or (at your option) any later version.
00012  *
00013  *  This library is distributed in the hope that it will be useful,
00014  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00015  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00016  *  Library General Public License for more details.
00017  *
00018  *  You should have received a copy of the GNU Library General Public
00019  *  License along with this library; if not, write to the Free Software
00020  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
00021  */
00022 
00023 #include <config.h>
00024 
00025 #if defined(HAVE_VALGRIND_MEMCHECK_H) && !defined(NDEBUG)
00026 
00027 #include <valgrind/memcheck.h>
00028 #define VALGRIND_SUPPORT
00029 
00030 #endif
00031 
00032 
00033 #include "kjs_proxy.h"
00034 
00035 #include "kjs_window.h"
00036 #include "kjs_events.h"
00037 #include "kjs_debugwin.h"
00038 #include "xml/dom_nodeimpl.h"
00039 #include "khtmlpart_p.h"
00040 #include <khtml_part.h>
00041 #include <kprotocolmanager.h>
00042 #include <kdebug.h>
00043 #include <kmessagebox.h>
00044 #include <klocale.h>
00045 #include <unistd.h>
00046 #include <signal.h>
00047 #include <sys/time.h>
00048 #include <assert.h>
00049 #include <kjs/function.h>
00050 
00051 using namespace KJS;
00052 
00053 extern "C" {
00054   KJSProxy *kjs_html_init(khtml::ChildFrame *childframe);
00055 }
00056 
00057 namespace KJS {
00058 
00059 class KJSProxyImpl : public KJSProxy {
00060 public:
00061   KJSProxyImpl(khtml::ChildFrame *frame);
00062   virtual ~KJSProxyImpl();
00063   virtual QVariant evaluate(QString filename, int baseLine, const QString &, const DOM::Node &n,
00064                 Completion *completion = 0);
00065   virtual void clear();
00066   virtual DOM::EventListener *createHTMLEventHandler(QString sourceUrl, QString name, QString code, DOM::NodeImpl *node);
00067   virtual void finishedWithEvent(const DOM::Event &event);
00068   virtual KJS::Interpreter *interpreter();
00069 
00070   virtual void setDebugEnabled(bool enabled);
00071   virtual void showDebugWindow(bool show=true);
00072   virtual bool paused() const;
00073   virtual void dataReceived();
00074 
00075   void initScript();
00076   void applyUserAgent();
00077 
00078 private:
00079   KJS::ScriptInterpreter* m_script;
00080   bool m_debugEnabled;
00081 #ifndef NDEBUG
00082   static int s_count;
00083 #endif
00084 };
00085 
00086 } // namespace KJS
00087 
00088 #ifndef NDEBUG
00089 int KJSProxyImpl::s_count = 0;
00090 #endif
00091 
00092 KJSProxyImpl::KJSProxyImpl(khtml::ChildFrame *frame)
00093 {
00094   m_script = 0;
00095   m_frame = frame;
00096   m_debugEnabled = false;
00097 #ifndef NDEBUG
00098   s_count++;
00099 #endif
00100 }
00101 
00102 KJSProxyImpl::~KJSProxyImpl()
00103 {
00104   if ( m_script ) {
00105     //kdDebug() << "KJSProxyImpl::~KJSProxyImpl clearing global object " << m_script->globalObject().imp() << endl;
00106     // This allows to delete the global-object properties, like all the protos
00107     static_cast<ObjectImp*>(m_script->globalObject().imp())->deleteAllProperties( m_script->globalExec() );
00108     //kdDebug() << "KJSProxyImpl::~KJSProxyImpl garbage collecting" << endl;
00109     while (KJS::Interpreter::collect())
00110         ;
00111     //kdDebug() << "KJSProxyImpl::~KJSProxyImpl deleting interpreter " << m_script << endl;
00112     delete m_script;
00113     //kdDebug() << "KJSProxyImpl::~KJSProxyImpl garbage collecting again" << endl;
00114     // Garbage collect - as many times as necessary
00115     // (we could delete an object which was holding another object, so
00116     // the deref() will happen too late for deleting the impl of the 2nd object).
00117     while (KJS::Interpreter::collect())
00118         ;
00119   }
00120 
00121 #ifndef NDEBUG
00122   s_count--;
00123   // If it was the last interpreter, we should have nothing left
00124 #ifdef KJS_DEBUG_MEM
00125   if ( s_count == 0 )
00126     Interpreter::finalCheck();
00127 #endif
00128 #endif
00129 }
00130 
00131 QVariant KJSProxyImpl::evaluate(QString filename, int baseLine,
00132                                 const QString&str, const DOM::Node &n, Completion *completion) {
00133   // evaluate code. Returns the JS return value or an invalid QVariant
00134   // if there was none, an error occurred or the type couldn't be converted.
00135 
00136   initScript();
00137   // inlineCode is true for <a href="javascript:doSomething()">
00138   // and false for <script>doSomething()</script>. Check if it has the
00139   // expected value in all cases.
00140   // See smart window.open policy for where this is used.
00141   bool inlineCode = filename.isNull();
00142   //kdDebug(6070) << "KJSProxyImpl::evaluate inlineCode=" << inlineCode << endl;
00143 
00144 #ifdef KJS_DEBUGGER
00145   if (inlineCode)
00146     filename = "(unknown file)";
00147   if (KJSDebugWin::debugWindow()) {
00148     KJSDebugWin::debugWindow()->attach(m_script);
00149     KJSDebugWin::debugWindow()->setNextSourceInfo(filename,baseLine);
00150   //    KJSDebugWin::debugWindow()->setMode(KJSDebugWin::Step);
00151   }
00152 #else
00153   Q_UNUSED(baseLine);
00154 #endif
00155 
00156   m_script->setInlineCode(inlineCode);
00157   Window* window = Window::retrieveWindow( m_frame->m_part );
00158   KJS::Value thisNode = n.isNull() ? Window::retrieve( m_frame->m_part ) : getDOMNode(m_script->globalExec(),n);
00159 
00160   UString code( str );
00161 
00162   KJSCPUGuard guard;
00163   guard.start();
00164   Completion comp = m_script->evaluate(code, thisNode);
00165   guard.stop();
00166 
00167   bool success = ( comp.complType() == Normal ) || ( comp.complType() == ReturnValue );
00168 
00169   if (completion)
00170     *completion = comp;
00171 
00172 #ifdef KJS_DEBUGGER
00173     //    KJSDebugWin::debugWindow()->setCode(QString::null);
00174 #endif
00175 
00176   window->afterScriptExecution();
00177 
00178   // let's try to convert the return value
00179   if (success && comp.value().isValid())
00180     return ValueToVariant( m_script->globalExec(), comp.value());
00181   else
00182   {
00183     if ( comp.complType() == Throw )
00184     {
00185         UString msg = comp.value().toString(m_script->globalExec());
00186         kdDebug(6070) << "WARNING: Script threw exception: " << msg.qstring() << endl;
00187     }
00188     return QVariant();
00189   }
00190 }
00191 
00192 // Implementation of the debug() function
00193 class TestFunctionImp : public ObjectImp {
00194 public:
00195   TestFunctionImp() : ObjectImp() {}
00196   virtual bool implementsCall() const { return true; }
00197   virtual Value call(ExecState *exec, Object &thisObj, const List &args);
00198 };
00199 
00200 Value TestFunctionImp::call(ExecState *exec, Object &/*thisObj*/, const List &args)
00201 {
00202   fprintf(stderr,"--> %s\n",args[0].toString(exec).ascii());
00203   return Undefined();
00204 }
00205 
00206 void KJSProxyImpl::clear() {
00207   // clear resources allocated by the interpreter, and make it ready to be used by another page
00208   // We have to keep it, so that the Window object for the part remains the same.
00209   // (we used to delete and re-create it, previously)
00210   if (m_script) {
00211 #ifdef KJS_DEBUGGER
00212     // ###
00213     KJSDebugWin *debugWin = KJSDebugWin::debugWindow();
00214     if (debugWin) {
00215       if (debugWin->getExecState() &&
00216           debugWin->getExecState()->interpreter() == m_script)
00217         debugWin->slotStop();
00218       debugWin->clearInterpreter(m_script);
00219     }
00220 #endif
00221     m_script->clear();
00222 
00223     Window *win = static_cast<Window *>(m_script->globalObject().imp());
00224     if (win) {
00225       win->clear( m_script->globalExec() );
00226       // re-add "debug", clear() removed it
00227       m_script->globalObject().put(m_script->globalExec(),
00228                                    "debug", Value(new TestFunctionImp()), Internal);
00229       if ( win->part() )
00230         applyUserAgent();
00231     }
00232 
00233     // Really delete everything that can be, so that the DOM nodes get deref'ed
00234     //kdDebug() << k_funcinfo << "all done -> collecting" << endl;
00235     while (KJS::Interpreter::collect())
00236         ;
00237   }
00238 }
00239 
00240 DOM::EventListener *KJSProxyImpl::createHTMLEventHandler(QString sourceUrl, QString name, QString code, DOM::NodeImpl *node)
00241 {
00242   initScript();
00243 
00244 #ifdef KJS_DEBUGGER
00245   if (KJSDebugWin::debugWindow()) {
00246     KJSDebugWin::debugWindow()->attach(m_script);
00247     KJSDebugWin::debugWindow()->setNextSourceInfo(sourceUrl,m_handlerLineno);
00248   }
00249 #else
00250   Q_UNUSED(sourceUrl);
00251 #endif
00252 
00253   return KJS::Window::retrieveWindow(m_frame->m_part)->getJSLazyEventListener(code,name,node);
00254 }
00255 
00256 void KJSProxyImpl::finishedWithEvent(const DOM::Event &event)
00257 {
00258   // This is called when the DOM implementation has finished with a particular event. This
00259   // is the case in sitations where an event has been created just for temporary usage,
00260   // e.g. an image load or mouse move. Once the event has been dispatched, it is forgotten
00261   // by the DOM implementation and so does not need to be cached still by the interpreter
00262   ScriptInterpreter::forgetDOMObject(event.handle());
00263 }
00264 
00265 KJS::Interpreter *KJSProxyImpl::interpreter()
00266 {
00267   if (!m_script)
00268     initScript();
00269   return m_script;
00270 }
00271 
00272 void KJSProxyImpl::setDebugEnabled(bool enabled)
00273 {
00274 #ifdef KJS_DEBUGGER
00275   m_debugEnabled = enabled;
00276   //if (m_script)
00277   //    m_script->setDebuggingEnabled(enabled);
00278   // NOTE: this is consistent across all KJSProxyImpl instances, as we only
00279   // ever have 1 debug window
00280   if (!enabled && KJSDebugWin::debugWindow()) {
00281     KJSDebugWin::destroyInstance();
00282   }
00283   else if (enabled && !KJSDebugWin::debugWindow()) {
00284     KJSDebugWin::createInstance();
00285     initScript();
00286     KJSDebugWin::debugWindow()->attach(m_script);
00287   }
00288 #else
00289   Q_UNUSED(enabled);
00290 #endif
00291 }
00292 
00293 void KJSProxyImpl::showDebugWindow(bool /*show*/)
00294 {
00295 #ifdef KJS_DEBUGGER
00296   if (KJSDebugWin::debugWindow())
00297     KJSDebugWin::debugWindow()->show();
00298 #else
00299   //Q_UNUSED(show);
00300 #endif
00301 }
00302 
00303 bool KJSProxyImpl::paused() const
00304 {
00305 #ifdef KJS_DEBUGGER
00306   if (KJSDebugWin::debugWindow())
00307     return KJSDebugWin::debugWindow()->inSession();
00308 #endif
00309   return false;
00310 }
00311 
00312 void KJSProxyImpl::dataReceived()
00313 {
00314 #ifdef KJS_DEBUGGER
00315   if (KJSDebugWin::debugWindow() && m_frame->m_part)
00316     KJSDebugWin::debugWindow()->sourceChanged(m_script,m_frame->m_part->url().url());
00317 #endif
00318 }
00319 
00320 void KJSProxyImpl::initScript()
00321 {
00322   if (m_script)
00323     return;
00324 
00325   // Build the global object - which is a Window instance
00326   Object globalObject( new Window(m_frame) );
00327 
00328   // Create a KJS interpreter for this part
00329   m_script = new KJS::ScriptInterpreter(globalObject, m_frame);
00330   static_cast<ObjectImp*>(globalObject.imp())->setPrototype(m_script->builtinObjectPrototype());
00331 
00332 #ifdef KJS_DEBUGGER
00333   //m_script->setDebuggingEnabled(m_debugEnabled);
00334 #endif
00335   //m_script->enableDebug();
00336   globalObject.put(m_script->globalExec(),
00337            "debug", Value(new TestFunctionImp()), Internal);
00338   applyUserAgent();
00339 }
00340 
00341 void KJSProxyImpl::applyUserAgent()
00342 {
00343   assert( m_script );
00344   QString host = m_frame->m_part->url().isLocalFile() ? "localhost" : m_frame->m_part->url().host();
00345   QString userAgent = KProtocolManager::userAgentForHost(host);
00346   if (userAgent.find(QString::fromLatin1("Microsoft")) >= 0 ||
00347       userAgent.find(QString::fromLatin1("MSIE")) >= 0)
00348   {
00349     m_script->setCompatMode(Interpreter::IECompat);
00350 #ifdef KJS_VERBOSE
00351     kdDebug() << "Setting IE compat mode" << endl;
00352 #endif
00353   }
00354   else
00355     // If we find "Mozilla" but not "(compatible, ...)" we are a real Netscape
00356     if (userAgent.find(QString::fromLatin1("Mozilla")) >= 0 &&
00357         userAgent.find(QString::fromLatin1("compatible")) == -1 &&
00358         userAgent.find(QString::fromLatin1("KHTML")) == -1)
00359     {
00360       m_script->setCompatMode(Interpreter::NetscapeCompat);
00361 #ifdef KJS_VERBOSE
00362       kdDebug() << "Setting NS compat mode" << endl;
00363 #endif
00364     }
00365 }
00366 
00367 // Helper method, so that all classes which need jScript() don't need to be added
00368 // as friend to KHTMLPart
00369 KJSProxy * KJSProxy::proxy( KHTMLPart *part )
00370 {
00371     return part->jScript();
00372 }
00373 
00374 // initialize HTML module
00375 KJSProxy *kjs_html_init(khtml::ChildFrame *childframe)
00376 {
00377   return new KJSProxyImpl(childframe);
00378 }
00379 
00380 void KJSCPUGuard::start(unsigned int ms, unsigned int i_ms)
00381 {
00382 #ifdef VALGRIND_SUPPORT
00383     if (RUNNING_ON_VALGRIND) {
00384         ms   *= 50;
00385         i_ms *= 50;
00386     }
00387 #endif
00388 
00389   oldAlarmHandler = signal(SIGVTALRM, alarmHandler);
00390   itimerval tv = {
00391       { i_ms / 1000, (i_ms % 1000) * 1000 },
00392       { ms / 1000, (ms % 1000) * 1000 }
00393   };
00394   setitimer(ITIMER_VIRTUAL, &tv, &oldtv);
00395 }
00396 
00397 void KJSCPUGuard::stop()
00398 {
00399   setitimer(ITIMER_VIRTUAL, &oldtv, 0L);
00400   signal(SIGVTALRM, oldAlarmHandler);
00401 }
00402 
00403 bool KJSCPUGuard::confirmTerminate() {
00404   kdDebug(6070) << "alarmhandler" << endl;
00405   return KMessageBox::warningYesNo(0L, i18n("A script on this page is causing KHTML to freeze. If it continues to run, other applications may become less responsive.\nDo you want to abort the script?"), i18n("JavaScript"), i18n("&Abort"), KStdGuiItem::cont(), "kjscupguard_alarmhandler") == KMessageBox::Yes;
00406 }
00407 
00408 void KJSCPUGuard::alarmHandler(int) {
00409     ExecState::requestTerminate();
00410     ExecState::confirmTerminate = KJSCPUGuard::confirmTerminate;
00411 }
KDE Home | KDE Accessibility Home | Description of Access Keys