9c48a8cc3148614554cbb95ed24787842455fea7
[WebKit.git] / Source / WebCore / bindings / js / JSDOMWindowBase.cpp
1 /*
2  *  Copyright (C) 2000 Harri Porten (porten@kde.org)
3  *  Copyright (C) 2006 Jon Shier (jshier@iastate.edu)
4  *  Copyright (C) 2003-2017 Apple Inc. All rights reseved.
5  *  Copyright (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6  *  Copyright (c) 2015 Canon Inc. All rights reserved.
7  *
8  *  This library is free software; you can redistribute it and/or
9  *  modify it under the terms of the GNU Lesser General Public
10  *  License as published by the Free Software Foundation; either
11  *  version 2 of the License, or (at your option) any later version.
12  *
13  *  This library is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  *  Lesser General Public License for more details.
17  *
18  *  You should have received a copy of the GNU Lesser General Public
19  *  License along with this library; if not, write to the Free Software
20  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
21  *  USA
22  */
23
24 #include "config.h"
25 #include "JSDOMWindowBase.h"
26
27 #include "ActiveDOMCallbackMicrotask.h"
28 #include "Chrome.h"
29 #include "CommonVM.h"
30 #include "DOMWindow.h"
31 #include "Frame.h"
32 #include "InspectorController.h"
33 #include "JSDOMBindingSecurity.h"
34 #include "JSDOMGlobalObjectTask.h"
35 #include "JSDOMWindowCustom.h"
36 #include "JSMainThreadExecState.h"
37 #include "JSNode.h"
38 #include "Language.h"
39 #include "Logging.h"
40 #include "Page.h"
41 #include "RejectedPromiseTracker.h"
42 #include "RuntimeApplicationChecks.h"
43 #include "ScriptController.h"
44 #include "ScriptModuleLoader.h"
45 #include "SecurityOrigin.h"
46 #include "Settings.h"
47 #include "WebCoreJSClientData.h"
48 #include <bytecode/CodeBlock.h>
49 #include <heap/StrongInlines.h>
50 #include <runtime/JSInternalPromise.h>
51 #include <runtime/JSInternalPromiseDeferred.h>
52 #include <runtime/Microtask.h>
53 #include <wtf/MainThread.h>
54
55 #if PLATFORM(IOS)
56 #include "ChromeClient.h"
57 #endif
58
59 using namespace JSC;
60
61 namespace WebCore {
62
63 const ClassInfo JSDOMWindowBase::s_info = { "Window", &JSDOMGlobalObject::s_info, 0, CREATE_METHOD_TABLE(JSDOMWindowBase) };
64
65 const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = {
66     &supportsRichSourceInfo,
67     &shouldInterruptScript,
68     &javaScriptRuntimeFlags,
69     &queueTaskToEventLoop,
70     &shouldInterruptScriptBeforeTimeout,
71     &moduleLoaderImportModule,
72     &moduleLoaderResolve,
73     &moduleLoaderFetch,
74     nullptr, // moduleLoaderInstantiate
75     &moduleLoaderEvaluate,
76     &promiseRejectionTracker,
77     &defaultLanguage
78 };
79
80 JSDOMWindowBase::JSDOMWindowBase(VM& vm, Structure* structure, RefPtr<DOMWindow>&& window, JSDOMWindowShell* shell)
81     : JSDOMGlobalObject(vm, structure, shell->world(), &s_globalObjectMethodTable)
82     , m_windowCloseWatchpoints((window && window->frame()) ? IsWatched : IsInvalidated)
83     , m_wrapped(WTFMove(window))
84     , m_shell(shell)
85 {
86 }
87
88 void JSDOMWindowBase::finishCreation(VM& vm, JSDOMWindowShell* shell)
89 {
90     Base::finishCreation(vm, shell);
91     ASSERT(inherits(vm, info()));
92
93     GlobalPropertyInfo staticGlobals[] = {
94         GlobalPropertyInfo(vm.propertyNames->document, jsNull(), DontDelete | ReadOnly),
95         GlobalPropertyInfo(vm.propertyNames->window, m_shell, DontDelete | ReadOnly),
96     };
97
98     addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));
99
100     if (m_wrapped && m_wrapped->frame() && m_wrapped->frame()->settings().needsSiteSpecificQuirks())
101         setNeedsSiteSpecificQuirks(true);
102 }
103
104 void JSDOMWindowBase::visitChildren(JSCell* cell, SlotVisitor& visitor)
105 {
106     JSDOMWindowBase* thisObject = jsCast<JSDOMWindowBase*>(cell);
107     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
108     Base::visitChildren(thisObject, visitor);
109 }
110
111 void JSDOMWindowBase::destroy(JSCell* cell)
112 {
113     static_cast<JSDOMWindowBase*>(cell)->JSDOMWindowBase::~JSDOMWindowBase();
114 }
115
116 void JSDOMWindowBase::updateDocument()
117 {
118     // Since "document" property is defined as { configurable: false, writable: false, enumerable: true },
119     // users cannot change its attributes further.
120     // Reaching here, the attributes of "document" property should be never changed.
121     ASSERT(m_wrapped->document());
122     ExecState* exec = globalExec();
123     bool shouldThrowReadOnlyError = false;
124     bool ignoreReadOnlyErrors = true;
125     bool putResult = false;
126     symbolTablePutTouchWatchpointSet(this, exec, exec->vm().propertyNames->document, toJS(exec, this, m_wrapped->document()), shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
127 }
128
129 ScriptExecutionContext* JSDOMWindowBase::scriptExecutionContext() const
130 {
131     return m_wrapped->document();
132 }
133
134 void JSDOMWindowBase::printErrorMessage(const String& message) const
135 {
136     printErrorMessageForFrame(wrapped().frame(), message);
137 }
138
139 bool JSDOMWindowBase::supportsRichSourceInfo(const JSGlobalObject* object)
140 {
141     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
142     Frame* frame = thisObject->wrapped().frame();
143     if (!frame)
144         return false;
145
146     Page* page = frame->page();
147     if (!page)
148         return false;
149
150     bool enabled = page->inspectorController().enabled();
151     ASSERT(enabled || !thisObject->debugger());
152     return enabled;
153 }
154
155 static inline bool shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(Page* page)
156 {
157     // See <rdar://problem/5479443>. We don't think that page can ever be NULL
158     // in this case, but if it is, we've gotten into a state where we may have
159     // hung the UI, with no way to ask the client whether to cancel execution.
160     // For now, our solution is just to cancel execution no matter what,
161     // ensuring that we never hang. We might want to consider other solutions
162     // if we discover problems with this one.
163     ASSERT(page);
164     return !page;
165 }
166
167 bool JSDOMWindowBase::shouldInterruptScript(const JSGlobalObject* object)
168 {
169     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
170     ASSERT(thisObject->wrapped().frame());
171     Page* page = thisObject->wrapped().frame()->page();
172     return shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page);
173 }
174
175 bool JSDOMWindowBase::shouldInterruptScriptBeforeTimeout(const JSGlobalObject* object)
176 {
177     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
178     ASSERT(thisObject->wrapped().frame());
179     Page* page = thisObject->wrapped().frame()->page();
180
181     if (shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page))
182         return true;
183
184 #if PLATFORM(IOS)
185     if (page->chrome().client().isStopping())
186         return true;
187 #endif
188
189     return JSGlobalObject::shouldInterruptScriptBeforeTimeout(object);
190 }
191
192 RuntimeFlags JSDOMWindowBase::javaScriptRuntimeFlags(const JSGlobalObject* object)
193 {
194     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
195     Frame* frame = thisObject->wrapped().frame();
196     if (!frame)
197         return RuntimeFlags();
198     return frame->settings().javaScriptRuntimeFlags();
199 }
200
201 class JSDOMWindowMicrotaskCallback : public RefCounted<JSDOMWindowMicrotaskCallback> {
202 public:
203     static Ref<JSDOMWindowMicrotaskCallback> create(JSDOMWindowBase* globalObject, Ref<JSC::Microtask>&& task)
204     {
205         return adoptRef(*new JSDOMWindowMicrotaskCallback(globalObject, WTFMove(task)));
206     }
207
208     void call()
209     {
210         Ref<JSDOMWindowMicrotaskCallback> protectedThis(*this);
211         VM& vm = m_globalObject->vm();
212         JSLockHolder lock(vm);
213         auto scope = DECLARE_THROW_SCOPE(vm);
214
215         ExecState* exec = m_globalObject->globalExec();
216
217         JSMainThreadExecState::runTask(exec, m_task);
218
219         scope.assertNoException();
220     }
221
222 private:
223     JSDOMWindowMicrotaskCallback(JSDOMWindowBase* globalObject, Ref<JSC::Microtask>&& task)
224         : m_globalObject(globalObject->vm(), globalObject)
225         , m_task(WTFMove(task))
226     {
227     }
228
229     Strong<JSDOMWindowBase> m_globalObject;
230     Ref<JSC::Microtask> m_task;
231 };
232
233 void JSDOMWindowBase::queueTaskToEventLoop(const JSGlobalObject* object, Ref<JSC::Microtask>&& task)
234 {
235     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
236
237     RefPtr<JSDOMWindowMicrotaskCallback> callback = JSDOMWindowMicrotaskCallback::create((JSDOMWindowBase*)thisObject, WTFMove(task));
238     auto microtask = std::make_unique<ActiveDOMCallbackMicrotask>(MicrotaskQueue::mainThreadQueue(), *thisObject->scriptExecutionContext(), [callback]() mutable {
239         callback->call();
240     });
241
242     MicrotaskQueue::mainThreadQueue().append(WTFMove(microtask));
243 }
244
245 void JSDOMWindowBase::willRemoveFromWindowShell()
246 {
247     setCurrentEvent(0);
248 }
249
250 JSDOMWindowShell* JSDOMWindowBase::shell() const
251 {
252     return m_shell;
253 }
254
255 // JSDOMGlobalObject* is ignored, accessing a window in any context will
256 // use that DOMWindow's prototype chain.
257 JSValue toJS(ExecState* exec, JSDOMGlobalObject*, DOMWindow& domWindow)
258 {
259     return toJS(exec, domWindow);
260 }
261
262 JSValue toJS(ExecState* exec, DOMWindow& domWindow)
263 {
264     Frame* frame = domWindow.frame();
265     if (!frame)
266         return jsNull();
267     return frame->script().windowShell(currentWorld(exec));
268 }
269
270 JSDOMWindow* toJSDOMWindow(Frame* frame, DOMWrapperWorld& world)
271 {
272     if (!frame)
273         return 0;
274     return frame->script().windowShell(world)->window();
275 }
276
277 JSDOMWindow* toJSDOMWindow(JSC::VM& vm, JSValue value)
278 {
279     if (!value.isObject())
280         return 0;
281
282     while (!value.isNull()) {
283         JSObject* object = asObject(value);
284         const ClassInfo* classInfo = object->classInfo(vm);
285         if (classInfo == JSDOMWindow::info())
286             return jsCast<JSDOMWindow*>(object);
287         if (classInfo == JSDOMWindowShell::info())
288             return jsCast<JSDOMWindowShell*>(object)->window();
289         value = object->getPrototypeDirect();
290     }
291     return 0;
292 }
293
294 DOMWindow& incumbentDOMWindow(ExecState* exec)
295 {
296     class GetCallerGlobalObjectFunctor {
297     public:
298         GetCallerGlobalObjectFunctor() = default;
299
300         StackVisitor::Status operator()(StackVisitor& visitor) const
301         {
302             if (!m_hasSkippedFirstFrame) {
303                 m_hasSkippedFirstFrame = true;
304                 return StackVisitor::Continue;
305             }
306
307             if (auto* codeBlock = visitor->codeBlock())
308                 m_globalObject = codeBlock->globalObject();
309             else {
310                 ASSERT(visitor->callee().rawPtr());
311                 // FIXME: Callee is not an object if the caller is Web Assembly.
312                 // Figure out what to do here. We can probably get the global object
313                 // from the top-most Wasm Instance. https://bugs.webkit.org/show_bug.cgi?id=165721
314                 if (visitor->callee().isCell() && visitor->callee().asCell()->isObject())
315                     m_globalObject = jsCast<JSObject*>(visitor->callee().asCell())->globalObject();
316             }
317             return StackVisitor::Done;
318         }
319
320         JSGlobalObject* globalObject() const { return m_globalObject; }
321
322     private:
323         mutable bool m_hasSkippedFirstFrame { false };
324         mutable JSGlobalObject* m_globalObject { nullptr };
325     };
326
327     GetCallerGlobalObjectFunctor iter;
328     exec->iterate(iter);
329     return iter.globalObject() ? asJSDOMWindow(iter.globalObject())->wrapped() : firstDOMWindow(exec);
330 }
331
332 DOMWindow& activeDOMWindow(ExecState* exec)
333 {
334     return asJSDOMWindow(exec->lexicalGlobalObject())->wrapped();
335 }
336
337 DOMWindow& firstDOMWindow(ExecState* exec)
338 {
339     return asJSDOMWindow(exec->vmEntryGlobalObject())->wrapped();
340 }
341
342 void JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(DOMWindow* window)
343 {
344     JSC::VM& vm = commonVM();
345     JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData);
346     Vector<Ref<DOMWrapperWorld>> wrapperWorlds;
347     clientData->getAllWorlds(wrapperWorlds);
348     for (unsigned i = 0; i < wrapperWorlds.size(); ++i) {
349         DOMObjectWrapperMap& wrappers = wrapperWorlds[i]->m_wrappers;
350         auto result = wrappers.find(window);
351         if (result == wrappers.end())
352             continue;
353         JSC::JSObject* wrapper = result->value.get();
354         if (!wrapper)
355             continue;
356         JSDOMWindowBase* jsWindow = JSC::jsCast<JSDOMWindowBase*>(wrapper);
357         jsWindow->m_windowCloseWatchpoints.fireAll(vm, "Frame cleared");
358     }
359 }
360
361 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleName, JSC::JSValue importerModuleKey, JSC::JSValue scriptFetcher)
362 {
363     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
364     if (RefPtr<Document> document = thisObject->wrapped().document())
365         return document->moduleLoader()->resolve(globalObject, exec, moduleLoader, moduleName, importerModuleKey, scriptFetcher);
366     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
367     return deferred->reject(exec, jsUndefined());
368 }
369
370 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderFetch(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue scriptFetcher)
371 {
372     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
373     if (RefPtr<Document> document = thisObject->wrapped().document())
374         return document->moduleLoader()->fetch(globalObject, exec, moduleLoader, moduleKey, scriptFetcher);
375     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
376     return deferred->reject(exec, jsUndefined());
377 }
378
379 JSC::JSValue JSDOMWindowBase::moduleLoaderEvaluate(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue moduleRecord, JSC::JSValue scriptFetcher)
380 {
381     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
382     if (RefPtr<Document> document = thisObject->wrapped().document())
383         return document->moduleLoader()->evaluate(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
384     return JSC::jsUndefined();
385 }
386
387 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderImportModule(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, const JSC::SourceOrigin& sourceOrigin)
388 {
389     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
390     if (RefPtr<Document> document = thisObject->wrapped().document())
391         return document->moduleLoader()->importModule(globalObject, exec, moduleLoader, moduleName, sourceOrigin);
392     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
393     return deferred->reject(exec, jsUndefined());
394 }
395
396 void JSDOMWindowBase::promiseRejectionTracker(JSGlobalObject* jsGlobalObject, ExecState* exec, JSPromise* promise, JSPromiseRejectionOperation operation)
397 {
398     // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
399
400     VM& vm = exec->vm();
401     auto& globalObject = *JSC::jsCast<JSDOMWindowBase*>(jsGlobalObject);
402     auto* context = globalObject.scriptExecutionContext();
403     if (!context)
404         return;
405
406     // InternalPromises should not be exposed to user scripts.
407     if (JSC::jsDynamicCast<JSC::JSInternalPromise*>(vm, promise))
408         return;
409
410     // FIXME: If script has muted errors (cross origin), terminate these steps.
411     // <https://webkit.org/b/171415> Implement the `muted-errors` property of Scripts to avoid onerror/onunhandledrejection for cross-origin scripts
412
413     switch (operation) {
414     case JSPromiseRejectionOperation::Reject:
415         context->ensureRejectedPromiseTracker().promiseRejected(*exec, globalObject, *promise);
416         break;
417     case JSPromiseRejectionOperation::Handle:
418         context->ensureRejectedPromiseTracker().promiseHandled(*exec, globalObject, *promise);
419         break;
420     }
421 }
422
423 } // namespace WebCore