diff options
author | Steve Block <steveblock@google.com> | 2011-05-06 11:45:16 +0100 |
---|---|---|
committer | Steve Block <steveblock@google.com> | 2011-05-12 13:44:10 +0100 |
commit | cad810f21b803229eb11403f9209855525a25d57 (patch) | |
tree | 29a6fd0279be608e0fe9ffe9841f722f0f4e4269 /Source/WebCore/bindings/v8/V8DOMWindowShell.cpp | |
parent | 121b0cf4517156d0ac5111caf9830c51b69bae8f (diff) | |
download | external_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/v8/V8DOMWindowShell.cpp')
-rw-r--r-- | Source/WebCore/bindings/v8/V8DOMWindowShell.cpp | 609 |
1 files changed, 609 insertions, 0 deletions
diff --git a/Source/WebCore/bindings/v8/V8DOMWindowShell.cpp b/Source/WebCore/bindings/v8/V8DOMWindowShell.cpp new file mode 100644 index 0000000..9918577 --- /dev/null +++ b/Source/WebCore/bindings/v8/V8DOMWindowShell.cpp @@ -0,0 +1,609 @@ +/* + * Copyright (C) 2008, 2009 Google 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 "V8DOMWindowShell.h" + +#include "PlatformBridge.h" +#include "CSSMutableStyleDeclaration.h" +#include "DateExtension.h" +#include "DocumentLoader.h" +#include "Frame.h" +#include "FrameLoaderClient.h" +#include "Page.h" +#include "PageGroup.h" +#include "ScriptController.h" +#include "StorageNamespace.h" +#include "V8Binding.h" +#include "V8BindingState.h" +#include "V8Collection.h" +#include "V8ConsoleMessage.h" +#include "V8DOMMap.h" +#include "V8DOMWindow.h" +#include "V8Document.h" +#include "V8GCForContextDispose.h" +#include "V8HTMLDocument.h" +#include "V8HiddenPropertyName.h" +#include "V8History.h" +#include "V8Location.h" +#include "V8Proxy.h" +#include "WorkerContextExecutionProxy.h" + +#include <algorithm> +#include <stdio.h> +#include <utility> +#include <v8-debug.h> +#include <v8.h> +#include <wtf/Assertions.h> +#include <wtf/OwnArrayPtr.h> +#include <wtf/StdLibExtras.h> +#include <wtf/StringExtras.h> +#include <wtf/UnusedParam.h> +#include <wtf/text/CString.h> + +#ifdef ANDROID_INSTRUMENT +#include "TimeCounter.h" +#endif + +namespace WebCore { + +static void handleFatalErrorInV8() +{ + // FIXME: We temporarily deal with V8 internal error situations + // such as out-of-memory by crashing the renderer. + CRASH(); +} + +static void reportFatalErrorInV8(const char* location, const char* message) +{ + // V8 is shutdown, we cannot use V8 api. + // The only thing we can do is to disable JavaScript. + // FIXME: clean up V8Proxy and disable JavaScript. + int memoryUsageMB = -1; +#if PLATFORM(CHROMIUM) + memoryUsageMB = ChromiumBridge::actualMemoryUsageMB(); +#endif + printf("V8 error: %s (%s). Current memory usage: %d MB\n", message, location, memoryUsageMB); + handleFatalErrorInV8(); +} + +// Returns the owner frame pointer of a DOM wrapper object. It only works for +// these DOM objects requiring cross-domain access check. +static Frame* getTargetFrame(v8::Local<v8::Object> host, v8::Local<v8::Value> data) +{ + Frame* target = 0; + WrapperTypeInfo* type = WrapperTypeInfo::unwrap(data); + if (V8DOMWindow::info.equals(type)) { + v8::Handle<v8::Object> window = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), host); + if (window.IsEmpty()) + return target; + + DOMWindow* targetWindow = V8DOMWindow::toNative(window); + target = targetWindow->frame(); + } else if (V8History::info.equals(type)) { + History* history = V8History::toNative(host); + target = history->frame(); + } else if (V8Location::info.equals(type)) { + Location* location = V8Location::toNative(host); + target = location->frame(); + } + return target; +} + +static void reportUnsafeJavaScriptAccess(v8::Local<v8::Object> host, v8::AccessType type, v8::Local<v8::Value> data) +{ + Frame* target = getTargetFrame(host, data); + if (target) + V8Proxy::reportUnsafeAccessTo(target, V8Proxy::ReportLater); +} + +PassRefPtr<V8DOMWindowShell> V8DOMWindowShell::create(Frame* frame) +{ + return adoptRef(new V8DOMWindowShell(frame)); +} + +V8DOMWindowShell::V8DOMWindowShell(Frame* frame) + : m_frame(frame) +{ +} + +bool V8DOMWindowShell::isContextInitialized() +{ + // m_context, m_global, and m_wrapperBoilerplates should + // all be non-empty if if m_context is non-empty. + ASSERT(m_context.IsEmpty() || !m_global.IsEmpty()); + return !m_context.IsEmpty(); +} + +void V8DOMWindowShell::disposeContextHandles() +{ + if (!m_context.IsEmpty()) { + m_frame->loader()->client()->didDestroyScriptContextForFrame(); + m_context.Dispose(); + m_context.Clear(); + + // It's likely that disposing the context has created a lot of + // garbage. Notify V8 about this so it'll have a chance of cleaning + // it up when idle. + V8GCForContextDispose::instance().notifyContextDisposed(); + } + + WrapperBoilerplateMap::iterator it = m_wrapperBoilerplates.begin(); + for (; it != m_wrapperBoilerplates.end(); ++it) { + v8::Persistent<v8::Object> wrapper = it->second; + wrapper.Dispose(); + wrapper.Clear(); + } + m_wrapperBoilerplates.clear(); +} + +void V8DOMWindowShell::destroyGlobal() +{ + if (!m_global.IsEmpty()) { +#ifndef NDEBUG + V8GCController::unregisterGlobalHandle(this, m_global); +#endif + m_global.Dispose(); + m_global.Clear(); + } +} + +void V8DOMWindowShell::clearForClose() +{ + if (!m_context.IsEmpty()) { + v8::HandleScope handleScope; + + clearDocumentWrapper(); + disposeContextHandles(); + } +} + +void V8DOMWindowShell::clearForNavigation() +{ + if (!m_context.IsEmpty()) { + v8::HandleScope handle; + clearDocumentWrapper(); + + v8::Context::Scope contextScope(m_context); + + // Clear the document wrapper cache before turning on access checks on + // the old DOMWindow wrapper. This way, access to the document wrapper + // will be protected by the security checks on the DOMWindow wrapper. + clearDocumentWrapperCache(); + + // Turn on access check on the old DOMWindow wrapper. + v8::Handle<v8::Object> wrapper = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), m_global); + ASSERT(!wrapper.IsEmpty()); + wrapper->TurnOnAccessCheck(); + + // Separate the context from its global object. + m_context->DetachGlobal(); + + disposeContextHandles(); + } +} + +// Create a new environment and setup the global object. +// +// The global object corresponds to a DOMWindow instance. However, to +// allow properties of the JS DOMWindow instance to be shadowed, we +// use a shadow object as the global object and use the JS DOMWindow +// instance as the prototype for that shadow object. The JS DOMWindow +// instance is undetectable from javascript code because the __proto__ +// accessors skip that object. +// +// The shadow object and the DOMWindow instance are seen as one object +// from javascript. The javascript object that corresponds to a +// DOMWindow instance is the shadow object. When mapping a DOMWindow +// instance to a V8 object, we return the shadow object. +// +// To implement split-window, see +// 1) https://bugs.webkit.org/show_bug.cgi?id=17249 +// 2) https://wiki.mozilla.org/Gecko:SplitWindow +// 3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639 +// we need to split the shadow object further into two objects: +// an outer window and an inner window. The inner window is the hidden +// prototype of the outer window. The inner window is the default +// global object of the context. A variable declared in the global +// scope is a property of the inner window. +// +// The outer window sticks to a Frame, it is exposed to JavaScript +// via window.window, window.self, window.parent, etc. The outer window +// has a security token which is the domain. The outer window cannot +// have its own properties. window.foo = 'x' is delegated to the +// inner window. +// +// When a frame navigates to a new page, the inner window is cut off +// the outer window, and the outer window identify is preserved for +// the frame. However, a new inner window is created for the new page. +// If there are JS code holds a closure to the old inner window, +// it won't be able to reach the outer window via its global object. +bool V8DOMWindowShell::initContextIfNeeded() +{ + // Bail out if the context has already been initialized. + if (!m_context.IsEmpty()) + return false; + +#ifdef ANDROID_INSTRUMENT + android::TimeCounter::start(android::TimeCounter::JavaScriptInitTimeCounter); +#endif + + // Create a handle scope for all local handles. + v8::HandleScope handleScope; + + // Setup the security handlers and message listener. This only has + // to be done once. + static bool isV8Initialized = false; + if (!isV8Initialized) { + // Tells V8 not to call the default OOM handler, binding code + // will handle it. + v8::V8::IgnoreOutOfMemoryException(); + v8::V8::SetFatalErrorHandler(reportFatalErrorInV8); + + v8::V8::SetGlobalGCPrologueCallback(&V8GCController::gcPrologue); + v8::V8::SetGlobalGCEpilogueCallback(&V8GCController::gcEpilogue); + + v8::V8::AddMessageListener(&V8ConsoleMessage::handler); + + v8::V8::SetFailedAccessCheckCallbackFunction(reportUnsafeJavaScriptAccess); + + isV8Initialized = true; + } + + + m_context = createNewContext(m_global, 0); + if (m_context.IsEmpty()) + return false; + + v8::Local<v8::Context> v8Context = v8::Local<v8::Context>::New(m_context); + v8::Context::Scope contextScope(v8Context); + + // Store the first global object created so we can reuse it. + if (m_global.IsEmpty()) { + m_global = v8::Persistent<v8::Object>::New(v8Context->Global()); + // Bail out if allocation of the first global objects fails. + if (m_global.IsEmpty()) { + disposeContextHandles(); + return false; + } +#ifndef NDEBUG + V8GCController::registerGlobalHandle(PROXY, this, m_global); +#endif + } + + if (!installHiddenObjectPrototype(v8Context)) { + disposeContextHandles(); + return false; + } + + if (!installDOMWindow(v8Context, m_frame->domWindow())) { + disposeContextHandles(); + return false; + } + + updateDocument(); + + setSecurityToken(); + + m_frame->loader()->client()->didCreateScriptContextForFrame(); + + // FIXME: This is wrong. We should actually do this for the proper world once + // we do isolated worlds the WebCore way. + m_frame->loader()->dispatchDidClearWindowObjectInWorld(0); + +#ifdef ANDROID_INSTRUMENT + android::TimeCounter::record(android::TimeCounter::JavaScriptInitTimeCounter, __FUNCTION__); +#endif + + return true; +} + +v8::Persistent<v8::Context> V8DOMWindowShell::createNewContext(v8::Handle<v8::Object> global, int extensionGroup) +{ + v8::Persistent<v8::Context> result; + + // The activeDocumentLoader pointer could be 0 during frame shutdown. + if (!m_frame->loader()->activeDocumentLoader()) + return result; + + // Create a new environment using an empty template for the shadow + // object. Reuse the global object if one has been created earlier. + v8::Persistent<v8::ObjectTemplate> globalTemplate = V8DOMWindow::GetShadowObjectTemplate(); + if (globalTemplate.IsEmpty()) + return result; + + // Used to avoid sleep calls in unload handlers. + if (!V8Proxy::registeredExtensionWithV8(DateExtension::get())) + V8Proxy::registerExtension(DateExtension::get()); + + // Dynamically tell v8 about our extensions now. + const V8Extensions& extensions = V8Proxy::extensions(); + OwnArrayPtr<const char*> extensionNames(new const char*[extensions.size()]); + int index = 0; + for (size_t i = 0; i < extensions.size(); ++i) { + // Ensure our date extension is always allowed. + if (extensions[i] != DateExtension::get() + && !m_frame->loader()->client()->allowScriptExtension(extensions[i]->name(), extensionGroup)) + continue; + + extensionNames[index++] = extensions[i]->name(); + } + v8::ExtensionConfiguration extensionConfiguration(index, extensionNames.get()); + result = v8::Context::New(&extensionConfiguration, globalTemplate, global); + + return result; +} + +void V8DOMWindowShell::setContext(v8::Handle<v8::Context> context) +{ + // if we already have a context, clear it before setting the new one. + if (!m_context.IsEmpty()) { + m_context.Dispose(); + m_context.Clear(); + } + m_context = v8::Persistent<v8::Context>::New(context); +} + +bool V8DOMWindowShell::installDOMWindow(v8::Handle<v8::Context> context, DOMWindow* window) +{ + // Create a new JS window object and use it as the prototype for the shadow global object. + v8::Handle<v8::Function> windowConstructor = V8DOMWrapper::getConstructor(&V8DOMWindow::info, getHiddenObjectPrototype(context)); + v8::Local<v8::Object> jsWindow = SafeAllocation::newInstance(windowConstructor); + // Bail out if allocation failed. + if (jsWindow.IsEmpty()) + return false; + + // Wrap the window. + V8DOMWrapper::setDOMWrapper(jsWindow, &V8DOMWindow::info, window); + V8DOMWrapper::setDOMWrapper(v8::Handle<v8::Object>::Cast(jsWindow->GetPrototype()), &V8DOMWindow::info, window); + + window->ref(); + V8DOMWrapper::setJSWrapperForDOMObject(window, v8::Persistent<v8::Object>::New(jsWindow)); + + // Insert the window instance as the prototype of the shadow object. + v8::Handle<v8::Object> v8RealGlobal = v8::Handle<v8::Object>::Cast(context->Global()->GetPrototype()); + V8DOMWrapper::setDOMWrapper(v8RealGlobal, &V8DOMWindow::info, window); + v8RealGlobal->SetPrototype(jsWindow); + return true; +} + +void V8DOMWindowShell::updateDocumentWrapper(v8::Handle<v8::Object> wrapper) +{ + clearDocumentWrapper(); + + ASSERT(m_document.IsEmpty()); + m_document = v8::Persistent<v8::Object>::New(wrapper); +#ifndef NDEBUG + V8GCController::registerGlobalHandle(PROXY, this, m_document); +#endif +} + +void V8DOMWindowShell::clearDocumentWrapper() +{ + if (!m_document.IsEmpty()) { +#ifndef NDEBUG + V8GCController::unregisterGlobalHandle(this, m_document); +#endif + m_document.Dispose(); + m_document.Clear(); + } +} + +static void checkDocumentWrapper(v8::Handle<v8::Object> wrapper, Document* document) +{ + ASSERT(V8Document::toNative(wrapper) == document); + ASSERT(!document->isHTMLDocument() || (V8Document::toNative(v8::Handle<v8::Object>::Cast(wrapper->GetPrototype())) == document)); +} + +void V8DOMWindowShell::updateDocumentWrapperCache() +{ + v8::HandleScope handleScope; + v8::Context::Scope contextScope(m_context); + + // If the document has no frame, NodeToV8Object might get the + // document wrapper for a document that is about to be deleted. + // If the ForceSet below causes a garbage collection, the document + // might get deleted and the global handle for the document + // wrapper cleared. Using the cleared global handle will lead to + // crashes. In this case we clear the cache and let the DOMWindow + // accessor handle access to the document. + if (!m_frame->document()->frame()) { + clearDocumentWrapperCache(); + return; + } + + v8::Handle<v8::Value> documentWrapper = toV8(m_frame->document()); + ASSERT(documentWrapper == m_document || m_document.IsEmpty()); + if (m_document.IsEmpty()) + updateDocumentWrapper(v8::Handle<v8::Object>::Cast(documentWrapper)); + checkDocumentWrapper(m_document, m_frame->document()); + + // If instantiation of the document wrapper fails, clear the cache + // and let the DOMWindow accessor handle access to the document. + if (documentWrapper.IsEmpty()) { + clearDocumentWrapperCache(); + return; + } + ASSERT(documentWrapper->IsObject()); + m_context->Global()->ForceSet(v8::String::New("document"), documentWrapper, static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete)); +} + +void V8DOMWindowShell::clearDocumentWrapperCache() +{ + ASSERT(!m_context.IsEmpty()); + m_context->Global()->ForceDelete(v8::String::New("document")); +} + +void V8DOMWindowShell::setSecurityToken() +{ + Document* document = m_frame->document(); + // Setup security origin and security token. + if (!document) { + m_context->UseDefaultSecurityToken(); + return; + } + + // Ask the document's SecurityOrigin to generate a security token. + // If two tokens are equal, then the SecurityOrigins canAccess each other. + // If two tokens are not equal, then we have to call canAccess. + // Note: we can't use the HTTPOrigin if it was set from the DOM. + SecurityOrigin* origin = document->securityOrigin(); + String token; + if (!origin->domainWasSetInDOM()) + token = document->securityOrigin()->toString(); + + // An empty or "null" token means we always have to call + // canAccess. The toString method on securityOrigins returns the + // string "null" for empty security origins and for security + // origins that should only allow access to themselves. In this + // case, we use the global object as the security token to avoid + // calling canAccess when a script accesses its own objects. + if (token.isEmpty() || token == "null") { + m_context->UseDefaultSecurityToken(); + return; + } + + CString utf8Token = token.utf8(); + // NOTE: V8 does identity comparison in fast path, must use a symbol + // as the security token. + m_context->SetSecurityToken(v8::String::NewSymbol(utf8Token.data(), utf8Token.length())); +} + +void V8DOMWindowShell::updateDocument() +{ + if (!m_frame->document()) + return; + + if (m_global.IsEmpty()) + return; + + // There is an existing JavaScript wrapper for the global object + // of this frame. JavaScript code in other frames might hold a + // reference to this wrapper. We eagerly initialize the JavaScript + // context for the new document to make property access on the + // global object wrapper succeed. + initContextIfNeeded(); + + // Bail out if context initialization failed. + if (m_context.IsEmpty()) + return; + + // We have a new document and we need to update the cache. + updateDocumentWrapperCache(); + + updateSecurityOrigin(); +} + +v8::Handle<v8::Value> getter(v8::Local<v8::String> property, const v8::AccessorInfo& info) +{ + // FIXME(antonm): consider passing AtomicStringImpl directly. + AtomicString name = v8StringToAtomicWebCoreString(property); + HTMLDocument* htmlDocument = V8HTMLDocument::toNative(info.Holder()); + ASSERT(htmlDocument); + v8::Handle<v8::Value> result = V8HTMLDocument::GetNamedProperty(htmlDocument, name); + if (!result.IsEmpty()) + return result; + v8::Handle<v8::Value> prototype = info.Holder()->GetPrototype(); + if (prototype->IsObject()) + return prototype.As<v8::Object>()->Get(property); + return v8::Undefined(); +} + +void V8DOMWindowShell::namedItemAdded(HTMLDocument* doc, const AtomicString& name) +{ + initContextIfNeeded(); + + v8::HandleScope handleScope; + v8::Context::Scope contextScope(m_context); + + ASSERT(!m_document.IsEmpty()); + checkDocumentWrapper(m_document, doc); + m_document->SetAccessor(v8String(name), getter); +} + +void V8DOMWindowShell::namedItemRemoved(HTMLDocument* doc, const AtomicString& name) +{ +} + +void V8DOMWindowShell::updateSecurityOrigin() +{ + v8::HandleScope scope; + setSecurityToken(); +} + +v8::Handle<v8::Value> V8DOMWindowShell::getHiddenObjectPrototype(v8::Handle<v8::Context> context) +{ + return context->Global()->GetHiddenValue(V8HiddenPropertyName::objectPrototype()); +} + +bool V8DOMWindowShell::installHiddenObjectPrototype(v8::Handle<v8::Context> context) +{ + v8::Handle<v8::String> objectString = v8::String::New("Object"); + v8::Handle<v8::String> prototypeString = v8::String::New("prototype"); + v8::Handle<v8::String> hiddenObjectPrototypeString = V8HiddenPropertyName::objectPrototype(); + // Bail out if allocation failed. + if (objectString.IsEmpty() || prototypeString.IsEmpty() || hiddenObjectPrototypeString.IsEmpty()) + return false; + + v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(context->Global()->Get(objectString)); + // Bail out if fetching failed. + if (object.IsEmpty()) + return false; + v8::Handle<v8::Value> objectPrototype = object->Get(prototypeString); + // Bail out if fetching failed. + if (objectPrototype.IsEmpty()) + return false; + + context->Global()->SetHiddenValue(hiddenObjectPrototypeString, objectPrototype); + + return true; +} + +v8::Local<v8::Object> V8DOMWindowShell::createWrapperFromCacheSlowCase(WrapperTypeInfo* type) +{ + // Not in cache. + initContextIfNeeded(); + v8::Context::Scope scope(m_context); + v8::Local<v8::Function> function = V8DOMWrapper::getConstructor(type, getHiddenObjectPrototype(m_context)); + v8::Local<v8::Object> instance = SafeAllocation::newInstance(function); + if (!instance.IsEmpty()) { + m_wrapperBoilerplates.set(type, v8::Persistent<v8::Object>::New(instance)); + return instance->Clone(); + } + return notHandledByInterceptor(); +} + +void V8DOMWindowShell::setLocation(DOMWindow* window, const String& locationString) +{ + State<V8Binding>* state = V8BindingState::Only(); + window->setLocation(locationString, state->activeWindow(), state->firstWindow()); +} + +} // WebCore |