Home | History | Annotate | Download | only in v8
      1 /*
      2  * Copyright (C) 2008, 2009, 2011 Google Inc. All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions are
      6  * met:
      7  *
      8  *     * Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *     * Redistributions in binary form must reproduce the above
     11  * copyright notice, this list of conditions and the following disclaimer
     12  * in the documentation and/or other materials provided with the
     13  * distribution.
     14  *     * Neither the name of Google Inc. nor the names of its
     15  * contributors may be used to endorse or promote products derived from
     16  * this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
     22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29  */
     30 
     31 #include "config.h"
     32 #include "bindings/v8/V8WindowShell.h"
     33 
     34 #include "bindings/core/v8/V8Document.h"
     35 #include "bindings/core/v8/V8HTMLCollection.h"
     36 #include "bindings/core/v8/V8HTMLDocument.h"
     37 #include "bindings/core/v8/V8Window.h"
     38 #include "bindings/v8/DOMWrapperWorld.h"
     39 #include "bindings/v8/ScriptController.h"
     40 #include "bindings/v8/V8Binding.h"
     41 #include "bindings/v8/V8DOMActivityLogger.h"
     42 #include "bindings/v8/V8GCForContextDispose.h"
     43 #include "bindings/v8/V8HiddenValue.h"
     44 #include "bindings/v8/V8Initializer.h"
     45 #include "bindings/v8/V8ObjectConstructor.h"
     46 #include "core/dom/ScriptForbiddenScope.h"
     47 #include "core/frame/LocalFrame.h"
     48 #include "core/frame/csp/ContentSecurityPolicy.h"
     49 #include "core/html/HTMLCollection.h"
     50 #include "core/html/HTMLIFrameElement.h"
     51 #include "core/inspector/InspectorInstrumentation.h"
     52 #include "core/loader/DocumentLoader.h"
     53 #include "core/loader/FrameLoader.h"
     54 #include "core/loader/FrameLoaderClient.h"
     55 #include "platform/RuntimeEnabledFeatures.h"
     56 #include "platform/TraceEvent.h"
     57 #include "platform/heap/Handle.h"
     58 #include "platform/weborigin/SecurityOrigin.h"
     59 #include "public/platform/Platform.h"
     60 #include "wtf/Assertions.h"
     61 #include "wtf/OwnPtr.h"
     62 #include "wtf/StringExtras.h"
     63 #include "wtf/text/CString.h"
     64 #include <algorithm>
     65 #include <utility>
     66 #include <v8-debug.h>
     67 #include <v8.h>
     68 
     69 namespace WebCore {
     70 
     71 static void checkDocumentWrapper(v8::Handle<v8::Object> wrapper, Document* document)
     72 {
     73     ASSERT(V8Document::toNative(wrapper) == document);
     74     ASSERT(!document->isHTMLDocument() || (V8Document::toNative(v8::Handle<v8::Object>::Cast(wrapper->GetPrototype())) == document));
     75 }
     76 
     77 static void setInjectedScriptContextDebugId(v8::Handle<v8::Context> targetContext, int debugId)
     78 {
     79     V8PerContextDebugData::setContextDebugData(targetContext, "injected", debugId);
     80 }
     81 
     82 PassOwnPtr<V8WindowShell> V8WindowShell::create(LocalFrame* frame, DOMWrapperWorld& world, v8::Isolate* isolate)
     83 {
     84     return adoptPtr(new V8WindowShell(frame, &world, isolate));
     85 }
     86 
     87 V8WindowShell::V8WindowShell(LocalFrame* frame, PassRefPtr<DOMWrapperWorld> world, v8::Isolate* isolate)
     88     : m_frame(frame)
     89     , m_isolate(isolate)
     90     , m_world(world)
     91 {
     92 }
     93 
     94 void V8WindowShell::disposeContext(GlobalDetachmentBehavior behavior)
     95 {
     96     if (!isContextInitialized())
     97         return;
     98 
     99     v8::HandleScope handleScope(m_isolate);
    100     v8::Handle<v8::Context> context = m_scriptState->context();
    101     m_frame->loader().client()->willReleaseScriptContext(context, m_world->worldId());
    102 
    103     if (behavior == DetachGlobal)
    104         context->DetachGlobal();
    105 
    106     m_scriptState->disposePerContextData();
    107 
    108     // It's likely that disposing the context has created a lot of
    109     // garbage. Notify V8 about this so it'll have a chance of cleaning
    110     // it up when idle.
    111     V8GCForContextDispose::instanceTemplate().notifyContextDisposed(m_frame->isMainFrame());
    112 }
    113 
    114 void V8WindowShell::clearForClose()
    115 {
    116     if (!isContextInitialized())
    117         return;
    118 
    119     m_document.clear();
    120     disposeContext(DoNotDetachGlobal);
    121 }
    122 
    123 void V8WindowShell::clearForNavigation()
    124 {
    125     if (!isContextInitialized())
    126         return;
    127 
    128     ScriptState::Scope scope(m_scriptState.get());
    129 
    130     m_document.clear();
    131 
    132     // Clear the document wrapper cache before turning on access checks on
    133     // the old LocalDOMWindow wrapper. This way, access to the document wrapper
    134     // will be protected by the security checks on the LocalDOMWindow wrapper.
    135     clearDocumentProperty();
    136 
    137     v8::Handle<v8::Object> windowWrapper = V8Window::findInstanceInPrototypeChain(m_global.newLocal(m_isolate), m_isolate);
    138     ASSERT(!windowWrapper.IsEmpty());
    139     windowWrapper->TurnOnAccessCheck();
    140     disposeContext(DetachGlobal);
    141 }
    142 
    143 // Create a new environment and setup the global object.
    144 //
    145 // The global object corresponds to a LocalDOMWindow instance. However, to
    146 // allow properties of the JS LocalDOMWindow instance to be shadowed, we
    147 // use a shadow object as the global object and use the JS LocalDOMWindow
    148 // instance as the prototype for that shadow object. The JS LocalDOMWindow
    149 // instance is undetectable from JavaScript code because the __proto__
    150 // accessors skip that object.
    151 //
    152 // The shadow object and the LocalDOMWindow instance are seen as one object
    153 // from JavaScript. The JavaScript object that corresponds to a
    154 // LocalDOMWindow instance is the shadow object. When mapping a LocalDOMWindow
    155 // instance to a V8 object, we return the shadow object.
    156 //
    157 // To implement split-window, see
    158 //   1) https://bugs.webkit.org/show_bug.cgi?id=17249
    159 //   2) https://wiki.mozilla.org/Gecko:SplitWindow
    160 //   3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639
    161 // we need to split the shadow object further into two objects:
    162 // an outer window and an inner window. The inner window is the hidden
    163 // prototype of the outer window. The inner window is the default
    164 // global object of the context. A variable declared in the global
    165 // scope is a property of the inner window.
    166 //
    167 // The outer window sticks to a LocalFrame, it is exposed to JavaScript
    168 // via window.window, window.self, window.parent, etc. The outer window
    169 // has a security token which is the domain. The outer window cannot
    170 // have its own properties. window.foo = 'x' is delegated to the
    171 // inner window.
    172 //
    173 // When a frame navigates to a new page, the inner window is cut off
    174 // the outer window, and the outer window identify is preserved for
    175 // the frame. However, a new inner window is created for the new page.
    176 // If there are JS code holds a closure to the old inner window,
    177 // it won't be able to reach the outer window via its global object.
    178 bool V8WindowShell::initializeIfNeeded()
    179 {
    180     if (isContextInitialized())
    181         return true;
    182 
    183     DOMWrapperWorld::setWorldOfInitializingWindow(m_world.get());
    184     bool result = initialize();
    185     DOMWrapperWorld::setWorldOfInitializingWindow(0);
    186     return result;
    187 }
    188 
    189 bool V8WindowShell::initialize()
    190 {
    191     TRACE_EVENT0("v8", "V8WindowShell::initialize");
    192     TRACE_EVENT_SCOPED_SAMPLING_STATE("Blink", "InitializeWindow");
    193 
    194     ScriptForbiddenScope::AllowUserAgentScript allowScript;
    195 
    196     v8::HandleScope handleScope(m_isolate);
    197 
    198     createContext();
    199 
    200     if (!isContextInitialized())
    201         return false;
    202 
    203     ScriptState::Scope scope(m_scriptState.get());
    204     v8::Handle<v8::Context> context = m_scriptState->context();
    205     if (m_global.isEmpty()) {
    206         m_global.set(m_isolate, context->Global());
    207         if (m_global.isEmpty()) {
    208             disposeContext(DoNotDetachGlobal);
    209             return false;
    210         }
    211     }
    212 
    213     if (!m_world->isMainWorld()) {
    214         V8WindowShell* mainWindow = m_frame->script().existingWindowShell(DOMWrapperWorld::mainWorld());
    215         if (mainWindow && !mainWindow->context().IsEmpty())
    216             setInjectedScriptContextDebugId(context, m_frame->script().contextDebugId(mainWindow->context()));
    217     }
    218 
    219     if (!installDOMWindow()) {
    220         disposeContext(DoNotDetachGlobal);
    221         return false;
    222     }
    223 
    224     if (m_world->isMainWorld()) {
    225         // ActivityLogger for main world is updated within updateDocument().
    226         updateDocument();
    227         if (m_frame->document()) {
    228             setSecurityToken(m_frame->document()->securityOrigin());
    229             ContentSecurityPolicy* csp = m_frame->document()->contentSecurityPolicy();
    230             context->AllowCodeGenerationFromStrings(csp->allowEval(0, ContentSecurityPolicy::SuppressReport));
    231             context->SetErrorMessageForCodeGenerationFromStrings(v8String(m_isolate, csp->evalDisabledErrorMessage()));
    232         }
    233     } else {
    234         updateActivityLogger();
    235         SecurityOrigin* origin = m_world->isolatedWorldSecurityOrigin();
    236         setSecurityToken(origin);
    237         if (origin && InspectorInstrumentation::hasFrontends()) {
    238             InspectorInstrumentation::didCreateIsolatedContext(m_frame, m_scriptState.get(), origin);
    239         }
    240     }
    241     m_frame->loader().client()->didCreateScriptContext(context, m_world->extensionGroup(), m_world->worldId());
    242     return true;
    243 }
    244 
    245 void V8WindowShell::createContext()
    246 {
    247     // The documentLoader pointer could be 0 during frame shutdown.
    248     // FIXME: Can we remove this check?
    249     if (!m_frame->loader().documentLoader())
    250         return;
    251 
    252     // Create a new environment using an empty template for the shadow
    253     // object. Reuse the global object if one has been created earlier.
    254     v8::Handle<v8::ObjectTemplate> globalTemplate = V8Window::getShadowObjectTemplate(m_isolate);
    255     if (globalTemplate.IsEmpty())
    256         return;
    257 
    258     double contextCreationStartInSeconds = currentTime();
    259 
    260     // Dynamically tell v8 about our extensions now.
    261     const V8Extensions& extensions = ScriptController::registeredExtensions();
    262     OwnPtr<const char*[]> extensionNames = adoptArrayPtr(new const char*[extensions.size()]);
    263     int index = 0;
    264     int extensionGroup = m_world->extensionGroup();
    265     int worldId = m_world->worldId();
    266     for (size_t i = 0; i < extensions.size(); ++i) {
    267         if (!m_frame->loader().client()->allowScriptExtension(extensions[i]->name(), extensionGroup, worldId))
    268             continue;
    269 
    270         extensionNames[index++] = extensions[i]->name();
    271     }
    272     v8::ExtensionConfiguration extensionConfiguration(index, extensionNames.get());
    273 
    274     v8::Handle<v8::Context> context = v8::Context::New(m_isolate, &extensionConfiguration, globalTemplate, m_global.newLocal(m_isolate));
    275     if (context.IsEmpty())
    276         return;
    277     m_scriptState = ScriptState::create(context, m_world);
    278 
    279     double contextCreationDurationInMilliseconds = (currentTime() - contextCreationStartInSeconds) * 1000;
    280     const char* histogramName = "WebCore.V8WindowShell.createContext.MainWorld";
    281     if (!m_world->isMainWorld())
    282         histogramName = "WebCore.V8WindowShell.createContext.IsolatedWorld";
    283     blink::Platform::current()->histogramCustomCounts(histogramName, contextCreationDurationInMilliseconds, 0, 10000, 50);
    284 }
    285 
    286 static v8::Handle<v8::Object> toInnerGlobalObject(v8::Handle<v8::Context> context)
    287 {
    288     return v8::Handle<v8::Object>::Cast(context->Global()->GetPrototype());
    289 }
    290 
    291 bool V8WindowShell::installDOMWindow()
    292 {
    293     LocalDOMWindow* window = m_frame->domWindow();
    294     v8::Local<v8::Object> windowWrapper = V8ObjectConstructor::newInstance(m_isolate, m_scriptState->perContextData()->constructorForType(&V8Window::wrapperTypeInfo));
    295     if (windowWrapper.IsEmpty())
    296         return false;
    297 
    298     V8Window::installPerContextEnabledProperties(windowWrapper, window, m_isolate);
    299 
    300     V8DOMWrapper::setNativeInfoForHiddenWrapper(v8::Handle<v8::Object>::Cast(windowWrapper->GetPrototype()), &V8Window::wrapperTypeInfo, window);
    301 
    302     // Install the windowWrapper as the prototype of the innerGlobalObject.
    303     // The full structure of the global object is as follows:
    304     //
    305     // outerGlobalObject (Empty object, remains after navigation)
    306     //   -- has prototype --> innerGlobalObject (Holds global variables, changes during navigation)
    307     //   -- has prototype --> LocalDOMWindow instance
    308     //   -- has prototype --> Window.prototype
    309     //   -- has prototype --> Object.prototype
    310     //
    311     // Note: Much of this prototype structure is hidden from web content. The
    312     //       outer, inner, and LocalDOMWindow instance all appear to be the same
    313     //       JavaScript object.
    314     //
    315     // Note: With Oilpan, the LocalDOMWindow object is garbage collected.
    316     //       Persistent references to this inner global object view of the LocalDOMWindow
    317     //       aren't kept, as that would prevent the global object from ever being released.
    318     //       It is safe not to do so, as the wrapper for the LocalDOMWindow being installed here
    319     //       already keeps a persistent reference, and it along with the inner global object
    320     //       views of the LocalDOMWindow will die together once that wrapper clears the persistent
    321     //       reference.
    322     v8::Handle<v8::Object> innerGlobalObject = toInnerGlobalObject(m_scriptState->context());
    323     V8DOMWrapper::setNativeInfoForHiddenWrapper(innerGlobalObject, &V8Window::wrapperTypeInfo, window);
    324     innerGlobalObject->SetPrototype(windowWrapper);
    325     V8DOMWrapper::associateObjectWithWrapper<V8Window>(PassRefPtrWillBeRawPtr<LocalDOMWindow>(window), &V8Window::wrapperTypeInfo, windowWrapper, m_isolate, WrapperConfiguration::Dependent);
    326     return true;
    327 }
    328 
    329 void V8WindowShell::updateDocumentWrapper(v8::Handle<v8::Object> wrapper)
    330 {
    331     ASSERT(m_world->isMainWorld());
    332     m_document.set(m_isolate, wrapper);
    333 }
    334 
    335 void V8WindowShell::updateDocumentProperty()
    336 {
    337     if (!m_world->isMainWorld())
    338         return;
    339 
    340     ScriptState::Scope scope(m_scriptState.get());
    341     v8::Handle<v8::Context> context = m_scriptState->context();
    342     v8::Handle<v8::Value> documentWrapper = toV8(m_frame->document(), context->Global(), context->GetIsolate());
    343     ASSERT(documentWrapper == m_document.newLocal(m_isolate) || m_document.isEmpty());
    344     if (m_document.isEmpty())
    345         updateDocumentWrapper(v8::Handle<v8::Object>::Cast(documentWrapper));
    346     checkDocumentWrapper(m_document.newLocal(m_isolate), m_frame->document());
    347 
    348     // If instantiation of the document wrapper fails, clear the cache
    349     // and let the LocalDOMWindow accessor handle access to the document.
    350     if (documentWrapper.IsEmpty()) {
    351         clearDocumentProperty();
    352         return;
    353     }
    354     ASSERT(documentWrapper->IsObject());
    355     context->Global()->ForceSet(v8AtomicString(m_isolate, "document"), documentWrapper, static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
    356 
    357     // We also stash a reference to the document on the inner global object so that
    358     // LocalDOMWindow objects we obtain from JavaScript references are guaranteed to have
    359     // live Document objects.
    360     V8HiddenValue::setHiddenValue(m_isolate, toInnerGlobalObject(context), V8HiddenValue::document(m_isolate), documentWrapper);
    361 }
    362 
    363 void V8WindowShell::clearDocumentProperty()
    364 {
    365     ASSERT(isContextInitialized());
    366     if (!m_world->isMainWorld())
    367         return;
    368     v8::HandleScope handleScope(m_isolate);
    369     m_scriptState->context()->Global()->ForceDelete(v8AtomicString(m_isolate, "document"));
    370 }
    371 
    372 void V8WindowShell::updateActivityLogger()
    373 {
    374     m_scriptState->perContextData()->setActivityLogger(V8DOMActivityLogger::activityLogger(
    375         m_world->worldId(), m_frame->document() ? m_frame->document()->baseURI() : KURL()));
    376 }
    377 
    378 void V8WindowShell::setSecurityToken(SecurityOrigin* origin)
    379 {
    380     // If two tokens are equal, then the SecurityOrigins canAccess each other.
    381     // If two tokens are not equal, then we have to call canAccess.
    382     // Note: we can't use the HTTPOrigin if it was set from the DOM.
    383     String token;
    384     // We stick with an empty token if document.domain was modified or if we
    385     // are in the initial empty document, so that we can do a full canAccess
    386     // check in those cases.
    387     bool delaySet = m_world->isMainWorld()
    388         && (origin->domainWasSetInDOM()
    389             || m_frame->loader().stateMachine()->isDisplayingInitialEmptyDocument());
    390     if (origin && !delaySet)
    391         token = origin->toString();
    392 
    393     // An empty or "null" token means we always have to call
    394     // canAccess. The toString method on securityOrigins returns the
    395     // string "null" for empty security origins and for security
    396     // origins that should only allow access to themselves. In this
    397     // case, we use the global object as the security token to avoid
    398     // calling canAccess when a script accesses its own objects.
    399     v8::HandleScope handleScope(m_isolate);
    400     v8::Handle<v8::Context> context = m_scriptState->context();
    401     if (token.isEmpty() || token == "null") {
    402         context->UseDefaultSecurityToken();
    403         return;
    404     }
    405 
    406     CString utf8Token = token.utf8();
    407     // NOTE: V8 does identity comparison in fast path, must use a symbol
    408     // as the security token.
    409     context->SetSecurityToken(v8AtomicString(m_isolate, utf8Token.data(), utf8Token.length()));
    410 }
    411 
    412 void V8WindowShell::updateDocument()
    413 {
    414     ASSERT(m_world->isMainWorld());
    415     if (!isGlobalInitialized())
    416         return;
    417     if (!isContextInitialized())
    418         return;
    419     updateActivityLogger();
    420     updateDocumentProperty();
    421     updateSecurityOrigin(m_frame->document()->securityOrigin());
    422 }
    423 
    424 static v8::Handle<v8::Value> getNamedProperty(HTMLDocument* htmlDocument, const AtomicString& key, v8::Handle<v8::Object> creationContext, v8::Isolate* isolate)
    425 {
    426     if (!htmlDocument->hasNamedItem(key) && !htmlDocument->hasExtraNamedItem(key))
    427         return v8Undefined();
    428 
    429     RefPtrWillBeRawPtr<HTMLCollection> items = htmlDocument->documentNamedItems(key);
    430     if (items->isEmpty())
    431         return v8Undefined();
    432 
    433     if (items->hasExactlyOneItem()) {
    434         Element* element = items->item(0);
    435         ASSERT(element);
    436         Frame* frame = 0;
    437         if (isHTMLIFrameElement(*element) && (frame = toHTMLIFrameElement(*element).contentFrame()))
    438             return toV8(frame->domWindow(), creationContext, isolate);
    439         return toV8(element, creationContext, isolate);
    440     }
    441     return toV8(items.release(), creationContext, isolate);
    442 }
    443 
    444 static void getter(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
    445 {
    446     // FIXME: Consider passing StringImpl directly.
    447     AtomicString name = toCoreAtomicString(property);
    448     HTMLDocument* htmlDocument = V8HTMLDocument::toNative(info.Holder());
    449     ASSERT(htmlDocument);
    450     v8::Handle<v8::Value> result = getNamedProperty(htmlDocument, name, info.Holder(), info.GetIsolate());
    451     if (!result.IsEmpty()) {
    452         v8SetReturnValue(info, result);
    453         return;
    454     }
    455     v8::Handle<v8::Value> prototype = info.Holder()->GetPrototype();
    456     if (prototype->IsObject()) {
    457         v8SetReturnValue(info, prototype.As<v8::Object>()->Get(property));
    458         return;
    459     }
    460 }
    461 
    462 void V8WindowShell::namedItemAdded(HTMLDocument* document, const AtomicString& name)
    463 {
    464     ASSERT(m_world->isMainWorld());
    465 
    466     if (!isContextInitialized())
    467         return;
    468 
    469     ScriptState::Scope scope(m_scriptState.get());
    470     ASSERT(!m_document.isEmpty());
    471     v8::Handle<v8::Object> documentHandle = m_document.newLocal(m_isolate);
    472     checkDocumentWrapper(documentHandle, document);
    473     documentHandle->SetAccessor(v8String(m_isolate, name), getter);
    474 }
    475 
    476 void V8WindowShell::namedItemRemoved(HTMLDocument* document, const AtomicString& name)
    477 {
    478     ASSERT(m_world->isMainWorld());
    479 
    480     if (!isContextInitialized())
    481         return;
    482 
    483     if (document->hasNamedItem(name) || document->hasExtraNamedItem(name))
    484         return;
    485 
    486     ScriptState::Scope scope(m_scriptState.get());
    487     ASSERT(!m_document.isEmpty());
    488     v8::Handle<v8::Object> documentHandle = m_document.newLocal(m_isolate);
    489     checkDocumentWrapper(documentHandle, document);
    490     documentHandle->Delete(v8String(m_isolate, name));
    491 }
    492 
    493 void V8WindowShell::updateSecurityOrigin(SecurityOrigin* origin)
    494 {
    495     ASSERT(m_world->isMainWorld());
    496     if (!isContextInitialized())
    497         return;
    498     setSecurityToken(origin);
    499 }
    500 
    501 } // WebCore
    502