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