summaryrefslogtreecommitdiffstats
path: root/Source/WebCore/bindings/js/ScriptController.cpp
diff options
context:
space:
mode:
authorSteve Block <steveblock@google.com>2011-05-06 11:45:16 +0100
committerSteve Block <steveblock@google.com>2011-05-12 13:44:10 +0100
commitcad810f21b803229eb11403f9209855525a25d57 (patch)
tree29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/bindings/js/ScriptController.cpp
parent121b0cf4517156d0ac5111caf9830c51b69bae8f (diff)
downloadexternal_webkit-cad810f21b803229eb11403f9209855525a25d57.zip
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.gz
external_webkit-cad810f21b803229eb11403f9209855525a25d57.tar.bz2
Merge WebKit at r75315: Initial merge by git.
Change-Id: I570314b346ce101c935ed22a626b48c2af266b84
Diffstat (limited to 'Source/WebCore/bindings/js/ScriptController.cpp')
-rw-r--r--Source/WebCore/bindings/js/ScriptController.cpp520
1 files changed, 520 insertions, 0 deletions
diff --git a/Source/WebCore/bindings/js/ScriptController.cpp b/Source/WebCore/bindings/js/ScriptController.cpp
new file mode 100644
index 0000000..cf55080
--- /dev/null
+++ b/Source/WebCore/bindings/js/ScriptController.cpp
@@ -0,0 +1,520 @@
+/*
+ * Copyright (C) 1999-2001 Harri Porten (porten@kde.org)
+ * Copyright (C) 2001 Peter Kelly (pmk@post.com)
+ * Copyright (C) 2006, 2007, 2008 Apple Inc. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "ScriptController.h"
+
+#include "ScriptableDocumentParser.h"
+#include "Event.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "FrameLoaderClient.h"
+#include "GCController.h"
+#include "HTMLPlugInElement.h"
+#include "InspectorInstrumentation.h"
+#include "JSDocument.h"
+#include "JSMainThreadExecState.h"
+#include "NP_jsobject.h"
+#include "Page.h"
+#include "PageGroup.h"
+#include "ScriptSourceCode.h"
+#include "ScriptValue.h"
+#include "Settings.h"
+#include "StorageNamespace.h"
+#include "UserGestureIndicator.h"
+#include "WebCoreJSClientData.h"
+#include "XSSAuditor.h"
+#include "npruntime_impl.h"
+#include "runtime_root.h"
+#include <debugger/Debugger.h>
+#include <runtime/InitializeThreading.h>
+#include <runtime/JSLock.h>
+#include <wtf/Threading.h>
+
+using namespace JSC;
+using namespace std;
+
+namespace WebCore {
+
+void ScriptController::initializeThreading()
+{
+ JSC::initializeThreading();
+ WTF::initializeMainThread();
+}
+
+ScriptController::ScriptController(Frame* frame)
+ : m_frame(frame)
+ , m_sourceURL(0)
+ , m_inExecuteScript(false)
+ , m_processingTimerCallback(false)
+ , m_paused(false)
+ , m_allowPopupsFromPlugin(false)
+#if ENABLE(NETSCAPE_PLUGIN_API)
+ , m_windowScriptNPObject(0)
+#endif
+#if PLATFORM(MAC)
+ , m_windowScriptObject(0)
+#endif
+ , m_XSSAuditor(new XSSAuditor(frame))
+{
+#if PLATFORM(MAC) && ENABLE(JAVA_BRIDGE)
+ static bool initializedJavaJSBindings;
+ if (!initializedJavaJSBindings) {
+ initializedJavaJSBindings = true;
+ initJavaJSBindings();
+ }
+#endif
+}
+
+ScriptController::~ScriptController()
+{
+ disconnectPlatformScriptObjects();
+
+ if (m_cacheableBindingRootObject) {
+ m_cacheableBindingRootObject->invalidate();
+ m_cacheableBindingRootObject = 0;
+ }
+
+ // It's likely that destroying m_windowShells will create a lot of garbage.
+ if (!m_windowShells.isEmpty()) {
+ while (!m_windowShells.isEmpty())
+ destroyWindowShell(m_windowShells.begin()->first.get());
+ gcController().garbageCollectSoon();
+ }
+}
+
+void ScriptController::destroyWindowShell(DOMWrapperWorld* world)
+{
+ ASSERT(m_windowShells.contains(world));
+ m_windowShells.remove(world);
+ world->didDestroyWindowShell(this);
+}
+
+JSDOMWindowShell* ScriptController::createWindowShell(DOMWrapperWorld* world)
+{
+ ASSERT(!m_windowShells.contains(world));
+ JSDOMWindowShell* windowShell = new JSDOMWindowShell(m_frame->domWindow(), world);
+ m_windowShells.add(world, windowShell);
+ world->didCreateWindowShell(this);
+ return windowShell;
+}
+
+ScriptValue ScriptController::evaluateInWorld(const ScriptSourceCode& sourceCode, DOMWrapperWorld* world, ShouldAllowXSS shouldAllowXSS)
+{
+ const SourceCode& jsSourceCode = sourceCode.jsSourceCode();
+ String sourceURL = ustringToString(jsSourceCode.provider()->url());
+
+ if (shouldAllowXSS == DoNotAllowXSS && !m_XSSAuditor->canEvaluate(sourceCode.source())) {
+ // This script is not safe to be evaluated.
+ return JSValue();
+ }
+
+ // evaluate code. Returns the JS return value or 0
+ // if there was none, an error occurred or the type couldn't be converted.
+
+ // inlineCode is true for <a href="javascript:doSomething()">
+ // and false for <script>doSomething()</script>. Check if it has the
+ // expected value in all cases.
+ // See smart window.open policy for where this is used.
+ JSDOMWindowShell* shell = windowShell(world);
+ ExecState* exec = shell->window()->globalExec();
+ const String* savedSourceURL = m_sourceURL;
+ m_sourceURL = &sourceURL;
+
+ JSLock lock(SilenceAssertionsOnly);
+
+ RefPtr<Frame> protect = m_frame;
+
+ InspectorInstrumentationCookie cookie = InspectorInstrumentation::willEvaluateScript(m_frame, sourceURL, sourceCode.startLine());
+
+ exec->globalData().timeoutChecker.start();
+ Completion comp = JSMainThreadExecState::evaluate(exec, exec->dynamicGlobalObject()->globalScopeChain(), jsSourceCode, shell);
+ exec->globalData().timeoutChecker.stop();
+
+ InspectorInstrumentation::didEvaluateScript(cookie);
+
+ // Evaluating the JavaScript could cause the frame to be deallocated
+ // so we start the keep alive timer here.
+ m_frame->keepAlive();
+
+ if (comp.complType() == Normal || comp.complType() == ReturnValue) {
+ m_sourceURL = savedSourceURL;
+ return comp.value();
+ }
+
+ if (comp.complType() == Throw || comp.complType() == Interrupted)
+ reportException(exec, comp.value());
+
+ m_sourceURL = savedSourceURL;
+ return JSValue();
+}
+
+ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ShouldAllowXSS shouldAllowXSS)
+{
+ return evaluateInWorld(sourceCode, mainThreadNormalWorld(), shouldAllowXSS);
+}
+
+PassRefPtr<DOMWrapperWorld> ScriptController::createWorld()
+{
+ return DOMWrapperWorld::create(JSDOMWindow::commonJSGlobalData());
+}
+
+void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
+{
+ static_cast<WebCoreJSClientData*>(JSDOMWindow::commonJSGlobalData()->clientData)->getAllWorlds(worlds);
+}
+
+void ScriptController::clearWindowShell(bool goingIntoPageCache)
+{
+ if (m_windowShells.isEmpty())
+ return;
+
+ JSLock lock(SilenceAssertionsOnly);
+
+ for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter) {
+ JSDOMWindowShell* windowShell = iter->second;
+
+ // Clear the debugger from the current window before setting the new window.
+ attachDebugger(windowShell, 0);
+
+ windowShell->window()->willRemoveFromWindowShell();
+ windowShell->setWindow(m_frame->domWindow());
+
+ // An m_cacheableBindingRootObject persists between page navigations
+ // so needs to know about the new JSDOMWindow.
+ if (m_cacheableBindingRootObject)
+ m_cacheableBindingRootObject->updateGlobalObject(windowShell->window());
+
+ if (Page* page = m_frame->page()) {
+ attachDebugger(windowShell, page->debugger());
+ windowShell->window()->setProfileGroup(page->group().identifier());
+ }
+ }
+
+ // It's likely that resetting our windows created a lot of garbage, unless
+ // it went in a back/forward cache.
+ if (!goingIntoPageCache)
+ gcController().garbageCollectSoon();
+}
+
+JSDOMWindowShell* ScriptController::initScript(DOMWrapperWorld* world)
+{
+ ASSERT(!m_windowShells.contains(world));
+
+ JSLock lock(SilenceAssertionsOnly);
+
+ JSDOMWindowShell* windowShell = createWindowShell(world);
+
+ windowShell->window()->updateDocument();
+
+ if (Page* page = m_frame->page()) {
+ attachDebugger(windowShell, page->debugger());
+ windowShell->window()->setProfileGroup(page->group().identifier());
+ }
+
+ m_frame->loader()->dispatchDidClearWindowObjectInWorld(world);
+
+ return windowShell;
+}
+
+int ScriptController::eventHandlerLineNumber() const
+{
+ // JSC expects 1-based line numbers, so we must add one here to get it right.
+ ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
+ if (parser)
+ return parser->lineNumber() + 1;
+ return 0;
+}
+
+bool ScriptController::processingUserGesture()
+{
+ ExecState* exec = JSMainThreadExecState::currentState();
+ Frame* frame = exec ? toDynamicFrame(exec) : 0;
+ // No script is running, so it is user-initiated unless the gesture stack
+ // explicitly says it is not.
+ if (!frame)
+ return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture;
+
+ // FIXME: We check the plugin popup flag and javascript anchor navigation
+ // from the dynamic frame becuase they should only be initiated on the
+ // dynamic frame in which execution began if they do happen.
+ ScriptController* scriptController = frame->script();
+ ASSERT(scriptController);
+ if (scriptController->allowPopupsFromPlugin() || scriptController->isJavaScriptAnchorNavigation())
+ return true;
+
+ // If a DOM event is being processed, check that it was initiated by the user
+ // and that it is in the whitelist of event types allowed to generate pop-ups.
+ if (JSDOMWindowShell* shell = scriptController->existingWindowShell(currentWorld(exec)))
+ if (Event* event = shell->window()->currentEvent())
+ return event->fromUserGesture();
+
+ return UserGestureIndicator::processingUserGesture();
+}
+
+// FIXME: This seems like an insufficient check to verify a click on a javascript: anchor.
+bool ScriptController::isJavaScriptAnchorNavigation() const
+{
+ // This is the <a href="javascript:window.open('...')> case -> we let it through
+ if (m_sourceURL && m_sourceURL->isNull() && !m_processingTimerCallback)
+ return true;
+
+ // This is the <script>window.open(...)</script> case or a timer callback -> block it
+ return false;
+}
+
+bool ScriptController::anyPageIsProcessingUserGesture() const
+{
+ Page* page = m_frame->page();
+ if (!page)
+ return false;
+
+ const HashSet<Page*>& pages = page->group().pages();
+ HashSet<Page*>::const_iterator end = pages.end();
+ for (HashSet<Page*>::const_iterator it = pages.begin(); it != end; ++it) {
+ for (Frame* frame = page->mainFrame(); frame; frame = frame->tree()->traverseNext()) {
+ ScriptController* script = frame->script();
+
+ if (script->m_allowPopupsFromPlugin)
+ return true;
+
+ const ShellMap::const_iterator iterEnd = m_windowShells.end();
+ for (ShellMap::const_iterator iter = m_windowShells.begin(); iter != iterEnd; ++iter) {
+ JSDOMWindowShell* shell = iter->second.get();
+ Event* event = shell->window()->currentEvent();
+ if (event && event->fromUserGesture())
+ return true;
+ }
+
+ if (isJavaScriptAnchorNavigation())
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
+{
+ ExecState* exec = JSMainThreadExecState::currentState();
+ if (exec)
+ return allowsAccessFromFrame(exec, frame);
+ // If the current state is 0 we're in a call path where the DOM security
+ // check doesn't apply (eg. parser).
+ return true;
+}
+
+void ScriptController::attachDebugger(JSC::Debugger* debugger)
+{
+ for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
+ attachDebugger(iter->second, debugger);
+}
+
+void ScriptController::attachDebugger(JSDOMWindowShell* shell, JSC::Debugger* debugger)
+{
+ if (!shell)
+ return;
+
+ JSDOMWindow* globalObject = shell->window();
+ if (debugger)
+ debugger->attach(globalObject);
+ else if (JSC::Debugger* currentDebugger = globalObject->debugger())
+ currentDebugger->detach(globalObject);
+}
+
+void ScriptController::updateDocument()
+{
+ if (!m_frame->document())
+ return;
+
+ JSLock lock(SilenceAssertionsOnly);
+ for (ShellMap::iterator iter = m_windowShells.begin(); iter != m_windowShells.end(); ++iter)
+ iter->second->window()->updateDocument();
+}
+
+void ScriptController::updateSecurityOrigin()
+{
+ // Our bindings do not do anything in this case.
+}
+
+Bindings::RootObject* ScriptController::cacheableBindingRootObject()
+{
+ if (!canExecuteScripts(NotAboutToExecuteScript))
+ return 0;
+
+ if (!m_cacheableBindingRootObject) {
+ JSLock lock(SilenceAssertionsOnly);
+ m_cacheableBindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
+ }
+ return m_cacheableBindingRootObject.get();
+}
+
+Bindings::RootObject* ScriptController::bindingRootObject()
+{
+ if (!canExecuteScripts(NotAboutToExecuteScript))
+ return 0;
+
+ if (!m_bindingRootObject) {
+ JSLock lock(SilenceAssertionsOnly);
+ m_bindingRootObject = Bindings::RootObject::create(0, globalObject(pluginWorld()));
+ }
+ return m_bindingRootObject.get();
+}
+
+PassRefPtr<Bindings::RootObject> ScriptController::createRootObject(void* nativeHandle)
+{
+ RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
+ if (it != m_rootObjects.end())
+ return it->second;
+
+ RefPtr<Bindings::RootObject> rootObject = Bindings::RootObject::create(nativeHandle, globalObject(pluginWorld()));
+
+ m_rootObjects.set(nativeHandle, rootObject);
+ return rootObject.release();
+}
+
+#if ENABLE(INSPECTOR)
+void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
+{
+}
+#endif
+
+#if ENABLE(NETSCAPE_PLUGIN_API)
+
+NPObject* ScriptController::windowScriptNPObject()
+{
+ if (!m_windowScriptNPObject) {
+ if (canExecuteScripts(NotAboutToExecuteScript)) {
+ // JavaScript is enabled, so there is a JavaScript window object.
+ // Return an NPObject bound to the window object.
+ JSC::JSLock lock(SilenceAssertionsOnly);
+ JSObject* win = windowShell(pluginWorld())->window();
+ ASSERT(win);
+ Bindings::RootObject* root = bindingRootObject();
+ m_windowScriptNPObject = _NPN_CreateScriptObject(0, win, root);
+ } else {
+ // JavaScript is not enabled, so we cannot bind the NPObject to the JavaScript window object.
+ // Instead, we create an NPObject of a different class, one which is not bound to a JavaScript object.
+ m_windowScriptNPObject = _NPN_CreateNoScriptObject();
+ }
+ }
+
+ return m_windowScriptNPObject;
+}
+
+NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
+{
+ JSObject* object = jsObjectForPluginElement(plugin);
+ if (!object)
+ return _NPN_CreateNoScriptObject();
+
+ // Wrap the JSObject in an NPObject
+ return _NPN_CreateScriptObject(0, object, bindingRootObject());
+}
+
+#endif
+
+JSObject* ScriptController::jsObjectForPluginElement(HTMLPlugInElement* plugin)
+{
+ // Can't create JSObjects when JavaScript is disabled
+ if (!canExecuteScripts(NotAboutToExecuteScript))
+ return 0;
+
+ // Create a JSObject bound to this element
+ JSLock lock(SilenceAssertionsOnly);
+ JSDOMWindow* globalObj = globalObject(pluginWorld());
+ // FIXME: is normal okay? - used for NP plugins?
+ JSValue jsElementValue = toJS(globalObj->globalExec(), globalObj, plugin);
+ if (!jsElementValue || !jsElementValue.isObject())
+ return 0;
+
+ return jsElementValue.getObject();
+}
+
+#if !PLATFORM(MAC)
+
+void ScriptController::updatePlatformScriptObjects()
+{
+}
+
+void ScriptController::disconnectPlatformScriptObjects()
+{
+}
+
+#endif
+
+void ScriptController::cleanupScriptObjectsForPlugin(void* nativeHandle)
+{
+ RootObjectMap::iterator it = m_rootObjects.find(nativeHandle);
+
+ if (it == m_rootObjects.end())
+ return;
+
+ it->second->invalidate();
+ m_rootObjects.remove(it);
+}
+
+void ScriptController::clearScriptObjects()
+{
+ JSLock lock(SilenceAssertionsOnly);
+
+ RootObjectMap::const_iterator end = m_rootObjects.end();
+ for (RootObjectMap::const_iterator it = m_rootObjects.begin(); it != end; ++it)
+ it->second->invalidate();
+
+ m_rootObjects.clear();
+
+ if (m_bindingRootObject) {
+ m_bindingRootObject->invalidate();
+ m_bindingRootObject = 0;
+ }
+
+#if ENABLE(NETSCAPE_PLUGIN_API)
+ if (m_windowScriptNPObject) {
+ // Call _NPN_DeallocateObject() instead of _NPN_ReleaseObject() so that we don't leak if a plugin fails to release the window
+ // script object properly.
+ // This shouldn't cause any problems for plugins since they should have already been stopped and destroyed at this point.
+ _NPN_DeallocateObject(m_windowScriptNPObject);
+ m_windowScriptNPObject = 0;
+ }
+#endif
+}
+
+ScriptValue ScriptController::executeScriptInWorld(DOMWrapperWorld* world, const String& script, bool forceUserGesture, ShouldAllowXSS shouldAllowXSS)
+{
+ ScriptSourceCode sourceCode(script, forceUserGesture ? KURL() : m_frame->document()->url());
+
+ if (!canExecuteScripts(AboutToExecuteScript) || isPaused())
+ return ScriptValue();
+
+ bool wasInExecuteScript = m_inExecuteScript;
+ m_inExecuteScript = true;
+
+ ScriptValue result = evaluateInWorld(sourceCode, world, shouldAllowXSS);
+
+ if (!wasInExecuteScript) {
+ m_inExecuteScript = false;
+ Document::updateStyleForAllDocuments();
+ }
+
+ return result;
+}
+
+} // namespace WebCore