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 "ScriptableDocumentParser.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 "InspectorInstrumentation.h"
     32 #include "JSDocument.h"
     33 #include "JSMainThreadExecState.h"
     34 #include "NP_jsobject.h"
     35 #include "Page.h"
     36 #include "PageGroup.h"
     37 #include "ScriptSourceCode.h"
     38 #include "ScriptValue.h"
     39 #include "Settings.h"
     40 #include "StorageNamespace.h"
     41 #include "UserGestureIndicator.h"
     42 #include "WebCoreJSClientData.h"
     43 #include "npruntime_impl.h"
     44 #include "runtime_root.h"
     45 #include <debugger/Debugger.h>
     46 #include <runtime/InitializeThreading.h>
     47 #include <runtime/JSLock.h>
     48 #include <wtf/Threading.h>
     49 
     50 using namespace JSC;
     51 using namespace std;
     52 
     53 namespace WebCore {
     54 
     55 void ScriptController::initializeThreading()
     56 {
     57     JSC::initializeThreading();
     58     WTF::initializeMainThread();
     59 }
     60 
     61 ScriptController::ScriptController(Frame* frame)
     62     : m_frame(frame)
     63     , m_sourceURL(0)
     64     , m_inExecuteScript(false)
     65     , m_processingTimerCallback(false)
     66     , m_paused(false)
     67     , m_allowPopupsFromPlugin(false)
     68 #if ENABLE(NETSCAPE_PLUGIN_API)
     69     , m_windowScriptNPObject(0)
     70 #endif
     71 #if PLATFORM(MAC)
     72     , m_windowScriptObject(0)
     73 #endif
     74 {
     75 #if PLATFORM(MAC) && ENABLE(JAVA_BRIDGE)
     76     static bool initializedJavaJSBindings;
     77     if (!initializedJavaJSBindings) {
     78         initializedJavaJSBindings = true;
     79         initJavaJSBindings();
     80     }
     81 #endif
     82 }
     83 
     84 ScriptController::~ScriptController()
     85 {
     86     disconnectPlatformScriptObjects();
     87 
     88     if (m_cacheableBindingRootObject) {
     89         m_cacheableBindingRootObject->invalidate();
     90         m_cacheableBindingRootObject = 0;
     91     }
     92 
     93     // It's likely that destroying m_windowShells will create a lot of garbage.
     94     if (!m_windowShells.isEmpty()) {
     95         while (!m_windowShells.isEmpty())
     96             destroyWindowShell(m_windowShells.begin()->first.get());
     97         gcController().garbageCollectSoon();
     98     }
     99 }
    100 
    101 void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
    102 {
    103     ASSERT(m_windowShells.contains(world));
    104     m_windowShells.remove(world);
    105     world->didDestroyWindowShell(this);
    106 }
    107 
    108 JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world)
    109 {
    110     ASSERT(!m_windowShells.contains(world));
    111     Strong<JSDOMWindowShell> windowShell(*world->globalData(), new JSDOMWindowShell(m_frame->domWindow(), world));
    112     Strong<JSDOMWindowShell> windowShell2(windowShell);
    113     m_windowShells.add(world, windowShell);
    114     world->didCreateWindowShell(this);
    115     return windowShell.get();
    116 }
    117 
    118 ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world)
    119 {
    120     const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
    121     String sourceURL = ustringToString(jsSourceCode.provider()->url());
    122 
    123     // evaluate code. Returns the JS return value or 0
    124     // if there was none, an error occurred or the type couldn't be converted.
    125 
    126     // inlineCode is true for <a href="javascript:doSomething()">
    127     // and false for <script>doSomething()</script>. Check if it has the
    128     // expected value in all cases.
    129     // See smart window.open policy for where this is used.
    130     JSDOMWindowShell* shell = windowShell(world);
    131     ExecState* exec = shell->window()->globalExec();
    132     const String* savedSourceURL = m_sourceURL;
    133     m_sourceURL = &sourceURL;
    134 
    135     JSLock lock(SilenceAssertionsOnly);
    136 
    137     RefPtr<Frame> protect = m_frame;
    138 
    139     InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
    140 
    141     exec->globalData().timeoutChecker.start();
    142     Completion comp = JSMainThreadExecState::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
    143     exec->globalData().timeoutChecker.stop();
    144 
    145     InspectorInstrumentation::didEvaluateScript(cookie);
    146 
    147     // Evaluating the JavaScript could cause the frame to be deallocated
    148     // so we start the keep alive timer here.
    149     m_frame->keepAlive();
    150 
    151     if (comp.complType() == Normal || comp.complType() == ReturnValue) {
    152         m_sourceURL = savedSourceURL;
    153         return ScriptValue(exec->globalData(), comp.value());
    154     }
    155 
    156     if (comp.complType() == Throw || comp.complType() == Interrupted)
    157         reportException(exec, comp.value());
    158 
    159     m_sourceURL = savedSourceURL;
    160     return ScriptValue();
    161 }
    162 
    163 ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode)
    164 {
    165     return evaluateInWorld(sourceCode, mainThreadNormalWorld());
    166 }
    167 
    168 PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
    169 {
    170     return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData());
    171 }
    172 
    173 void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
    174 {
    175     static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
    176 }
    177 
    178 void ScriptController::clearWindowShell(bool goingIntoPageCache)
    179 {
    180     if (m_windowShells.isEmpty())
    181         return;
    182 
    183     JSLock lock(SilenceAssertionsOnly);
    184 
    185     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
    186         JSDOMWindowShell* windowShell = iter->second.get();
    187 
    188         // Clear the debugger from the current window before setting the new window.
    189         attachDebugger(windowShell, 0);
    190 
    191         windowShell->window()->willRemoveFromWindowShell();
    192         windowShell->setWindow(m_frame->domWindow());
    193 
    194         // An m_cacheableBindingRootObject persists between page navigations
    195         // so needs to know about the new JSDOMWindow.
    196         if (m_cacheableBindingRootObject)
    197             m_cacheableBindingRootObject->updateGlobalObject(windowShell->window());
    198 
    199         if (Page* page = m_frame->page()) {
    200             attachDebugger(windowShell, page->debugger());
    201             windowShell->window()->setProfileGroup(page->group().identifier());
    202         }
    203     }
    204 
    205     // It's likely that resetting our windows created a lot of garbage, unless
    206     // it went in a back/forward cache.
    207     if (!goingIntoPageCache)
    208         gcController().garbageCollectSoon();
    209 }
    210 
    211 JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
    212 {
    213     ASSERT(!m_windowShells.contains(world));
    214 
    215     JSLock lock(SilenceAssertionsOnly);
    216 
    217     JSDOMWindowShell* windowShell = createWindowShell(world);
    218 
    219     windowShell->window()->updateDocument();
    220 
    221     if (Page* page = m_frame->page()) {
    222         attachDebugger(windowShell, page->debugger());
    223         windowShell->window()->setProfileGroup(page->group().identifier());
    224     }
    225 
    226     m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
    227 
    228     return windowShell;
    229 }
    230 
    231 int ScriptController::eventHandlerLineNumber() const
    232 {
    233     // JSC expects 1-based line numbers, so we must add one here to get it right.
    234     ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
    235     if (parser)
    236         return parser->lineNumber() + 1;
    237     return 0;
    238 }
    239 
    240 bool ScriptController::processingUserGesture()
    241 {
    242     ExecState* exec = JSMainThreadExecState::currentState();
    243     Frame* frame = exec ? toDynamicFrame(exec) : 0;
    244     // No script is running, so it is user-initiated unless the gesture stack
    245     // explicitly says it is not.
    246     if (!frame)
    247         return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture;
    248 
    249     // FIXME: We check the plugin popup flag and javascript anchor navigation
    250     // from the dynamic frame becuase they should only be initiated on the
    251     // dynamic frame in which execution began if they do happen.
    252     ScriptController* scriptController = frame->script();
    253     ASSERT(scriptController);
    254     if (scriptController->allowPopupsFromPlugin() || scriptController->isJavaScriptAnchorNavigation())
    255         return true;
    256 
    257     // If a DOM event is being processed, check that it was initiated by the user
    258     // and that it is in the whitelist of event types allowed to generate pop-ups.
    259     if (JSDOMWindowShell* shell = scriptController->existingWindowShell(currentWorld(exec)))
    260         if (Event* event = shell->window()->currentEvent())
    261             return event->fromUserGesture();
    262 
    263     return UserGestureIndicator::processingUserGesture();
    264 }
    265 
    266 // FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
    267 bool ScriptController::isJavaScriptAnchorNavigation() const
    268 {
    269     // This is the <a href="javascript:window.open('...')> case -> we let it through
    270     if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
    271         return true;
    272 
    273     // This is the <script>window.open(...)</script> case or a timer callback -> block it
    274     return false;
    275 }
    276 
    277 bool ScriptController::anyPageIsProcessingUserGesture() const
    278 {
    279     Page* page = m_frame->page();
    280     if (!page)
    281         return false;
    282 
    283     const HashSet<Page*>& pages = page->group().pages();
    284     HashSet<Page*>::const_iterator end = pages.end();
    285     for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
    286         for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
    287             ScriptController* script = frame->script();
    288 
    289             if (script->m_allowPopupsFromPlugin)
    290                 return true;
    291 
    292             const ShellMap::const_iterator iterEnd = m_windowShells.end();
    293             for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) {
    294                 JSDOMWindowShell* shell = iter->second.get();
    295                 Event* event = shell->window()->currentEvent();
    296                 if (event && event->fromUserGesture())
    297                     return true;
    298             }
    299 
    300             if (isJavaScriptAnchorNavigation())
    301                 return true;
    302         }
    303     }
    304 
    305     return false;
    306 }
    307 
    308 bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
    309 {
    310     ExecState* exec = JSMainThreadExecState::currentState();
    311     if (exec)
    312         return allowsAccessFromFrame(exec, frame);
    313     // If the current state is 0 we're in a call path where the DOM security
    314     // check doesn't apply (eg. parser).
    315     return true;
    316 }
    317 
    318 void ScriptController::attachDebugger(JSC::Debugger* debugger)
    319 {
    320     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
    321         attachDebugger(iter->second.get(), debugger);
    322 }
    323 
    324 void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
    325 {
    326     if (!shell)
    327         return;
    328 
    329     JSDOMWindow* globalObject = shell->window();
    330     if (debugger)
    331         debugger->attach(globalObject);
    332     else if (JSC::Debugger* currentDebugger = globalObject->debugger())
    333         currentDebugger->detach(globalObject);
    334 }
    335 
    336 void ScriptController::updateDocument()
    337 {
    338     if (!m_frame->document())
    339         return;
    340 
    341     JSLock lock(SilenceAssertionsOnly);
    342     for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
    343         iter->second->window()->updateDocument();
    344 }
    345 
    346 void ScriptController::updateSecurityOrigin()
    347 {
    348     // Our bindings do not do anything in this case.
    349 }
    350 
    351 Bindings::RootObject* ScriptController::cacheableBindingRootObject()
    352 {
    353     if (!canExecuteScripts(NotAboutToExecuteScript))
    354         return 0;
    355 
    356     if (!m_cacheableBindingRootObject) {
    357         JSLock lock(SilenceAssertionsOnly);
    358         m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
    359     }
    360     return m_cacheableBindingRootObject.get();
    361 }
    362 
    363 Bindings::RootObject* ScriptController::bindingRootObject()
    364 {
    365     if (!canExecuteScripts(NotAboutToExecuteScript))
    366         return 0;
    367 
    368     if (!m_bindingRootObject) {
    369         JSLock lock(SilenceAssertionsOnly);
    370         m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
    371     }
    372     return m_bindingRootObject.get();
    373 }
    374 
    375 PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
    376 {
    377     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
    378     if (it != m_rootObjects.end())
    379         return it->second;
    380 
    381     RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
    382 
    383     m_rootObjects.set(nativeHandle, rootObject);
    384     return rootObject.release();
    385 }
    386 
    387 #if ENABLE(INSPECTOR)
    388 void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
    389 {
    390 }
    391 #endif
    392 
    393 #if ENABLE(NETSCAPE_PLUGIN_API)
    394 
    395 NPObject* ScriptController::windowScriptNPObject()
    396 {
    397     if (!m_windowScriptNPObject) {
    398         if (canExecuteScripts(NotAboutToExecuteScript)) {
    399             // JavaScript is enabled, so there is a JavaScript window object.
    400             // Return an NPObject bound to the window object.
    401             JSC::JSLock lock(SilenceAssertionsOnly);
    402             JSObject* win = windowShell(pluginWorld())->window();
    403             ASSERT(win);
    404             Bindings::RootObject* root = bindingRootObject();
    405             m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
    406         } else {
    407             // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
    408             // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
    409             m_windowScriptNPObject = _NPN_CreateNoScriptObject();
    410         }
    411     }
    412 
    413     return m_windowScriptNPObject;
    414 }
    415 
    416 NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
    417 {
    418     JSObject* object = jsObjectForPluginElement(plugin);
    419     if (!object)
    420         return _NPN_CreateNoScriptObject();
    421 
    422     // Wrap the JSObject in an NPObject
    423     return _NPN_CreateScriptObject(0, object, bindingRootObject());
    424 }
    425 
    426 #endif
    427 
    428 JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
    429 {
    430     // Can't create JSObjects when JavaScript is disabled
    431     if (!canExecuteScripts(NotAboutToExecuteScript))
    432         return 0;
    433 
    434     // Create a JSObject bound to this element
    435     JSLock lock(SilenceAssertionsOnly);
    436     JSDOMWindow* globalObj = globalObject(pluginWorld());
    437     // FIXME: is normal okay? - used for NP plugins?
    438     JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
    439     if (!jsElementValue || !jsElementValue.isObject())
    440         return 0;
    441 
    442     return jsElementValue.getObject();
    443 }
    444 
    445 #if !PLATFORM(MAC)
    446 
    447 void ScriptController::updatePlatformScriptObjects()
    448 {
    449 }
    450 
    451 void ScriptController::disconnectPlatformScriptObjects()
    452 {
    453 }
    454 
    455 #endif
    456 
    457 void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
    458 {
    459     RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
    460 
    461     if (it == m_rootObjects.end())
    462         return;
    463 
    464     it->second->invalidate();
    465     m_rootObjects.remove(it);
    466 }
    467 
    468 void ScriptController::clearScriptObjects()
    469 {
    470     JSLock lock(SilenceAssertionsOnly);
    471 
    472     RootObjectMap::const_iterator end = m_rootObjects.end();
    473     for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
    474         it->second->invalidate();
    475 
    476     m_rootObjects.clear();
    477 
    478     if (m_bindingRootObject) {
    479         m_bindingRootObject->invalidate();
    480         m_bindingRootObject = 0;
    481     }
    482 
    483 #if ENABLE(NETSCAPE_PLUGIN_API)
    484     if (m_windowScriptNPObject) {
    485         // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
    486         // script object properly.
    487         // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
    488         _NPN_DeallocateObject(m_windowScriptNPObject);
    489         m_windowScriptNPObject = 0;
    490     }
    491 #endif
    492 }
    493 
    494 ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture)
    495 {
    496     ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->document()->url());
    497 
    498     if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
    499         return ScriptValue();
    500 
    501     bool wasInExecuteScript = m_inExecuteScript;
    502     m_inExecuteScript = true;
    503 
    504     ScriptValue result = evaluateInWorld(sourceCode, world);
    505 
    506     if (!wasInExecuteScript) {
    507         m_inExecuteScript = false;
    508         Document::updateStyleForAllDocuments();
    509     }
    510 
    511     return result;
    512 }
    513 
    514 } // namespace WebCore
    515