Home | History | Annotate | Download | only in js
      1 /*
      2  *  Copyright (C) 1999-2001 Harri Porten (porten (at) kde.org)
      3  *  Copyright (C) 2001 Peter Kelly (pmk (at) post.com)
      4  *  Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
      5  *
      6  *  This library is free software; you can redistribute it and/or
      7  *  modify it under the terms of the GNU Lesser General Public
      8  *  License as published by the Free Software Foundation; either
      9  *  version 2 of the License, or (at your option) any later version.
     10  *
     11  *  This library is distributed in the hope that it will be useful,
     12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
     13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     14  *  Lesser General Public License for more details.
     15  *
     16  *  You should have received a copy of the GNU Lesser General Public
     17  *  License along with this library; if not, write to the Free Software
     18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
     19  */
     20 
     21 #include "config.h"
     22 #include "ScriptController.h"
     23 
     24 #include "CString.h"
     25 #include "Event.h"
     26 #include "EventNames.h"
     27 #include "Frame.h"
     28 #include "FrameLoaderClient.h"
     29 #include "GCController.h"
     30 #include "HTMLPlugInElement.h"
     31 #include "InspectorTimelineAgent.h"
     32 #include "JSDocument.h"
     33 #include "NP_jsobject.h"
     34 #include "Page.h"
     35 #include "PageGroup.h"
     36 #include "ScriptSourceCode.h"
     37 #include "ScriptValue.h"
     38 #include "Settings.h"
     39 #include "StorageNamespace.h"
     40 #include "XSSAuditor.h"
     41 #include "npruntime_impl.h"
     42 #include "runtime_root.h"
     43 #include <debugger/Debugger.h>
     44 #include <runtime/InitializeThreading.h>
     45 #include <runtime/JSLock.h>
     46 
     47 using namespace JSC;
     48 using namespace std;
     49 
     50 namespace WebCore {
     51 
     52 void ScriptController::initializeThreading()
     53 {
     54     JSC::initializeThreading();
     55 }
     56 
     57 ScriptController::ScriptController(Frame* frame)
     58     : m_frame(frame)
     59     , m_handlerLineNumber(0)
     60     , m_sourceURL(0)
     61     , m_inExecuteScript(false)
     62     , m_processingTimerCallback(false)
     63     , m_paused(false)
     64     , m_allowPopupsFromPlugin(false)
     65 #if ENABLE(NETSCAPE_PLUGIN_API)
     66     , m_windowScriptNPObject(0)
     67 #endif
     68 #if PLATFORM(MAC)
     69     , m_windowScriptObject(0)
     70 #endif
     71     , m_XSSAuditor(new XSSAuditor(frame))
     72 {
     73 #if PLATFORM(MAC) && ENABLE(MAC_JAVA_BRIDGE)
     74     static bool initializedJavaJSBindings;
     75     if (!initializedJavaJSBindings) {
     76         initializedJavaJSBindings = true;
     77         initJavaJSBindings();
     78     }
     79 #endif
     80 }
     81 
     82 ScriptController::~ScriptController()
     83 {
     84     if (!m_windowShells.isEmpty()) {
     85         m_windowShells.clear();
     86 
     87         // It's likely that releasing the global object has created a lot of garbage.
     88         gcController().garbageCollectSoon();
     89     }
     90 
     91     disconnectPlatformScriptObjects();
     92 }
     93 
     94 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
     95 {
     96     const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
     97     String sourceURL = jsSourceCode.provider()->url();
     98 
     99     if (!m_XSSAuditor->canEvaluate(sourceCode.source())) {
    100         // This script is not safe to be evaluated.
    101         return JSValue();
    102     }
    103 
    104     // evaluate code. Returns the JS return value or 0
    105     // if there was none, an error occurred or the type couldn't be converted.
    106 
    107     // inlineCode is true for <a href="javascript:doSomething()">
    108     // and false for <script>doSomething()</script>. Check if it has the
    109     // expected value in all cases.
    110     // See smart window.open policy for where this is used.
    111     JSDOMWindowShell* shell = windowShell(world);
    112     ExecState* exec = shell->window()->globalExec();
    113     const String* savedSourceURL = m_sourceURL;
    114     m_sourceURL = &sourceURL;
    115 
    116     JSLock lock(SilenceAssertionsOnly);
    117 
    118     RefPtr<Frame> protect = m_frame;
    119 
    120 #if ENABLE(INSPECTOR)
    121     if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0)
    122         timelineAgent->willEvaluateScript(sourceURL, sourceCode.startLine());
    123 #endif
    124 
    125     exec->globalData().timeoutChecker.start();
    126     Completion comp = JSC::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
    127     exec->globalData().timeoutChecker.stop();
    128 
    129 #if ENABLE(INSPECTOR)
    130     if (InspectorTimelineAgent* timelineAgent = m_frame->page() ? m_frame->page()->inspectorTimelineAgent() : 0)
    131         timelineAgent->didEvaluateScript();
    132 #endif
    133 
    134     // Evaluating the JavaScript could cause the frame to be deallocated
    135     // so we start the keep alive timer here.
    136     m_frame->keepAlive();
    137 
    138     if (comp.complType() == Normal || comp.complType() == ReturnValue) {
    139         m_sourceURL = savedSourceURL;
    140         return comp.value();
    141     }
    142 
    143     if (comp.complType() == Throw || comp.complType() == Interrupted)
    144         reportException(exec, comp.value());
    145 
    146     m_sourceURL = savedSourceURL;
    147     return JSValue();
    148 }
    149 
    150 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
    151 {
    152     return evaluateInWorld(sourceCode, mainThreadNormalWorld());
    153 }
    154 
    155 // An DOMWrapperWorld other than the thread's normal world.
    156 class IsolatedWorld : public DOMWrapperWorld {
    157 public:
    158     static PassRefPtr<IsolatedWorld> create(JSGlobalData* globalData) { return adoptRef(new IsolatedWorld(globalData)); }
    159 
    160 protected:
    161     IsolatedWorld(JSGlobalData* globalData)
    162         : DOMWrapperWorld(globalData, false)
    163     {
    164         JSGlobalData::ClientData* clientData = globalData->clientData;
    165         ASSERT(clientData);
    166         static_cast<WebCoreJSClientData*>(clientData)->rememberWorld(this);
    167     }
    168 };
    169 
    170 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
    171 {
    172     return IsolatedWorld::create(JSDOMWindow::commonJSGlobalData());
    173 }
    174 
    175 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
    176 {
    177     static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
    178 }
    179 
    180 void ScriptController::clearWindowShell()
    181 {
    182     if (m_windowShells.isEmpty())
    183         return;
    184 
    185     JSLock lock(SilenceAssertionsOnly);
    186 
    187     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
    188         JSDOMWindowShell* windowShell = iter->second;
    189 
    190         // Clear the debugger from the current window before setting the new window.
    191         attachDebugger(windowShell, 0);
    192 
    193         windowShell->window()->willRemoveFromWindowShell();
    194         windowShell->setWindow(m_frame->domWindow());
    195 
    196         if (Page* page = m_frame->page()) {
    197             attachDebugger(windowShell, page->debugger());
    198             windowShell->window()->setProfileGroup(page->group().identifier());
    199         }
    200     }
    201 
    202     // There is likely to be a lot of garbage now.
    203     gcController().garbageCollectSoon();
    204 }
    205 
    206 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
    207 {
    208     ASSERT(!m_windowShells.contains(world));
    209 
    210     JSLock lock(SilenceAssertionsOnly);
    211 
    212     JSDOMWindowShell* windowShell = new JSDOMWindowShell(m_frame->domWindow(), world);
    213     m_windowShells.add(world, windowShell);
    214     windowShell->window()->updateDocument();
    215 
    216     if (Page* page = m_frame->page()) {
    217         attachDebugger(windowShell, page->debugger());
    218         windowShell->window()->setProfileGroup(page->group().identifier());
    219     }
    220 
    221     m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
    222 
    223     return windowShell;
    224 }
    225 
    226 bool ScriptController::processingUserGesture(DOMWrapperWorld* world) const
    227 {
    228     return m_allowPopupsFromPlugin || processingUserGestureEvent(world) || isJavaScriptAnchorNavigation();
    229 }
    230 
    231 bool ScriptController::processingUserGestureEvent(DOMWrapperWorld* world) const
    232 {
    233     JSDOMWindowShell* shell = existingWindowShell(world);
    234     if (!shell)
    235         return false;
    236 
    237     if (Event* event = shell->window()->currentEvent())
    238         return event->fromUserGesture();
    239 
    240     return false;
    241 }
    242 
    243 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
    244 bool ScriptController::isJavaScriptAnchorNavigation() const
    245 {
    246     // This is the <a href="javascript:window.open('...')> case -> we let it through
    247     if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
    248         return true;
    249 
    250     // This is the <script>window.open(...)</script> case or a timer callback -> block it
    251     return false;
    252 }
    253 
    254 bool ScriptController::anyPageIsProcessingUserGesture() const
    255 {
    256     Page* page = m_frame->page();
    257     if (!page)
    258         return false;
    259 
    260     const HashSet<Page*>& pages = page->group().pages();
    261     HashSet<Page*>::const_iterator end = pages.end();
    262     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
    263         for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
    264             ScriptController* script = frame->script();
    265 
    266             if (script->m_allowPopupsFromPlugin)
    267                 return true;
    268 
    269             const ShellMap::const_iterator iterEnd = m_windowShells.end();
    270             for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) {
    271                 JSDOMWindowShell* shell = iter->second.get();
    272                 Event* event = shell->window()->currentEvent();
    273                 if (event && event->fromUserGesture())
    274                     return true;
    275             }
    276 
    277             if (isJavaScriptAnchorNavigation())
    278                 return true;
    279         }
    280     }
    281 
    282     return false;
    283 }
    284 
    285 void ScriptController::attachDebugger(JSC::Debugger* debugger)
    286 {
    287     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
    288         attachDebugger(iter->second, debugger);
    289 }
    290 
    291 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
    292 {
    293     if (!shell)
    294         return;
    295 
    296     JSDOMWindow* globalObject = shell->window();
    297     if (debugger)
    298         debugger->attach(globalObject);
    299     else if (JSC::Debugger* currentDebugger = globalObject->debugger())
    300         currentDebugger->detach(globalObject);
    301 }
    302 
    303 void ScriptController::updateDocument()
    304 {
    305     if (!m_frame->document())
    306         return;
    307 
    308     JSLock lock(SilenceAssertionsOnly);
    309     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
    310         iter->second->window()->updateDocument();
    311 }
    312 
    313 void ScriptController::updateSecurityOrigin()
    314 {
    315     // Our bindings do not do anything in this case.
    316 }
    317 
    318 Bindings::RootObject* ScriptController::bindingRootObject()
    319 {
    320     if (!canExecuteScripts())
    321         return 0;
    322 
    323     if (!m_bindingRootObject) {
    324         JSLock lock(SilenceAssertionsOnly);
    325         m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
    326     }
    327     return m_bindingRootObject.get();
    328 }
    329 
    330 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
    331 {
    332     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
    333     if (it != m_rootObjects.end())
    334         return it->second;
    335 
    336     RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
    337 
    338     m_rootObjects.set(nativeHandle, rootObject);
    339     return rootObject.release();
    340 }
    341 
    342 #if ENABLE(NETSCAPE_PLUGIN_API)
    343 
    344 NPObject* ScriptController::windowScriptNPObject()
    345 {
    346     if (!m_windowScriptNPObject) {
    347         if (canExecuteScripts()) {
    348             // JavaScript is enabled, so there is a JavaScript window object.
    349             // Return an NPObject bound to the window object.
    350             JSC::JSLock lock(SilenceAssertionsOnly);
    351             JSObject* win = windowShell(pluginWorld())->window();
    352             ASSERT(win);
    353             Bindings::RootObject* root = bindingRootObject();
    354             m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
    355         } else {
    356             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
    357             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
    358             m_windowScriptNPObject = _NPN_CreateNoScriptObject();
    359         }
    360     }
    361 
    362     return m_windowScriptNPObject;
    363 }
    364 
    365 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
    366 {
    367     JSObject* object = jsObjectForPluginElement(plugin);
    368     if (!object)
    369         return _NPN_CreateNoScriptObject();
    370 
    371     // Wrap the JSObject in an NPObject
    372     return _NPN_CreateScriptObject(0, object, bindingRootObject());
    373 }
    374 
    375 #endif
    376 
    377 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
    378 {
    379     // Can't create JSObjects when JavaScript is disabled
    380     if (!canExecuteScripts())
    381         return 0;
    382 
    383     // Create a JSObject bound to this element
    384     JSLock lock(SilenceAssertionsOnly);
    385     JSDOMWindow* globalObj = globalObject(pluginWorld());
    386     // FIXME: is normal okay? - used for NP plugins?
    387     JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
    388     if (!jsElementValue || !jsElementValue.isObject())
    389         return 0;
    390 
    391     return jsElementValue.getObject();
    392 }
    393 
    394 #if !PLATFORM(MAC)
    395 
    396 void ScriptController::updatePlatformScriptObjects()
    397 {
    398 }
    399 
    400 void ScriptController::disconnectPlatformScriptObjects()
    401 {
    402 }
    403 
    404 #endif
    405 
    406 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
    407 {
    408     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
    409 
    410     if (it == m_rootObjects.end())
    411         return;
    412 
    413     it->second->invalidate();
    414     m_rootObjects.remove(it);
    415 }
    416 
    417 void ScriptController::clearScriptObjects()
    418 {
    419     JSLock lock(SilenceAssertionsOnly);
    420 
    421     RootObjectMap::const_iterator end = m_rootObjects.end();
    422     for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
    423         it->second->invalidate();
    424 
    425     m_rootObjects.clear();
    426 
    427     if (m_bindingRootObject) {
    428         m_bindingRootObject->invalidate();
    429         m_bindingRootObject = 0;
    430     }
    431 
    432 #if ENABLE(NETSCAPE_PLUGIN_API)
    433     if (m_windowScriptNPObject) {
    434         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
    435         // script object properly.
    436         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
    437         _NPN_DeallocateObject(m_windowScriptNPObject);
    438         m_windowScriptNPObject = 0;
    439     }
    440 #endif
    441 }
    442 
    443 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture)
    444 {
    445     ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->loader()->url());
    446 
    447     if (!canExecuteScripts() || isPaused())
    448         return ScriptValue();
    449 
    450     bool wasInExecuteScript = m_inExecuteScript;
    451     m_inExecuteScript = true;
    452 
    453     ScriptValue result = evaluateInWorld(sourceCode, world);
    454 
    455     if (!wasInExecuteScript) {
    456         m_inExecuteScript = false;
    457         Document::updateStyleForAllDocuments();
    458     }
    459 
    460     return result;
    461 }
    462 
    463 } // namespace WebCore
    464