1c44a41c603021b811adbabfcf809600fe5cba31
[WebKit-https.git] / Source / WebCore / bindings / v8 / V8DOMWindowShell.cpp
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 "V8DOMWindowShell.h"
33
34 #include "PlatformSupport.h"
35 #include "DateExtension.h"
36 #include "DocumentLoader.h"
37 #include "Frame.h"
38 #include "FrameLoaderClient.h"
39 #include "Page.h"
40 #include "PageGroup.h"
41 #include "RuntimeEnabledFeatures.h"
42 #include "SafeAllocation.h"
43 #include "ScriptCallStack.h"
44 #include "ScriptCallStackFactory.h"
45 #include "ScriptProfiler.h"
46 #include "SecurityOrigin.h"
47 #include "StorageNamespace.h"
48 #include "StylePropertySet.h"
49 #include "V8Binding.h"
50 #include "V8BindingPerContextData.h"
51 #include "V8BindingState.h"
52 #include "V8Collection.h"
53 #include "V8DOMMap.h"
54 #include "V8DOMWindow.h"
55 #include "V8Document.h"
56 #include "V8GCForContextDispose.h"
57 #include "V8HTMLDocument.h"
58 #include "V8HiddenPropertyName.h"
59 #include "V8History.h"
60 #include "V8Location.h"
61 #include "V8Proxy.h"
62 #include "WorkerContextExecutionProxy.h"
63
64 #include <algorithm>
65 #include <stdio.h>
66 #include <utility>
67 #include <v8-debug.h>
68 #include <v8.h>
69
70 #if ENABLE(JAVASCRIPT_I18N_API)
71 #include <v8-i18n/include/extension.h>
72 #endif
73
74 #include <wtf/Assertions.h>
75 #include <wtf/OwnArrayPtr.h>
76 #include <wtf/StdLibExtras.h>
77 #include <wtf/StringExtras.h>
78 #include <wtf/UnusedParam.h>
79 #include <wtf/text/CString.h>
80
81 namespace WebCore {
82
83 static void handleFatalErrorInV8()
84 {
85     // FIXME: We temporarily deal with V8 internal error situations
86     // such as out-of-memory by crashing the renderer.
87     CRASH();
88 }
89
90 static void reportFatalErrorInV8(const char* location, const char* message)
91 {
92     // V8 is shutdown, we cannot use V8 api.
93     // The only thing we can do is to disable JavaScript.
94     // FIXME: clean up V8Proxy and disable JavaScript.
95     int memoryUsageMB = -1;
96 #if PLATFORM(CHROMIUM)
97     memoryUsageMB = PlatformSupport::actualMemoryUsageMB();
98 #endif
99     printf("V8 error: %s (%s).  Current memory usage: %d MB\n", message, location, memoryUsageMB);
100     handleFatalErrorInV8();
101 }
102
103 static void v8UncaughtExceptionHandler(v8::Handle<v8::Message> message, v8::Handle<v8::Value> data)
104 {
105     // Use the frame where JavaScript is called from.
106     Frame* frame = V8Proxy::retrieveFrameForEnteredContext();
107     if (!frame)
108         return;
109
110     v8::Handle<v8::String> errorMessageString = message->Get();
111     ASSERT(!errorMessageString.IsEmpty());
112     String errorMessage = toWebCoreString(errorMessageString);
113
114     v8::Handle<v8::StackTrace> stackTrace = message->GetStackTrace();
115     RefPtr<ScriptCallStack> callStack;
116     // Currently stack trace is only collected when inspector is open.
117     if (!stackTrace.IsEmpty() && stackTrace->GetFrameCount() > 0)
118         callStack = createScriptCallStack(stackTrace, ScriptCallStack::maxCallStackSizeToCapture);
119
120     v8::Handle<v8::Value> resourceName = message->GetScriptResourceName();
121     bool useURL = resourceName.IsEmpty() || !resourceName->IsString();
122     Document* document = frame->document();
123     String resourceNameString = useURL ? document->url() : toWebCoreString(resourceName);
124     document->reportException(errorMessage, message->GetLineNumber(), resourceNameString, callStack);
125 }
126
127 // Returns the owner frame pointer of a DOM wrapper object. It only works for
128 // these DOM objects requiring cross-domain access check.
129 static Frame* getTargetFrame(v8::Local<v8::Object> host, v8::Local<v8::Value> data)
130 {
131     Frame* target = 0;
132     WrapperTypeInfo* type = WrapperTypeInfo::unwrap(data);
133     if (V8DOMWindow::info.equals(type)) {
134         v8::Handle<v8::Object> window = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), host);
135         if (window.IsEmpty())
136             return target;
137
138         DOMWindow* targetWindow = V8DOMWindow::toNative(window);
139         target = targetWindow->frame();
140     } else if (V8History::info.equals(type)) {
141         History* history = V8History::toNative(host);
142         target = history->frame();
143     } else if (V8Location::info.equals(type)) {
144         Location* location = V8Location::toNative(host);
145         target = location->frame();
146     }
147     return target;
148 }
149
150 static void reportUnsafeJavaScriptAccess(v8::Local<v8::Object> host, v8::AccessType type, v8::Local<v8::Value> data)
151 {
152     Frame* target = getTargetFrame(host, data);
153     if (target)
154         V8Proxy::reportUnsafeAccessTo(target);
155 }
156
157 PassRefPtr<V8DOMWindowShell> V8DOMWindowShell::create(Frame* frame)
158 {
159     return adoptRef(new V8DOMWindowShell(frame));
160 }
161
162 V8DOMWindowShell::V8DOMWindowShell(Frame* frame)
163     : m_frame(frame)
164 {
165 }
166
167 bool V8DOMWindowShell::isContextInitialized()
168 {
169     // m_context, m_global, and m_wrapperBoilerplates should
170     // all be non-empty if if m_context is non-empty.
171     ASSERT(m_context.IsEmpty() || !m_global.IsEmpty());
172     return !m_context.IsEmpty();
173 }
174
175 void V8DOMWindowShell::disposeContextHandles()
176 {
177     if (!m_context.IsEmpty()) {
178         m_frame->loader()->client()->willReleaseScriptContext(m_context, 0);
179         m_context.Dispose();
180         m_context.Clear();
181
182         // It's likely that disposing the context has created a lot of
183         // garbage. Notify V8 about this so it'll have a chance of cleaning
184         // it up when idle.
185         bool isMainFrame = m_frame->page() && (m_frame->page()->mainFrame() == m_frame); 
186         V8GCForContextDispose::instance().notifyContextDisposed(isMainFrame);
187     }
188
189     m_perContextData.clear();
190 }
191
192 void V8DOMWindowShell::destroyGlobal()
193 {
194     if (!m_global.IsEmpty()) {
195 #ifndef NDEBUG
196         V8GCController::unregisterGlobalHandle(this, m_global);
197 #endif
198         m_global.Dispose();
199         m_global.Clear();
200     }
201 }
202
203 void V8DOMWindowShell::clearForClose()
204 {
205     if (!m_context.IsEmpty()) {
206         v8::HandleScope handleScope;
207
208         clearDocumentWrapper();
209         disposeContextHandles();
210     }
211 }
212
213 void V8DOMWindowShell::clearForNavigation()
214 {
215     if (!m_context.IsEmpty()) {
216         v8::HandleScope handle;
217         clearDocumentWrapper();
218
219         v8::Context::Scope contextScope(m_context);
220
221         // Clear the document wrapper cache before turning on access checks on
222         // the old DOMWindow wrapper. This way, access to the document wrapper
223         // will be protected by the security checks on the DOMWindow wrapper.
224         clearDocumentWrapperCache();
225
226         // Turn on access check on the old DOMWindow wrapper.
227         v8::Handle<v8::Object> wrapper = V8DOMWrapper::lookupDOMWrapper(V8DOMWindow::GetTemplate(), m_global);
228         ASSERT(!wrapper.IsEmpty());
229         wrapper->TurnOnAccessCheck();
230
231         // Separate the context from its global object.
232         m_context->DetachGlobal();
233
234         disposeContextHandles();
235     }
236 }
237
238 // Create a new environment and setup the global object.
239 //
240 // The global object corresponds to a DOMWindow instance. However, to
241 // allow properties of the JS DOMWindow instance to be shadowed, we
242 // use a shadow object as the global object and use the JS DOMWindow
243 // instance as the prototype for that shadow object. The JS DOMWindow
244 // instance is undetectable from javascript code because the __proto__
245 // accessors skip that object.
246 //
247 // The shadow object and the DOMWindow instance are seen as one object
248 // from javascript. The javascript object that corresponds to a
249 // DOMWindow instance is the shadow object. When mapping a DOMWindow
250 // instance to a V8 object, we return the shadow object.
251 //
252 // To implement split-window, see
253 //   1) https://bugs.webkit.org/show_bug.cgi?id=17249
254 //   2) https://wiki.mozilla.org/Gecko:SplitWindow
255 //   3) https://bugzilla.mozilla.org/show_bug.cgi?id=296639
256 // we need to split the shadow object further into two objects:
257 // an outer window and an inner window. The inner window is the hidden
258 // prototype of the outer window. The inner window is the default
259 // global object of the context. A variable declared in the global
260 // scope is a property of the inner window.
261 //
262 // The outer window sticks to a Frame, it is exposed to JavaScript
263 // via window.window, window.self, window.parent, etc. The outer window
264 // has a security token which is the domain. The outer window cannot
265 // have its own properties. window.foo = 'x' is delegated to the
266 // inner window.
267 //
268 // When a frame navigates to a new page, the inner window is cut off
269 // the outer window, and the outer window identify is preserved for
270 // the frame. However, a new inner window is created for the new page.
271 // If there are JS code holds a closure to the old inner window,
272 // it won't be able to reach the outer window via its global object.
273 bool V8DOMWindowShell::initContextIfNeeded()
274 {
275     // Bail out if the context has already been initialized.
276     if (!m_context.IsEmpty())
277         return true;
278
279     // Create a handle scope for all local handles.
280     v8::HandleScope handleScope;
281
282     // Setup the security handlers and message listener. This only has
283     // to be done once.
284     static bool isV8Initialized = false;
285     if (!isV8Initialized) {
286         // Tells V8 not to call the default OOM handler, binding code
287         // will handle it.
288         v8::V8::IgnoreOutOfMemoryException();
289         v8::V8::SetFatalErrorHandler(reportFatalErrorInV8);
290
291         v8::V8::SetGlobalGCPrologueCallback(&V8GCController::gcPrologue);
292         v8::V8::SetGlobalGCEpilogueCallback(&V8GCController::gcEpilogue);
293
294         v8::V8::AddMessageListener(&v8UncaughtExceptionHandler);
295
296         v8::V8::SetFailedAccessCheckCallbackFunction(reportUnsafeJavaScriptAccess);
297 #if ENABLE(JAVASCRIPT_DEBUGGER)
298         ScriptProfiler::initialize();
299 #endif
300         V8BindingPerIsolateData::ensureInitialized(v8::Isolate::GetCurrent());
301
302         isV8Initialized = true;
303     }
304
305     m_context = createNewContext(m_global, 0, 0);
306     if (m_context.IsEmpty())
307         return false;
308
309     v8::Local<v8::Context> v8Context = v8::Local<v8::Context>::New(m_context);
310     v8::Context::Scope contextScope(v8Context);
311
312     // Store the first global object created so we can reuse it.
313     if (m_global.IsEmpty()) {
314         m_global = v8::Persistent<v8::Object>::New(v8Context->Global());
315         // Bail out if allocation of the first global objects fails.
316         if (m_global.IsEmpty()) {
317             disposeContextHandles();
318             return false;
319         }
320 #ifndef NDEBUG
321         V8GCController::registerGlobalHandle(PROXY, this, m_global);
322 #endif
323     }
324
325     m_perContextData = V8BindingPerContextData::create(m_context);
326     if (!m_perContextData->init()) {
327         disposeContextHandles();
328         return false;
329     }
330
331     if (!installDOMWindow(v8Context, m_frame->domWindow())) {
332         disposeContextHandles();
333         return false;
334     }
335
336     updateDocument();
337
338     setSecurityToken();
339
340     m_frame->loader()->client()->didCreateScriptContext(m_context, 0, 0);
341
342     // FIXME: This is wrong. We should actually do this for the proper world once
343     // we do isolated worlds the WebCore way.
344     m_frame->loader()->dispatchDidClearWindowObjectInWorld(0);
345
346     return true;
347 }
348
349 v8::Persistent<v8::Context> V8DOMWindowShell::createNewContext(v8::Handle<v8::Object> global, int extensionGroup, int worldId)
350 {
351     v8::Persistent<v8::Context> result;
352
353     // The activeDocumentLoader pointer could be 0 during frame shutdown.
354     if (!m_frame->loader()->activeDocumentLoader())
355         return result;
356
357     // Create a new environment using an empty template for the shadow
358     // object. Reuse the global object if one has been created earlier.
359     v8::Persistent<v8::ObjectTemplate> globalTemplate = V8DOMWindow::GetShadowObjectTemplate();
360     if (globalTemplate.IsEmpty())
361         return result;
362
363     // Used to avoid sleep calls in unload handlers.
364     if (!V8Proxy::registeredExtensionWithV8(DateExtension::get()))
365         V8Proxy::registerExtension(DateExtension::get());
366
367 #if ENABLE(JAVASCRIPT_I18N_API)
368     // Enables experimental i18n API in V8.
369     if (RuntimeEnabledFeatures::javaScriptI18NAPIEnabled() && !V8Proxy::registeredExtensionWithV8(v8_i18n::Extension::get()))
370         V8Proxy::registerExtension(v8_i18n::Extension::get());
371 #endif
372
373     // Dynamically tell v8 about our extensions now.
374     const V8Extensions& extensions = V8Proxy::extensions();
375     OwnArrayPtr<const char*> extensionNames = adoptArrayPtr(new const char*[extensions.size()]);
376     int index = 0;
377     for (size_t i = 0; i < extensions.size(); ++i) {
378         // Ensure our date extension is always allowed.
379         if (extensions[i] != DateExtension::get()
380             && !m_frame->loader()->client()->allowScriptExtension(extensions[i]->name(), extensionGroup, worldId))
381             continue;
382
383         extensionNames[index++] = extensions[i]->name();
384     }
385     v8::ExtensionConfiguration extensionConfiguration(index, extensionNames.get());
386     result = v8::Context::New(&extensionConfiguration, globalTemplate, global);
387
388     return result;
389 }
390
391 void V8DOMWindowShell::setContext(v8::Handle<v8::Context> context)
392 {
393     // if we already have a context, clear it before setting the new one.
394     if (!m_context.IsEmpty()) {
395         m_context.Dispose();
396         m_context.Clear();
397     }
398     m_context = v8::Persistent<v8::Context>::New(context);
399 }
400
401 bool V8DOMWindowShell::installDOMWindow(v8::Handle<v8::Context> context, DOMWindow* window)
402 {
403     // Create a new JS window object and use it as the prototype for the  shadow global object.
404     v8::Handle<v8::Function> windowConstructor = V8DOMWrapper::constructorForType(&V8DOMWindow::info, window);
405     v8::Local<v8::Object> jsWindow = SafeAllocation::newInstance(windowConstructor);
406     // Bail out if allocation failed.
407     if (jsWindow.IsEmpty())
408         return false;
409
410     // Wrap the window.
411     V8DOMWrapper::setDOMWrapper(jsWindow, &V8DOMWindow::info, window);
412     V8DOMWrapper::setDOMWrapper(v8::Handle<v8::Object>::Cast(jsWindow->GetPrototype()), &V8DOMWindow::info, window);
413
414     V8DOMWrapper::setJSWrapperForDOMObject(PassRefPtr<DOMWindow>(window), v8::Persistent<v8::Object>::New(jsWindow));
415
416     // Insert the window instance as the prototype of the shadow object.
417     v8::Handle<v8::Object> v8RealGlobal = v8::Handle<v8::Object>::Cast(context->Global()->GetPrototype());
418     V8DOMWrapper::setDOMWrapper(v8RealGlobal, &V8DOMWindow::info, window);
419     v8RealGlobal->SetPrototype(jsWindow);
420     return true;
421 }
422
423 void V8DOMWindowShell::updateDocumentWrapper(v8::Handle<v8::Object> wrapper)
424 {
425     clearDocumentWrapper();
426
427     ASSERT(m_document.IsEmpty());
428     m_document = v8::Persistent<v8::Object>::New(wrapper);
429 #ifndef NDEBUG
430     V8GCController::registerGlobalHandle(PROXY, this, m_document);
431 #endif
432 }
433
434 void V8DOMWindowShell::clearDocumentWrapper()
435 {
436     if (!m_document.IsEmpty()) {
437 #ifndef NDEBUG
438         V8GCController::unregisterGlobalHandle(this, m_document);
439 #endif
440         m_document.Dispose();
441         m_document.Clear();
442     }
443 }
444
445 static void checkDocumentWrapper(v8::Handle<v8::Object> wrapper, Document* document)
446 {
447     ASSERT(V8Document::toNative(wrapper) == document);
448     ASSERT(!document->isHTMLDocument() || (V8Document::toNative(v8::Handle<v8::Object>::Cast(wrapper->GetPrototype())) == document));
449 }
450
451 void V8DOMWindowShell::updateDocumentWrapperCache()
452 {
453     v8::HandleScope handleScope;
454     v8::Context::Scope contextScope(m_context);
455
456     // If the document has no frame, NodeToV8Object might get the
457     // document wrapper for a document that is about to be deleted.
458     // If the ForceSet below causes a garbage collection, the document
459     // might get deleted and the global handle for the document
460     // wrapper cleared. Using the cleared global handle will lead to
461     // crashes. In this case we clear the cache and let the DOMWindow
462     // accessor handle access to the document.
463     if (!m_frame->document()->frame()) {
464         clearDocumentWrapperCache();
465         return;
466     }
467
468     v8::Handle<v8::Value> documentWrapper = toV8(m_frame->document());
469     ASSERT(documentWrapper == m_document || m_document.IsEmpty());
470     if (m_document.IsEmpty())
471         updateDocumentWrapper(v8::Handle<v8::Object>::Cast(documentWrapper));
472     checkDocumentWrapper(m_document, m_frame->document());
473
474     // If instantiation of the document wrapper fails, clear the cache
475     // and let the DOMWindow accessor handle access to the document.
476     if (documentWrapper.IsEmpty()) {
477         clearDocumentWrapperCache();
478         return;
479     }
480     ASSERT(documentWrapper->IsObject());
481     m_context->Global()->ForceSet(v8::String::New("document"), documentWrapper, static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
482 }
483
484 void V8DOMWindowShell::clearDocumentWrapperCache()
485 {
486     ASSERT(!m_context.IsEmpty());
487     m_context->Global()->ForceDelete(v8::String::New("document"));
488 }
489
490 void V8DOMWindowShell::setSecurityToken()
491 {
492     Document* document = m_frame->document();
493     // Setup security origin and security token.
494     if (!document) {
495         m_context->UseDefaultSecurityToken();
496         return;
497     }
498
499     // Ask the document's SecurityOrigin to generate a security token.
500     // If two tokens are equal, then the SecurityOrigins canAccess each other.
501     // If two tokens are not equal, then we have to call canAccess.
502     // Note: we can't use the HTTPOrigin if it was set from the DOM.
503     SecurityOrigin* origin = document->securityOrigin();
504     String token;
505     if (!origin->domainWasSetInDOM())
506         token = document->securityOrigin()->toString();
507
508     // An empty or "null" token means we always have to call
509     // canAccess. The toString method on securityOrigins returns the
510     // string "null" for empty security origins and for security
511     // origins that should only allow access to themselves. In this
512     // case, we use the global object as the security token to avoid
513     // calling canAccess when a script accesses its own objects.
514     if (token.isEmpty() || token == "null") {
515         m_context->UseDefaultSecurityToken();
516         return;
517     }
518
519     CString utf8Token = token.utf8();
520     // NOTE: V8 does identity comparison in fast path, must use a symbol
521     // as the security token.
522     m_context->SetSecurityToken(v8::String::NewSymbol(utf8Token.data(), utf8Token.length()));
523 }
524
525 void V8DOMWindowShell::updateDocument()
526 {
527     if (!m_frame->document())
528         return;
529
530     if (m_global.IsEmpty())
531         return;
532
533     // There is an existing JavaScript wrapper for the global object
534     // of this frame. JavaScript code in other frames might hold a
535     // reference to this wrapper. We eagerly initialize the JavaScript
536     // context for the new document to make property access on the
537     // global object wrapper succeed.
538     if (!initContextIfNeeded())
539         return;
540
541     // We have a new document and we need to update the cache.
542     updateDocumentWrapperCache();
543
544     updateSecurityOrigin();
545 }
546
547 v8::Handle<v8::Value> getter(v8::Local<v8::String> property, const v8::AccessorInfo& info)
548 {
549     // FIXME(antonm): consider passing AtomicStringImpl directly.
550     AtomicString name = v8StringToAtomicWebCoreString(property);
551     HTMLDocument* htmlDocument = V8HTMLDocument::toNative(info.Holder());
552     ASSERT(htmlDocument);
553     v8::Handle<v8::Value> result = V8HTMLDocument::GetNamedProperty(htmlDocument, name);
554     if (!result.IsEmpty())
555         return result;
556     v8::Handle<v8::Value> prototype = info.Holder()->GetPrototype();
557     if (prototype->IsObject())
558         return prototype.As<v8::Object>()->Get(property);
559     return v8::Undefined();
560 }
561
562 void V8DOMWindowShell::namedItemAdded(HTMLDocument* doc, const AtomicString& name)
563 {
564     if (!initContextIfNeeded())
565         return;
566
567     v8::HandleScope handleScope;
568     v8::Context::Scope contextScope(m_context);
569
570     ASSERT(!m_document.IsEmpty());
571     checkDocumentWrapper(m_document, doc);
572     m_document->SetAccessor(v8String(name), getter);
573 }
574
575 void V8DOMWindowShell::namedItemRemoved(HTMLDocument* doc, const AtomicString& name)
576 {
577     if (doc->hasNamedItem(name.impl()) || doc->hasExtraNamedItem(name.impl()))
578         return;
579
580     if (!initContextIfNeeded())
581         return;
582
583     v8::HandleScope handleScope;
584     v8::Context::Scope contextScope(m_context);
585
586     ASSERT(!m_document.IsEmpty());
587     checkDocumentWrapper(m_document, doc);
588     m_document->Delete(v8String(name));
589 }
590
591 void V8DOMWindowShell::updateSecurityOrigin()
592 {
593     v8::HandleScope scope;
594     setSecurityToken();
595 }
596
597 } // WebCore