--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/bindings/v8/ScriptController.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,480 @@
+/*
+ * Copyright (C) 2008, 2009 Google Inc. All rights reserved.
+ * Copyright (C) 2009 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "ScriptController.h"
+
+#include "PlatformBridge.h"
+#include "Document.h"
+#include "ScriptCallStack.h"
+#include "ScriptableDocumentParser.h"
+#include "DOMWindow.h"
+#include "Event.h"
+#include "EventListener.h"
+#include "EventNames.h"
+#include "Frame.h"
+#include "FrameLoaderClient.h"
+#include "Node.h"
+#include "NotImplemented.h"
+#include "npruntime_impl.h"
+#include "npruntime_priv.h"
+#include "NPV8Object.h"
+#include "ScriptSourceCode.h"
+#include "Settings.h"
+#include "UserGestureIndicator.h"
+#include "V8Binding.h"
+#include "V8BindingState.h"
+#include "V8DOMWindow.h"
+#include "V8Event.h"
+#include "V8HiddenPropertyName.h"
+#include "V8HTMLEmbedElement.h"
+#include "V8IsolatedContext.h"
+#include "V8NPObject.h"
+#include "V8Proxy.h"
+#include "Widget.h"
+#include "XSSAuditor.h"
+#include <wtf/StdLibExtras.h>
+#include <wtf/text/CString.h>
+
+namespace WebCore {
+
+void ScriptController::initializeThreading()
+{
+ static bool initializedThreading = false;
+ if (!initializedThreading) {
+ WTF::initializeThreading();
+ WTF::initializeMainThread();
+ initializedThreading = true;
+ }
+}
+
+void ScriptController::setFlags(const char* string, int length)
+{
+ v8::V8::SetFlagsFromString(string, length);
+}
+
+Frame* ScriptController::retrieveFrameForEnteredContext()
+{
+ return V8Proxy::retrieveFrameForEnteredContext();
+}
+
+Frame* ScriptController::retrieveFrameForCurrentContext()
+{
+ return V8Proxy::retrieveFrameForCurrentContext();
+}
+
+bool ScriptController::canAccessFromCurrentOrigin(Frame *frame)
+{
+ return !v8::Context::InContext() || V8BindingSecurity::canAccessFrame(V8BindingState::Only(), frame, true);
+}
+
+bool ScriptController::isSafeScript(Frame* target)
+{
+ return V8BindingSecurity::canAccessFrame(V8BindingState::Only(), target, true);
+}
+
+void ScriptController::gcProtectJSWrapper(void* domObject)
+{
+ V8GCController::gcProtect(domObject);
+}
+
+void ScriptController::gcUnprotectJSWrapper(void* domObject)
+{
+ V8GCController::gcUnprotect(domObject);
+}
+
+ScriptController::ScriptController(Frame* frame)
+ : m_frame(frame)
+ , m_sourceURL(0)
+ , m_inExecuteScript(false)
+ , m_processingTimerCallback(false)
+ , m_paused(false)
+ , m_proxy(new V8Proxy(frame))
+#if ENABLE(NETSCAPE_PLUGIN_API)
+ , m_windowScriptNPObject(0)
+#endif
+ , m_XSSAuditor(new XSSAuditor(frame))
+{
+}
+
+ScriptController::~ScriptController()
+{
+ m_proxy->disconnectFrame();
+}
+
+void ScriptController::clearScriptObjects()
+{
+ PluginObjectMap::iterator it = m_pluginObjects.begin();
+ for (; it != m_pluginObjects.end(); ++it) {
+ _NPN_UnregisterObject(it->second);
+ _NPN_ReleaseObject(it->second);
+ }
+ m_pluginObjects.clear();
+
+#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
+}
+
+void ScriptController::updateSecurityOrigin()
+{
+ m_proxy->windowShell()->updateSecurityOrigin();
+}
+
+void ScriptController::updatePlatformScriptObjects()
+{
+ notImplemented();
+}
+
+bool ScriptController::processingUserGesture(DOMWrapperWorld*) const
+{
+ // No script is running, so it is user-initiated unless the gesture stack
+ // explicitly says it is not.
+ if (!m_proxy->executingScript())
+ return UserGestureIndicator::getUserGestureState() != DefinitelyNotProcessingUserGesture;
+
+ v8::HandleScope handleScope;
+ v8::Handle<v8::Context> v8Context = m_proxy->mainWorldContext();
+ // FIXME: find all cases context can be empty:
+ // 1) JS is disabled;
+ // 2) page is NULL;
+ if (v8Context.IsEmpty())
+ return true;
+
+ v8::Context::Scope scope(v8Context);
+
+ v8::Handle<v8::Object> global = v8Context->Global();
+ v8::Handle<v8::String> eventSymbol = V8HiddenPropertyName::event();
+ v8::Handle<v8::Value> jsEvent = global->GetHiddenValue(eventSymbol);
+ Event* event = V8DOMWrapper::isValidDOMObject(jsEvent) ? V8Event::toNative(v8::Handle<v8::Object>::Cast(jsEvent)) : 0;
+
+ // Based on code from JSC's ScriptController::processingUserGesture.
+ // Note: This is more liberal than Firefox's implementation.
+ if (event) {
+ // Event::fromUserGesture will return false when UserGestureIndicator::processingUserGesture() returns false.
+ return event->fromUserGesture();
+ }
+ if (m_sourceURL && m_sourceURL->isNull() && !m_proxy->timerCallback()) {
+ // This is the <a href="javascript:window.open('...')> case -> we let it through.
+ return true;
+ }
+
+ // This is the <script>window.open(...)</script> case or a timer callback -> block it.
+ // Based on JSC version, use returned value of UserGestureIndicator::processingUserGesture for all other situations.
+ return UserGestureIndicator::processingUserGesture();
+}
+
+bool ScriptController::anyPageIsProcessingUserGesture() const
+{
+ // FIXME: is this right?
+ return processingUserGesture();
+}
+
+void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources)
+{
+ m_proxy->evaluateInIsolatedWorld(worldID, sources, 0);
+}
+
+void ScriptController::evaluateInIsolatedWorld(unsigned worldID, const Vector<ScriptSourceCode>& sources, int extensionGroup)
+{
+ m_proxy->evaluateInIsolatedWorld(worldID, sources, extensionGroup);
+}
+
+// Evaluate a script file in the environment of this proxy.
+ScriptValue ScriptController::evaluate(const ScriptSourceCode& sourceCode, ShouldAllowXSS shouldAllowXSS)
+{
+ String sourceURL = sourceCode.url();
+ const String* savedSourceURL = m_sourceURL;
+ m_sourceURL = &sourceURL;
+
+ if (shouldAllowXSS == DoNotAllowXSS && !m_XSSAuditor->canEvaluate(sourceCode.source())) {
+ // This script is not safe to be evaluated.
+ return ScriptValue();
+ }
+
+ v8::HandleScope handleScope;
+ v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_proxy->frame());
+ if (v8Context.IsEmpty())
+ return ScriptValue();
+
+ v8::Context::Scope scope(v8Context);
+
+ RefPtr<Frame> protect(m_frame);
+
+ v8::Local<v8::Value> object = m_proxy->evaluate(sourceCode, 0);
+
+ // Evaluating the JavaScript could cause the frame to be deallocated
+ // so we start the keep alive timer here.
+ m_frame->keepAlive();
+
+ m_sourceURL = savedSourceURL;
+
+ if (object.IsEmpty() || object->IsUndefined())
+ return ScriptValue();
+
+ return ScriptValue(object);
+}
+
+int ScriptController::eventHandlerLineNumber() const
+{
+ ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
+ if (parser)
+ return parser->lineNumber();
+ return 0;
+}
+
+int ScriptController::eventHandlerColumnNumber() const
+{
+ ScriptableDocumentParser* parser = m_frame->document()->scriptableDocumentParser();
+ if (parser)
+ return parser->columnNumber();
+ return 0;
+}
+
+void ScriptController::finishedWithEvent(Event* event)
+{
+ m_proxy->finishedWithEvent(event);
+}
+
+// Create a V8 object with an interceptor of NPObjectPropertyGetter.
+void ScriptController::bindToWindowObject(Frame* frame, const String& key, NPObject* object)
+{
+ v8::HandleScope handleScope;
+
+ v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
+ if (v8Context.IsEmpty())
+ return;
+
+ v8::Context::Scope scope(v8Context);
+
+ v8::Handle<v8::Object> value = createV8ObjectForNPObject(object, 0);
+
+ // Attach to the global object.
+ v8::Handle<v8::Object> global = v8Context->Global();
+ global->Set(v8String(key), value);
+}
+
+void ScriptController::collectGarbage()
+{
+ v8::HandleScope handleScope;
+
+ v8::Persistent<v8::Context> v8Context = v8::Context::New();
+ if (v8Context.IsEmpty())
+ return;
+ {
+ v8::Context::Scope scope(v8Context);
+ v8::Local<v8::String> source = v8::String::New("if (gc) gc();");
+ v8::Local<v8::String> name = v8::String::New("gc");
+ v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
+ if (!script.IsEmpty())
+ script->Run();
+ }
+ v8Context.Dispose();
+}
+
+void ScriptController::lowMemoryNotification()
+{
+ v8::V8::LowMemoryNotification();
+}
+
+bool ScriptController::haveInterpreter() const
+{
+ return m_proxy->windowShell()->isContextInitialized();
+}
+
+PassScriptInstance ScriptController::createScriptInstanceForWidget(Widget* widget)
+{
+ ASSERT(widget);
+
+ if (widget->isFrameView())
+ return 0;
+
+ NPObject* npObject = PlatformBridge::pluginScriptableObject(widget);
+
+ if (!npObject)
+ return 0;
+
+ // Frame Memory Management for NPObjects
+ // -------------------------------------
+ // NPObjects are treated differently than other objects wrapped by JS.
+ // NPObjects can be created either by the browser (e.g. the main
+ // window object) or by the plugin (the main plugin object
+ // for a HTMLEmbedElement). Further, unlike most DOM Objects, the frame
+ // is especially careful to ensure NPObjects terminate at frame teardown because
+ // if a plugin leaks a reference, it could leak its objects (or the browser's objects).
+ //
+ // The Frame maintains a list of plugin objects (m_pluginObjects)
+ // which it can use to quickly find the wrapped embed object.
+ //
+ // Inside the NPRuntime, we've added a few methods for registering
+ // wrapped NPObjects. The purpose of the registration is because
+ // javascript garbage collection is non-deterministic, yet we need to
+ // be able to tear down the plugin objects immediately. When an object
+ // is registered, javascript can use it. When the object is destroyed,
+ // or when the object's "owning" object is destroyed, the object will
+ // be un-registered, and the javascript engine must not use it.
+ //
+ // Inside the javascript engine, the engine can keep a reference to the
+ // NPObject as part of its wrapper. However, before accessing the object
+ // it must consult the _NPN_Registry.
+
+ v8::Local<v8::Object> wrapper = createV8ObjectForNPObject(npObject, 0);
+
+ // Track the plugin object. We've been given a reference to the object.
+ m_pluginObjects.set(widget, npObject);
+
+ return V8ScriptInstance::create(wrapper);
+}
+
+void ScriptController::cleanupScriptObjectsForPlugin(Widget* nativeHandle)
+{
+ PluginObjectMap::iterator it = m_pluginObjects.find(nativeHandle);
+ if (it == m_pluginObjects.end())
+ return;
+ _NPN_UnregisterObject(it->second);
+ _NPN_ReleaseObject(it->second);
+ m_pluginObjects.remove(it);
+}
+
+void ScriptController::getAllWorlds(Vector<DOMWrapperWorld*>& worlds)
+{
+ worlds.append(mainThreadNormalWorld());
+}
+
+void ScriptController::evaluateInWorld(const ScriptSourceCode& source,
+ DOMWrapperWorld* world)
+{
+ Vector<ScriptSourceCode> sources;
+ sources.append(source);
+ // FIXME: Get an ID from the world param.
+ evaluateInIsolatedWorld(0, sources);
+}
+
+static NPObject* createNoScriptObject()
+{
+ notImplemented();
+ return 0;
+}
+
+static NPObject* createScriptObject(Frame* frame)
+{
+ v8::HandleScope handleScope;
+ v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(frame);
+ if (v8Context.IsEmpty())
+ return createNoScriptObject();
+
+ v8::Context::Scope scope(v8Context);
+ DOMWindow* window = frame->domWindow();
+ v8::Handle<v8::Value> global = toV8(window);
+ ASSERT(global->IsObject());
+ return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(global), window);
+}
+
+NPObject* ScriptController::windowScriptNPObject()
+{
+ if (m_windowScriptNPObject)
+ return m_windowScriptNPObject;
+
+ if (canExecuteScripts(NotAboutToExecuteScript)) {
+ // JavaScript is enabled, so there is a JavaScript window object.
+ // Return an NPObject bound to the window object.
+ m_windowScriptNPObject = createScriptObject(m_frame);
+ _NPN_RegisterObject(m_windowScriptNPObject, 0);
+ } 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 = createNoScriptObject();
+ }
+ return m_windowScriptNPObject;
+}
+
+NPObject* ScriptController::createScriptObjectForPluginElement(HTMLPlugInElement* plugin)
+{
+ // Can't create NPObjects when JavaScript is disabled.
+ if (!canExecuteScripts(NotAboutToExecuteScript))
+ return createNoScriptObject();
+
+ v8::HandleScope handleScope;
+ v8::Handle<v8::Context> v8Context = V8Proxy::mainWorldContext(m_frame);
+ if (v8Context.IsEmpty())
+ return createNoScriptObject();
+ v8::Context::Scope scope(v8Context);
+
+ DOMWindow* window = m_frame->domWindow();
+ v8::Handle<v8::Value> v8plugin = toV8(static_cast<HTMLEmbedElement*>(plugin));
+ if (!v8plugin->IsObject())
+ return createNoScriptObject();
+
+ return npCreateV8ScriptObject(0, v8::Handle<v8::Object>::Cast(v8plugin), window);
+}
+
+
+void ScriptController::clearWindowShell(bool)
+{
+ // V8 binding expects ScriptController::clearWindowShell only be called
+ // when a frame is loading a new page. V8Proxy::clearForNavigation
+ // creates a new context for the new page.
+ m_proxy->clearForNavigation();
+}
+
+#if ENABLE(INSPECTOR)
+void ScriptController::setCaptureCallStackForUncaughtExceptions(bool)
+{
+ v8::V8::SetCaptureStackTraceForUncaughtExceptions(true, ScriptCallStack::maxCallStackSizeToCapture);
+}
+#endif
+
+void ScriptController::attachDebugger(void*)
+{
+ notImplemented();
+}
+
+void ScriptController::updateDocument()
+{
+ m_proxy->windowShell()->updateDocument();
+}
+
+void ScriptController::namedItemAdded(HTMLDocument* doc, const AtomicString& name)
+{
+}
+
+void ScriptController::namedItemRemoved(HTMLDocument* doc, const AtomicString& name)
+{
+}
+
+} // namespace WebCore