File indexing completed on 2023-10-03 03:17:44
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 }