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