File indexing completed on 2024-04-28 15:23:15

0001 /*
0002  *  This file is part of the KDE libraries
0003  *  Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
0004  *  Copyright (C) 2001,2003 Peter Kelly (pmk@post.com)
0005  *  Copyright (C) 2001-2003 David Faure (faure@kde.org)
0006  *
0007  *  This library is free software; you can redistribute it and/or
0008  *  modify it under the terms of the GNU Library General Public
0009  *  License as published by the Free Software Foundation; either
0010  *  version 2 of the License, or (at your option) any later version.
0011  *
0012  *  This library is distributed in the hope that it will be useful,
0013  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
0014  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015  *  Library General Public License for more details.
0016  *
0017  *  You should have received a copy of the GNU Library General Public
0018  *  License along with this library; if not, write to the Free Software
0019  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
0020  */
0021 
0022 #include "kjs_proxy.h"
0023 
0024 #include "kjs_window.h"
0025 #include "kjs_events.h"
0026 #ifdef KJS_DEBUGGER
0027 #include "debugger/debugwindow.h"
0028 #endif
0029 #include <xml/dom_nodeimpl.h>
0030 #include <khtmlpart_p.h>
0031 #include <khtml_part.h>
0032 #include <kprotocolmanager.h>
0033 #include "khtml_debug.h"
0034 #include <klocalizedstring.h>
0035 #include <assert.h>
0036 #include <kjs/function.h>
0037 #include <kjs/JSLock.h>
0038 
0039 using namespace KJS;
0040 using namespace KJSDebugger;
0041 
0042 #ifndef NDEBUG
0043 int KJSProxy::s_count = 0;
0044 #endif
0045 
0046 KJSProxy::KJSProxy(khtml::ChildFrame *frame)
0047 {
0048     m_script = nullptr;
0049     m_frame = frame;
0050     m_debugEnabled = false;
0051     m_running = 0;
0052     m_handlerLineno = 0;
0053 #ifndef NDEBUG
0054     s_count++;
0055 #endif
0056 }
0057 
0058 KJSProxy::~KJSProxy()
0059 {
0060     if (m_script) {
0061         //qCDebug(KHTML_LOG) << "KJSProxy::~KJSProxyImpl clearing global object " << m_script->globalObject().imp();
0062         // This allows to delete the global-object properties, like all the protos
0063         m_script->globalObject()->clearProperties();
0064         //qCDebug(KHTML_LOG) << "KJSProxy::~KJSProxyImpl garbage collecting";
0065 
0066         JSLock::lock();
0067         while (Interpreter::collect())
0068             ;
0069         JSLock::unlock();
0070         //qCDebug(KHTML_LOG) << "KJSProxy::~KJSProxyImpl deleting interpreter " << m_script;
0071         delete m_script;
0072         //qCDebug(KHTML_LOG) << "KJSProxy::~KJSProxyImpl garbage collecting again";
0073         // Garbage collect - as many times as necessary
0074         // (we could delete an object which was holding another object, so
0075         // the deref() will happen too late for deleting the impl of the 2nd object).
0076         JSLock::lock();
0077         while (Interpreter::collect())
0078             ;
0079         JSLock::unlock();
0080     }
0081 
0082 #ifndef NDEBUG
0083     s_count--;
0084     // If it was the last interpreter, we should have nothing left
0085 #ifdef KJS_DEBUG_MEM
0086     if (s_count == 0) {
0087         Interpreter::finalCheck();
0088     }
0089 #endif
0090 #endif
0091 }
0092 
0093 QVariant KJSProxy::evaluate(QString filename, int baseLine,
0094                             const QString &str, const DOM::Node &n, Completion *completion)
0095 {
0096     ++m_running;
0097     // evaluate code. Returns the JS return value or an invalid QVariant
0098     // if there was none, an error occurred or the type couldn't be converted.
0099 
0100     initScript();
0101     // inlineCode is true for <a href="javascript:doSomething()">
0102     // and false for <script>doSomething()</script>. Check if it has the
0103     // expected value in all cases.
0104     // See smart window.open policy for where this is used.
0105     bool inlineCode = filename.isNull();
0106     //qCDebug(KHTML_LOG) << "KJSProxy::evaluate inlineCode=" << inlineCode;
0107 
0108 #ifdef KJS_DEBUGGER
0109     if (inlineCode) {
0110         filename = "(unknown file)";
0111     }
0112     if (m_debugWindow) {
0113         m_debugWindow->attach(m_script);
0114     }
0115 #else
0116     Q_UNUSED(baseLine);
0117 #endif
0118 
0119     m_script->setInlineCode(inlineCode);
0120     Window *window = Window::retrieveWindow(m_frame->m_part);
0121     KJS::JSValue *thisNode = n.isNull() ? Window::retrieve(m_frame->m_part) : getDOMNode(m_script->globalExec(), n.handle());
0122 
0123     UString code(str);
0124 
0125     m_script->startCPUGuard();
0126     Completion comp = m_script->evaluate(filename, baseLine, code, thisNode);
0127     m_script->stopCPUGuard();
0128 
0129     bool success = (comp.complType() == KJS::Normal) || (comp.complType() == ReturnValue);
0130 
0131     if (completion) {
0132         *completion = comp;
0133     }
0134 
0135 #ifdef KJS_DEBUGGER
0136     //    KJSDebugWin::debugWindow()->setCode(QString());
0137 #endif
0138 
0139     window->afterScriptExecution();
0140 
0141     --m_running;
0142 
0143     // let's try to convert the return value
0144     if (success && comp.value()) {
0145         return ValueToVariant(m_script->globalExec(), comp.value());
0146     } else {
0147         if (comp.complType() == Throw) {
0148             UString msg = comp.value()->toString(m_script->globalExec());
0149             // qCDebug(KHTML_LOG) << "WARNING: Script threw exception: " << msg.qstring();
0150         }
0151         return QVariant();
0152     }
0153 }
0154 
0155 bool KJSProxy::isRunningScript()
0156 {
0157     return m_running != 0;
0158 }
0159 
0160 // Implementation of the debug() function
0161 class TestFunctionImp : public JSObject
0162 {
0163 public:
0164     TestFunctionImp() : JSObject() {}
0165     bool implementsCall() const override
0166     {
0167         return true;
0168     }
0169     JSValue *callAsFunction(ExecState *exec, JSObject *thisObj, const List &args) override;
0170 };
0171 
0172 JSValue *TestFunctionImp::callAsFunction(ExecState *exec, JSObject * /*thisObj*/, const List &args)
0173 {
0174     fprintf(stderr, "--> %s\n", args[0]->toString(exec).ascii());
0175     return jsUndefined();
0176 }
0177 
0178 void KJSProxy::clear()
0179 {
0180     // clear resources allocated by the interpreter, and make it ready to be used by another page
0181     // We have to keep it, so that the Window object for the part remains the same.
0182     // (we used to delete and re-create it, previously)
0183     if (m_script) {
0184 #ifdef KJS_DEBUGGER
0185         if (m_debugWindow) {
0186             m_debugWindow->clearInterpreter(m_script);
0187         }
0188 #endif
0189         m_script->clear();
0190 
0191         Window *win = static_cast<Window *>(m_script->globalObject());
0192         if (win) {
0193             win->clear(m_script->globalExec());
0194             // re-add "debug", clear() removed it
0195             m_script->globalObject()->put(m_script->globalExec(),
0196                                           "debug", new TestFunctionImp(), Internal);
0197             if (win->part()) {
0198                 applyUserAgent();
0199             }
0200         }
0201 
0202         // Really delete everything that can be, so that the DOM nodes get deref'ed
0203         //qCDebug(KHTML_LOG) << "all done -> collecting";
0204         JSLock::lock();
0205         while (Interpreter::collect())
0206             ;
0207         JSLock::unlock();
0208     }
0209 
0210 #ifdef KJS_DEBUGGER
0211     // Detach from debugging entirely if it's been turned off.
0212     if (m_debugWindow && !m_debugEnabled) {
0213         m_debugWindow->detach(m_script);
0214         m_debugWindow = 0;
0215     }
0216 #endif
0217 }
0218 
0219 DOM::EventListener *KJSProxy::createHTMLEventHandler(QString sourceUrl, QString name, QString code, DOM::NodeImpl *node, bool svg)
0220 {
0221     initScript();
0222 
0223 #ifdef KJS_DEBUGGER
0224     if (m_debugWindow) {
0225         m_debugWindow->attach(m_script);
0226     }
0227 #else
0228     Q_UNUSED(sourceUrl);
0229 #endif
0230 
0231     return KJS::Window::retrieveWindow(m_frame->m_part)->getJSLazyEventListener(
0232                code, sourceUrl, m_handlerLineno, name, node, svg);
0233 }
0234 
0235 void KJSProxy::finishedWithEvent(const DOM::Event &event)
0236 {
0237     // This is called when the DOM implementation has finished with a particular event. This
0238     // is the case in sitations where an event has been created just for temporary usage,
0239     // e.g. an image load or mouse move. Once the event has been dispatched, it is forgotten
0240     // by the DOM implementation and so does not need to be cached still by the interpreter
0241     ScriptInterpreter::forgetDOMObject(event.handle());
0242 }
0243 
0244 KJS::Interpreter *KJSProxy::interpreter()
0245 {
0246     if (!m_script) {
0247         initScript();
0248     }
0249     return m_script;
0250 }
0251 
0252 void KJSProxy::setDebugEnabled(bool enabled)
0253 {
0254 #ifdef KJS_DEBUGGER
0255     m_debugEnabled = enabled;
0256 
0257     // Note that we attach to the debugger only before
0258     // running a script. Detaches/disabling are done between
0259     // documents, at clear. Both are done so the debugger
0260     // see the entire session
0261     if (enabled) {
0262         m_debugWindow = DebugWindow::window();
0263     }
0264 #else
0265     Q_UNUSED(enabled)
0266 #endif
0267 }
0268 
0269 bool KJSProxy::debugEnabled() const
0270 {
0271 #ifdef KJS_DEBUGGER
0272     return m_debugEnabled;
0273 #else
0274     return false;
0275 #endif
0276 }
0277 
0278 void KJSProxy::showDebugWindow(bool /*show*/)
0279 {
0280 #ifdef KJS_DEBUGGER
0281     if (m_debugWindow) {
0282         m_debugWindow->show();
0283     }
0284 #else
0285     //Q_UNUSED(show);
0286 #endif
0287 }
0288 
0289 bool KJSProxy::paused() const
0290 {
0291 #ifdef KJS_DEBUGGER
0292     // if (DebugWindow::window())
0293     //     return DebugWindow::window()->inSession();
0294 #endif
0295     return false;
0296 }
0297 
0298 KJS_QT_UNICODE_IMPL
0299 
0300 void KJSProxy::initScript()
0301 {
0302     if (m_script) {
0303         return;
0304     }
0305 
0306     // Build the global object - which is a Window instance
0307     JSGlobalObject *globalObject(new Window(m_frame));
0308 
0309     // Create a KJS interpreter for this part
0310     m_script = new KJS::ScriptInterpreter(globalObject, m_frame);
0311     KJS_QT_UNICODE_SET;
0312     globalObject->setPrototype(m_script->builtinObjectPrototype());
0313 
0314 #ifdef KJS_DEBUGGER
0315     //m_script->setDebuggingEnabled(m_debugEnabled);
0316 #endif
0317     //m_script->enableDebug();
0318     globalObject->put(m_script->globalExec(),
0319                       "debug", new TestFunctionImp(), Internal);
0320     applyUserAgent();
0321 
0322 #ifdef KJS_DEBUGGER
0323     // Attach debugger as early as possible as not all scrips have a direct DOM-relation
0324     // NOTE: attach can be called multiple times
0325     if (m_debugEnabled) {
0326         m_debugWindow->attach(m_script);
0327     }
0328 #endif
0329 }
0330 
0331 void KJSProxy::applyUserAgent()
0332 {
0333     assert(m_script);
0334     QUrl url = m_frame->m_part.data()->url();
0335     QString host = url.isLocalFile() ? "localhost" : url.host();
0336     QString userAgent = KProtocolManager::userAgentForHost(host);
0337     if (userAgent.indexOf(QLatin1String("Microsoft"), 0, Qt::CaseSensitive) >= 0 ||
0338             userAgent.indexOf(QLatin1String("MSIE"), 0, Qt::CaseSensitive) >= 0) {
0339         m_script->setCompatMode(Interpreter::IECompat);
0340 #ifdef KJS_VERBOSE
0341         qCDebug(KHTML_LOG) << "Setting IE compat mode";
0342 #endif
0343     } else
0344         // If we find "Mozilla" but not "(compatible, ...)" we are a real Netscape
0345         if (userAgent.indexOf(QLatin1String("Mozilla"), 0, Qt::CaseSensitive) >= 0 &&
0346                 userAgent.indexOf(QLatin1String("compatible"), 0, Qt::CaseSensitive) == -1 &&
0347                 userAgent.indexOf(QLatin1String("KHTML"), 0, Qt::CaseSensitive) == -1) {
0348             m_script->setCompatMode(Interpreter::NetscapeCompat);
0349 #ifdef KJS_VERBOSE
0350             qCDebug(KHTML_LOG) << "Setting NS compat mode";
0351 #endif
0352         }
0353 }
0354 
0355 // Helper method, so that all classes which need jScript() don't need to be added
0356 // as friend to KHTMLPart
0357 KJSProxy *KJSProxy::proxy(KHTMLPart *part)
0358 {
0359     return part->jScript();
0360 }