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 "RuntimeEnabledFeatures.h"
     35 #include "V8Document.h"
     36 #include "V8HTMLCollection.h"
     37 #include "V8HTMLDocument.h"
     38 #include "V8Window.h"
     39 #include "bindings/v8/DOMWrapperWorld.h"
     40 #include "bindings/v8/ScriptController.h"
     41 #include "bindings/v8/V8Binding.h"
     42 #include "bindings/v8/V8GCForContextDispose.h"
     43 #include "bindings/v8/V8HiddenPropertyName.h"
     44 #include "bindings/v8/V8Initializer.h"
     45 #include "bindings/v8/V8ObjectConstructor.h"
     46 #include "bindings/v8/V8PerContextData.h"
     47 #include "core/html/HTMLCollection.h"
     48 #include "core/html/HTMLIFrameElement.h"
     49 #include "core/inspector/InspectorInstrumentation.h"
     50 #include "core/loader/DocumentLoader.h"
     51 #include "core/loader/FrameLoader.h"
     52 #include "core/loader/FrameLoaderClient.h"
     53 #include "core/frame/ContentSecurityPolicy.h"
     54 #include "core/frame/Frame.h"
     55 #include "platform/TraceEvent.h"
     56 #include "platform/weborigin/SecurityOrigin.h"
     57 #include "public/platform/Platform.h"
     58 #include "wtf/Assertions.h"
     59 #include "wtf/OwnPtr.h"
     60 #include "wtf/StringExtras.h"
     61 #include "wtf/text/CString.h"
     62 #include <algorithm>
     63 #include <utility>
     64 #include <v8-debug.h>
     65 #include <v8.h>
     66 
     67 namespace WebCore {
     68 
     69 static void checkDocumentWrapper(v8::Handle<v8::Object> wrapper, Document* document)
     70 {
     71     ASSERT(V8Document::toNative(wrapper) == document);
     72     ASSERT(!document->isHTMLDocument() || (V8Document::toNative(v8::Handle<v8::Object>::Cast(wrapper->GetPrototype())) == document));
     73 }
     74 
     75 static void setInjectedScriptContextDebugId(v8::Handle<v8::Context> targetContext, int debugId)
     76 {
     77     V8PerContextDebugData::setContextDebugData(targetContext, "injected", debugId);
     78 }
     79 
     80 PassOwnPtr<V8WindowShell> V8WindowShell::create(Frame* frame, PassRefPtr<DOMWrapperWorld> world, v8::Isolate* isolate)
     81 {
     82     return adoptPtr(new V8WindowShell(frame, world, isolate));
     83 }
     84 
     85 V8WindowShell::V8WindowShell(Frame* frame, PassRefPtr<DOMWrapperWorld> world, v8::Isolate* isolate)
     86     : m_frame(frame)
     87     , m_world(world)
     88     , m_isolate(isolate)
     89 {
     90 }
     91 
     92 void V8WindowShell::disposeContext(GlobalDetachmentBehavior behavior)
     93 {
     94     m_perContextData.clear();
     95 
     96     if (!m_contextHolder)
     97         return;
     98 
     99     v8::HandleScope handleScope(m_isolate);
    100     v8::Handle<v8::Context> context = m_contextHolder->context();
    101     m_frame->loader().client()->willReleaseScriptContext(context, m_world->worldId());
    102 
    103     if (behavior == DetachGlobal)
    104         context->DetachGlobal();
    105 
    106     m_contextHolder.clear();
    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(bool destroyGlobal)
    115 {
    116     if (destroyGlobal)
    117         m_global.clear();
    118 
    119     if (!m_contextHolder)
    120         return;
    121 
    122     m_document.clear();
    123     disposeContext(DoNotDetachGlobal);
    124 }
    125 
    126 void V8WindowShell::clearForNavigation()
    127 {
    128     if (!m_contextHolder)
    129         return;
    130 
    131     v8::HandleScope handleScope(m_isolate);
    132     m_document.clear();
    133 
    134     v8::Handle<v8::Context> context = m_contextHolder->context();
    135     v8::Context::Scope contextScope(context);
    136 
    137     // Clear the document wrapper cache before turning on access checks on
    138     // the old DOMWindow wrapper. This way, access to the document wrapper
    139     // will be protected by the security checks on the DOMWindow wrapper.
    140     clearDocumentProperty();
    141 
    142     v8::Handle<v8::Object> windowWrapper = m_global.newLocal(m_isolate)->FindInstanceInPrototypeChain(V8Window::domTemplate(m_isolate, worldTypeInMainThread(m_isolate)));
    143     ASSERT(!windowWrapper.IsEmpty());
    144     windowWrapper->TurnOnAccessCheck();
    145     disposeContext(DetachGlobal);
    146 }
    147 
    148 // Create a new environment and setup the global object.
    149 //
    150 // The global object corresponds to a DOMWindow instance. However, to
    151 // allow properties of the JS DOMWindow instance to be shadowed, we
    152 // use a shadow object as the global object and use the JS DOMWindow
    153 // instance as the prototype for that shadow object. The JS DOMWindow
    154 // instance is undetectable from JavaScript code because the __proto__
    155 // accessors skip that object.
    156 //
    157 // The shadow object and the DOMWindow instance are seen as one object
    158 // from JavaScript. The JavaScript object that corresponds to a
    159 // DOMWindow instance is the shadow object. When mapping a DOMWindow
    160 // instance to a V8 object, we return the shadow object.
    161 //
    162 // To implement split-window, see
    163 //   1) https://bugs.webkit.org/show_bug.cgi?id=17249
    164 //   2) https://wiki.mozilla.org/Gecko:SplitWindow
    165 //   3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639
    166 // we need to split the shadow object further into two objects:
    167 // an outer window and an inner window. The inner window is the hidden
    168 // prototype of the outer window. The inner window is the default
    169 // global object of the context. A variable declared in the global
    170 // scope is a property of the inner window.
    171 //
    172 // The outer window sticks to a Frame, it is exposed to JavaScript
    173 // via window.window, window.self, window.parent, etc. The outer window
    174 // has a security token which is the domain. The outer window cannot
    175 // have its own properties. window.foo = 'x' is delegated to the
    176 // inner window.
    177 //
    178 // When a frame navigates to a new page, the inner window is cut off
    179 // the outer window, and the outer window identify is preserved for
    180 // the frame. However, a new inner window is created for the new page.
    181 // If there are JS code holds a closure to the old inner window,
    182 // it won't be able to reach the outer window via its global object.
    183 bool V8WindowShell::initializeIfNeeded()
    184 {
    185     if (m_contextHolder)
    186         return true;
    187 
    188     TRACE_EVENT0("v8", "V8WindowShell::initializeIfNeeded");
    189 
    190     v8::HandleScope handleScope(m_isolate);
    191 
    192     createContext();
    193     if (!m_contextHolder)
    194         return false;
    195 
    196     v8::Handle<v8::Context> context = m_contextHolder->context();
    197 
    198     V8PerContextDataHolder::install(context);
    199 
    200     m_world->setIsolatedWorldField(context);
    201 
    202     bool isMainWorld = m_world->isMainWorld();
    203 
    204     v8::Context::Scope contextScope(context);
    205 
    206     if (m_global.isEmpty()) {
    207         m_global.set(m_isolate, context->Global());
    208         if (m_global.isEmpty()) {
    209             disposeContext(DoNotDetachGlobal);
    210             return false;
    211         }
    212     }
    213 
    214     if (!isMainWorld) {
    215         V8WindowShell* mainWindow = m_frame->script().existingWindowShell(mainThreadNormalWorld());
    216         if (mainWindow && !mainWindow->context().IsEmpty())
    217             setInjectedScriptContextDebugId(context, m_frame->script().contextDebugId(mainWindow->context()));
    218     }
    219 
    220     m_perContextData = V8PerContextData::create(context);
    221     if (!m_perContextData->init()) {
    222         disposeContext(DoNotDetachGlobal);
    223         return false;
    224     }
    225     m_perContextData->setActivityLogger(DOMWrapperWorld::activityLogger(m_world->worldId()));
    226     if (!installDOMWindow()) {
    227         disposeContext(DoNotDetachGlobal);
    228         return false;
    229     }
    230 
    231     if (isMainWorld) {
    232         updateDocument();
    233         setSecurityToken();
    234         if (m_frame->document()) {
    235             ContentSecurityPolicy* csp = m_frame->document()->contentSecurityPolicy();
    236             context->AllowCodeGenerationFromStrings(csp->allowEval(0, ContentSecurityPolicy::SuppressReport));
    237             context->SetErrorMessageForCodeGenerationFromStrings(v8String(m_isolate, csp->evalDisabledErrorMessage()));
    238         }
    239     } else {
    240         // Using the default security token means that the canAccess is always
    241         // called, which is slow.
    242         // FIXME: Use tokens where possible. This will mean keeping track of all
    243         //        created contexts so that they can all be updated when the
    244         //        document domain
    245         //        changes.
    246         context->UseDefaultSecurityToken();
    247 
    248         SecurityOrigin* origin = m_world->isolatedWorldSecurityOrigin();
    249         if (origin && InspectorInstrumentation::hasFrontends()) {
    250             ScriptState* scriptState = ScriptState::forContext(v8::Local<v8::Context>::New(m_isolate, context));
    251             InspectorInstrumentation::didCreateIsolatedContext(m_frame, scriptState, origin);
    252         }
    253     }
    254     m_frame->loader().client()->didCreateScriptContext(context, m_world->extensionGroup(), m_world->worldId());
    255     return true;
    256 }
    257 
    258 void V8WindowShell::createContext()
    259 {
    260     // The activeDocumentLoader pointer could be 0 during frame shutdown.
    261     // FIXME: Can we remove this check?
    262     if (!m_frame->loader().activeDocumentLoader())
    263         return;
    264 
    265     // Create a new environment using an empty template for the shadow
    266     // object. Reuse the global object if one has been created earlier.
    267     v8::Handle<v8::ObjectTemplate> globalTemplate = V8Window::GetShadowObjectTemplate(m_isolate, m_world->isMainWorld() ? MainWorld : IsolatedWorld);
    268     if (globalTemplate.IsEmpty())
    269         return;
    270 
    271     double contextCreationStartInSeconds = currentTime();
    272 
    273     // Dynamically tell v8 about our extensions now.
    274     const V8Extensions& extensions = ScriptController::registeredExtensions();
    275     OwnPtr<const char*[]> extensionNames = adoptArrayPtr(new const char*[extensions.size()]);
    276     int index = 0;
    277     int extensionGroup = m_world->extensionGroup();
    278     int worldId = m_world->worldId();
    279     for (size_t i = 0; i < extensions.size(); ++i) {
    280         if (!m_frame->loader().client()->allowScriptExtension(extensions[i]->name(), extensionGroup, worldId))
    281             continue;
    282 
    283         extensionNames[index++] = extensions[i]->name();
    284     }
    285     v8::ExtensionConfiguration extensionConfiguration(index, extensionNames.get());
    286 
    287     v8::HandleScope handleScope(m_isolate);
    288     v8::Handle<v8::Context> context = v8::Context::New(m_isolate, &extensionConfiguration, globalTemplate, m_global.newLocal(m_isolate));
    289     if (!context.IsEmpty()) {
    290         m_contextHolder = adoptPtr(new gin::ContextHolder(m_isolate));
    291         m_contextHolder->SetContext(context);
    292     }
    293 
    294     double contextCreationDurationInMilliseconds = (currentTime() - contextCreationStartInSeconds) * 1000;
    295     const char* histogramName = "WebCore.V8WindowShell.createContext.MainWorld";
    296     if (!m_world->isMainWorld())
    297         histogramName = "WebCore.V8WindowShell.createContext.IsolatedWorld";
    298     blink::Platform::current()->histogramCustomCounts(histogramName, contextCreationDurationInMilliseconds, 0, 10000, 50);
    299 }
    300 
    301 bool V8WindowShell::installDOMWindow()
    302 {
    303     DOMWrapperWorld::setInitializingWindow(true);
    304     DOMWindow* window = m_frame->domWindow();
    305     v8::Local<v8::Object> windowWrapper = V8ObjectConstructor::newInstance(V8PerContextData::from(m_contextHolder->context())->constructorForType(&V8Window::wrapperTypeInfo));
    306     if (windowWrapper.IsEmpty())
    307         return false;
    308 
    309     V8Window::installPerContextEnabledProperties(windowWrapper, window, m_isolate);
    310 
    311     V8DOMWrapper::setNativeInfo(v8::Handle<v8::Object>::Cast(windowWrapper->GetPrototype()), &V8Window::wrapperTypeInfo, window);
    312 
    313     // Install the windowWrapper as the prototype of the innerGlobalObject.
    314     // The full structure of the global object is as follows:
    315     //
    316     // outerGlobalObject (Empty object, remains after navigation)
    317     //   -- has prototype --> innerGlobalObject (Holds global variables, changes during navigation)
    318     //   -- has prototype --> DOMWindow instance
    319     //   -- has prototype --> Window.prototype
    320     //   -- has prototype --> Object.prototype
    321     //
    322     // Note: Much of this prototype structure is hidden from web content. The
    323     //       outer, inner, and DOMWindow instance all appear to be the same
    324     //       JavaScript object.
    325     //
    326     v8::Handle<v8::Object> innerGlobalObject = toInnerGlobalObject(m_contextHolder->context());
    327     V8DOMWrapper::setNativeInfo(innerGlobalObject, &V8Window::wrapperTypeInfo, window);
    328     innerGlobalObject->SetPrototype(windowWrapper);
    329     V8DOMWrapper::associateObjectWithWrapper<V8Window>(PassRefPtr<DOMWindow>(window), &V8Window::wrapperTypeInfo, windowWrapper, m_isolate, WrapperConfiguration::Dependent);
    330     DOMWrapperWorld::setInitializingWindow(false);
    331     return true;
    332 }
    333 
    334 void V8WindowShell::updateDocumentWrapper(v8::Handle<v8::Object> wrapper)
    335 {
    336     ASSERT(m_world->isMainWorld());
    337     m_document.set(m_isolate, wrapper);
    338 }
    339 
    340 void V8WindowShell::updateDocumentProperty()
    341 {
    342     if (!m_world->isMainWorld())
    343         return;
    344 
    345     v8::HandleScope handleScope(m_isolate);
    346     v8::Handle<v8::Context> context = m_contextHolder->context();
    347     v8::Context::Scope contextScope(context);
    348 
    349     v8::Handle<v8::Value> documentWrapper = toV8(m_frame->document(), v8::Handle<v8::Object>(), context->GetIsolate());
    350     ASSERT(documentWrapper == m_document.newLocal(m_isolate) || m_document.isEmpty());
    351     if (m_document.isEmpty())
    352         updateDocumentWrapper(v8::Handle<v8::Object>::Cast(documentWrapper));
    353     checkDocumentWrapper(m_document.newLocal(m_isolate), m_frame->document());
    354 
    355     // If instantiation of the document wrapper fails, clear the cache
    356     // and let the DOMWindow accessor handle access to the document.
    357     if (documentWrapper.IsEmpty()) {
    358         clearDocumentProperty();
    359         return;
    360     }
    361     ASSERT(documentWrapper->IsObject());
    362     context->Global()->ForceSet(v8AtomicString(m_isolate, "document"), documentWrapper, static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
    363 
    364     // We also stash a reference to the document on the inner global object so that
    365     // DOMWindow objects we obtain from JavaScript references are guaranteed to have
    366     // live Document objects.
    367     toInnerGlobalObject(context)->SetHiddenValue(V8HiddenPropertyName::document(m_isolate), documentWrapper);
    368 }
    369 
    370 void V8WindowShell::clearDocumentProperty()
    371 {
    372     ASSERT(m_contextHolder);
    373     if (!m_world->isMainWorld())
    374         return;
    375     v8::HandleScope handleScope(m_isolate);
    376     m_contextHolder->context()->Global()->ForceDelete(v8AtomicString(m_isolate, "document"));
    377 }
    378 
    379 void V8WindowShell::setSecurityToken()
    380 {
    381     ASSERT(m_world->isMainWorld());
    382 
    383     Document* document = m_frame->document();
    384 
    385     // Ask the document's SecurityOrigin to generate a security token.
    386     // If two tokens are equal, then the SecurityOrigins canAccess each other.
    387     // If two tokens are not equal, then we have to call canAccess.
    388     // Note: we can't use the HTTPOrigin if it was set from the DOM.
    389     SecurityOrigin* origin = document->securityOrigin();
    390     String token;
    391     // We stick with an empty token if document.domain was modified or if we
    392     // are in the initial empty document, so that we can do a full canAccess
    393     // check in those cases.
    394     if (!origin->domainWasSetInDOM()
    395         && !m_frame->loader().stateMachine()->isDisplayingInitialEmptyDocument())
    396         token = document->securityOrigin()->toString();
    397 
    398     // An empty or "null" token means we always have to call
    399     // canAccess. The toString method on securityOrigins returns the
    400     // string "null" for empty security origins and for security
    401     // origins that should only allow access to themselves. In this
    402     // case, we use the global object as the security token to avoid
    403     // calling canAccess when a script accesses its own objects.
    404     v8::HandleScope handleScope(m_isolate);
    405     v8::Handle<v8::Context> context = m_contextHolder->context();
    406     if (token.isEmpty() || token == "null") {
    407         context->UseDefaultSecurityToken();
    408         return;
    409     }
    410 
    411     CString utf8Token = token.utf8();
    412     // NOTE: V8 does identity comparison in fast path, must use a symbol
    413     // as the security token.
    414     context->SetSecurityToken(v8AtomicString(m_isolate, utf8Token.data(), utf8Token.length()));
    415 }
    416 
    417 void V8WindowShell::updateDocument()
    418 {
    419     ASSERT(m_world->isMainWorld());
    420     if (m_global.isEmpty())
    421         return;
    422     if (!m_contextHolder)
    423         return;
    424     updateDocumentProperty();
    425     updateSecurityOrigin();
    426 }
    427 
    428 static v8::Handle<v8::Value> getNamedProperty(HTMLDocument* htmlDocument, const AtomicString& key, v8::Handle<v8::Object> creationContext, v8::Isolate* isolate)
    429 {
    430     if (!htmlDocument->hasNamedItem(key) && !htmlDocument->hasExtraNamedItem(key))
    431         return v8Undefined();
    432 
    433     RefPtr<HTMLCollection> items = htmlDocument->documentNamedItems(key);
    434     if (items->isEmpty())
    435         return v8Undefined();
    436 
    437     if (items->hasExactlyOneItem()) {
    438         Node* node = items->item(0);
    439         Frame* frame = 0;
    440         if (node->hasTagName(HTMLNames::iframeTag) && (frame = toHTMLIFrameElement(node)->contentFrame()))
    441             return toV8(frame->domWindow(), creationContext, isolate);
    442         return toV8(node, creationContext, isolate);
    443     }
    444     return toV8(items.release(), creationContext, isolate);
    445 }
    446 
    447 static void getter(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info)
    448 {
    449     // FIXME: Consider passing StringImpl directly.
    450     AtomicString name = toCoreAtomicString(property);
    451     HTMLDocument* htmlDocument = V8HTMLDocument::toNative(info.Holder());
    452     ASSERT(htmlDocument);
    453     v8::Handle<v8::Value> result = getNamedProperty(htmlDocument, name, info.Holder(), info.GetIsolate());
    454     if (!result.IsEmpty()) {
    455         v8SetReturnValue(info, result);
    456         return;
    457     }
    458     v8::Handle<v8::Value> prototype = info.Holder()->GetPrototype();
    459     if (prototype->IsObject()) {
    460         v8SetReturnValue(info, prototype.As<v8::Object>()->Get(property));
    461         return;
    462     }
    463 }
    464 
    465 void V8WindowShell::namedItemAdded(HTMLDocument* document, const AtomicString& name)
    466 {
    467     ASSERT(m_world->isMainWorld());
    468 
    469     if (!m_contextHolder)
    470         return;
    471 
    472     v8::HandleScope handleScope(m_isolate);
    473     v8::Context::Scope contextScope(m_contextHolder->context());
    474 
    475     ASSERT(!m_document.isEmpty());
    476     v8::Handle<v8::Object> documentHandle = m_document.newLocal(m_isolate);
    477     checkDocumentWrapper(documentHandle, document);
    478     documentHandle->SetAccessor(v8String(m_isolate, name), getter);
    479 }
    480 
    481 void V8WindowShell::namedItemRemoved(HTMLDocument* document, const AtomicString& name)
    482 {
    483     ASSERT(m_world->isMainWorld());
    484 
    485     if (!m_contextHolder)
    486         return;
    487 
    488     if (document->hasNamedItem(name) || document->hasExtraNamedItem(name))
    489         return;
    490 
    491     v8::HandleScope handleScope(m_isolate);
    492     v8::Context::Scope contextScope(m_contextHolder->context());
    493 
    494     ASSERT(!m_document.isEmpty());
    495     v8::Handle<v8::Object> documentHandle = m_document.newLocal(m_isolate);
    496     checkDocumentWrapper(documentHandle, document);
    497     documentHandle->Delete(v8String(m_isolate, name));
    498 }
    499 
    500 void V8WindowShell::updateSecurityOrigin()
    501 {
    502     ASSERT(m_world->isMainWorld());
    503     if (!m_contextHolder)
    504         return;
    505     v8::HandleScope handleScope(m_isolate);
    506     setSecurityToken();
    507 }
    508 
    509 } // WebCore
    510