8aeaa843714f215b84b155f70dfdc0f95ed5b910
[WebKit-https.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, nullptr, nullptr, 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, JSDOMWindowProxy* proxy)
81     : JSDOMGlobalObject(vm, structure, proxy->world(), &s_globalObjectMethodTable)
82     , m_windowCloseWatchpoints((window && window->frame()) ? IsWatched : IsInvalidated)
83     , m_wrapped(WTFMove(window))
84     , m_proxy(proxy)
85 {
86 }
87
88 void JSDOMWindowBase::finishCreation(VM& vm, JSDOMWindowProxy* proxy)
89 {
90     Base::finishCreation(vm, proxy);
91     ASSERT(inherits(vm, info()));
92
93     GlobalPropertyInfo staticGlobals[] = {
94         GlobalPropertyInfo(vm.propertyNames->document, jsNull(), DontDelete | ReadOnly),
95         GlobalPropertyInfo(vm.propertyNames->window, m_proxy, 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::destroy(JSCell* cell)
105 {
106     static_cast<JSDOMWindowBase*>(cell)->JSDOMWindowBase::~JSDOMWindowBase();
107 }
108
109 void JSDOMWindowBase::updateDocument()
110 {
111     // Since "document" property is defined as { configurable: false, writable: false, enumerable: true },
112     // users cannot change its attributes further.
113     // Reaching here, the attributes of "document" property should be never changed.
114     ASSERT(m_wrapped->document());
115     ExecState* exec = globalExec();
116     bool shouldThrowReadOnlyError = false;
117     bool ignoreReadOnlyErrors = true;
118     bool putResult = false;
119     symbolTablePutTouchWatchpointSet(this, exec, exec->vm().propertyNames->document, toJS(exec, this, m_wrapped->document()), shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
120 }
121
122 ScriptExecutionContext* JSDOMWindowBase::scriptExecutionContext() const
123 {
124     return m_wrapped->document();
125 }
126
127 void JSDOMWindowBase::printErrorMessage(const String& message) const
128 {
129     printErrorMessageForFrame(wrapped().frame(), message);
130 }
131
132 bool JSDOMWindowBase::supportsRichSourceInfo(const JSGlobalObject* object)
133 {
134     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
135     Frame* frame = thisObject->wrapped().frame();
136     if (!frame)
137         return false;
138
139     Page* page = frame->page();
140     if (!page)
141         return false;
142
143     bool enabled = page->inspectorController().enabled();
144     ASSERT(enabled || !thisObject->debugger());
145     return enabled;
146 }
147
148 static inline bool shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(Page* page)
149 {
150     // See <rdar://problem/5479443>. We don't think that page can ever be NULL
151     // in this case, but if it is, we've gotten into a state where we may have
152     // hung the UI, with no way to ask the client whether to cancel execution.
153     // For now, our solution is just to cancel execution no matter what,
154     // ensuring that we never hang. We might want to consider other solutions
155     // if we discover problems with this one.
156     ASSERT(page);
157     return !page;
158 }
159
160 bool JSDOMWindowBase::shouldInterruptScript(const JSGlobalObject* object)
161 {
162     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
163     ASSERT(thisObject->wrapped().frame());
164     Page* page = thisObject->wrapped().frame()->page();
165     return shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page);
166 }
167
168 bool JSDOMWindowBase::shouldInterruptScriptBeforeTimeout(const JSGlobalObject* object)
169 {
170     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
171     ASSERT(thisObject->wrapped().frame());
172     Page* page = thisObject->wrapped().frame()->page();
173
174     if (shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page))
175         return true;
176
177 #if PLATFORM(IOS)
178     if (page->chrome().client().isStopping())
179         return true;
180 #endif
181
182     return JSGlobalObject::shouldInterruptScriptBeforeTimeout(object);
183 }
184
185 RuntimeFlags JSDOMWindowBase::javaScriptRuntimeFlags(const JSGlobalObject* object)
186 {
187     const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
188     Frame* frame = thisObject->wrapped().frame();
189     if (!frame)
190         return RuntimeFlags();
191     return frame->settings().javaScriptRuntimeFlags();
192 }
193
194 class JSDOMWindowMicrotaskCallback : public RefCounted<JSDOMWindowMicrotaskCallback> {
195 public:
196     static Ref<JSDOMWindowMicrotaskCallback> create(JSDOMWindowBase& globalObject, Ref<JSC::Microtask>&& task)
197     {
198         return adoptRef(*new JSDOMWindowMicrotaskCallback(globalObject, WTFMove(task)));
199     }
200
201     void call()
202     {
203         Ref<JSDOMWindowMicrotaskCallback> protectedThis(*this);
204         VM& vm = m_globalObject->vm();
205         JSLockHolder lock(vm);
206         auto scope = DECLARE_THROW_SCOPE(vm);
207
208         ExecState* exec = m_globalObject->globalExec();
209
210         JSMainThreadExecState::runTask(exec, m_task);
211
212         scope.assertNoException();
213     }
214
215 private:
216     JSDOMWindowMicrotaskCallback(JSDOMWindowBase& globalObject, Ref<JSC::Microtask>&& task)
217         : m_globalObject { globalObject.vm(), &globalObject }
218         , m_task { WTFMove(task) }
219     {
220     }
221
222     Strong<JSDOMWindowBase> m_globalObject;
223     Ref<JSC::Microtask> m_task;
224 };
225
226 void JSDOMWindowBase::queueTaskToEventLoop(JSGlobalObject& object, Ref<JSC::Microtask>&& task)
227 {
228     JSDOMWindowBase& thisObject = static_cast<JSDOMWindowBase&>(object);
229
230     RefPtr<JSDOMWindowMicrotaskCallback> callback = JSDOMWindowMicrotaskCallback::create(thisObject, WTFMove(task));
231     auto microtask = std::make_unique<ActiveDOMCallbackMicrotask>(MicrotaskQueue::mainThreadQueue(), *thisObject.scriptExecutionContext(), [callback]() mutable {
232         callback->call();
233     });
234
235     MicrotaskQueue::mainThreadQueue().append(WTFMove(microtask));
236 }
237
238 void JSDOMWindowBase::willRemoveFromWindowProxy()
239 {
240     setCurrentEvent(0);
241 }
242
243 JSDOMWindowProxy* JSDOMWindowBase::proxy() const
244 {
245     return m_proxy;
246 }
247
248 // JSDOMGlobalObject* is ignored, accessing a window in any context will
249 // use that DOMWindow's prototype chain.
250 JSValue toJS(ExecState* state, JSDOMGlobalObject*, DOMWindow& domWindow)
251 {
252     return toJS(state, domWindow);
253 }
254
255 JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject*, Frame& frame)
256 {
257     return toJS(state, frame);
258 }
259
260 JSValue toJS(ExecState* state, DOMWindow& domWindow)
261 {
262     return toJS(state, domWindow.frame());
263 }
264
265 JSDOMWindow* toJSDOMWindow(Frame& frame, DOMWrapperWorld& world)
266 {
267     return frame.script().windowProxy(world)->window();
268 }
269
270 JSDOMWindow* toJSDOMWindow(JSC::VM& vm, JSValue value)
271 {
272     if (!value.isObject())
273         return nullptr;
274
275     while (!value.isNull()) {
276         JSObject* object = asObject(value);
277         const ClassInfo* classInfo = object->classInfo(vm);
278         if (classInfo == JSDOMWindow::info())
279             return jsCast<JSDOMWindow*>(object);
280         if (classInfo == JSDOMWindowProxy::info())
281             return jsCast<JSDOMWindowProxy*>(object)->window();
282         value = object->getPrototypeDirect();
283     }
284     return nullptr;
285 }
286
287 DOMWindow& incumbentDOMWindow(ExecState& state)
288 {
289     class GetCallerGlobalObjectFunctor {
290     public:
291         GetCallerGlobalObjectFunctor() = default;
292
293         StackVisitor::Status operator()(StackVisitor& visitor) const
294         {
295             if (!m_hasSkippedFirstFrame) {
296                 m_hasSkippedFirstFrame = true;
297                 return StackVisitor::Continue;
298             }
299
300             if (auto* codeBlock = visitor->codeBlock())
301                 m_globalObject = codeBlock->globalObject();
302             else {
303                 ASSERT(visitor->callee().rawPtr());
304                 // FIXME: Callee is not an object if the caller is Web Assembly.
305                 // Figure out what to do here. We can probably get the global object
306                 // from the top-most Wasm Instance. https://bugs.webkit.org/show_bug.cgi?id=165721
307                 if (visitor->callee().isCell() && visitor->callee().asCell()->isObject())
308                     m_globalObject = jsCast<JSObject*>(visitor->callee().asCell())->globalObject();
309             }
310             return StackVisitor::Done;
311         }
312
313         JSGlobalObject* globalObject() const { return m_globalObject; }
314
315     private:
316         mutable bool m_hasSkippedFirstFrame { false };
317         mutable JSGlobalObject* m_globalObject { nullptr };
318     };
319
320     GetCallerGlobalObjectFunctor iter;
321     state.iterate(iter);
322     return iter.globalObject() ? asJSDOMWindow(iter.globalObject())->wrapped() : firstDOMWindow(state);
323 }
324
325 DOMWindow& activeDOMWindow(ExecState& state)
326 {
327     return asJSDOMWindow(state.lexicalGlobalObject())->wrapped();
328 }
329
330 DOMWindow& firstDOMWindow(ExecState& state)
331 {
332     return asJSDOMWindow(state.vmEntryGlobalObject())->wrapped();
333 }
334
335 Document* responsibleDocument(ExecState& state)
336 {
337     CallerFunctor functor;
338     state.iterate(functor);
339     auto* callerFrame = functor.callerFrame();
340     if (!callerFrame)
341         return nullptr;
342     return asJSDOMWindow(callerFrame->lexicalGlobalObject())->wrapped().document();
343 }
344
345 void JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(DOMWindow* window)
346 {
347     JSC::VM& vm = commonVM();
348     JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData);
349     Vector<Ref<DOMWrapperWorld>> wrapperWorlds;
350     clientData->getAllWorlds(wrapperWorlds);
351     for (unsigned i = 0; i < wrapperWorlds.size(); ++i) {
352         DOMObjectWrapperMap& wrappers = wrapperWorlds[i]->m_wrappers;
353         auto result = wrappers.find(window);
354         if (result == wrappers.end())
355             continue;
356         JSC::JSObject* wrapper = result->value.get();
357         if (!wrapper)
358             continue;
359         JSDOMWindowBase* jsWindow = JSC::jsCast<JSDOMWindowBase*>(wrapper);
360         jsWindow->m_windowCloseWatchpoints.fireAll(vm, "Frame cleared");
361     }
362 }
363
364 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleName, JSC::JSValue importerModuleKey, JSC::JSValue scriptFetcher)
365 {
366     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
367     if (RefPtr<Document> document = thisObject->wrapped().document())
368         return document->moduleLoader()->resolve(globalObject, exec, moduleLoader, moduleName, importerModuleKey, scriptFetcher);
369     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
370     return deferred->reject(exec, jsUndefined());
371 }
372
373 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderFetch(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue scriptFetcher)
374 {
375     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
376     if (RefPtr<Document> document = thisObject->wrapped().document())
377         return document->moduleLoader()->fetch(globalObject, exec, moduleLoader, moduleKey, scriptFetcher);
378     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
379     return deferred->reject(exec, jsUndefined());
380 }
381
382 JSC::JSValue JSDOMWindowBase::moduleLoaderEvaluate(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue moduleRecord, JSC::JSValue scriptFetcher)
383 {
384     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
385     if (RefPtr<Document> document = thisObject->wrapped().document())
386         return document->moduleLoader()->evaluate(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
387     return JSC::jsUndefined();
388 }
389
390 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderImportModule(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, const JSC::SourceOrigin& sourceOrigin)
391 {
392     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
393     if (RefPtr<Document> document = thisObject->wrapped().document())
394         return document->moduleLoader()->importModule(globalObject, exec, moduleLoader, moduleName, sourceOrigin);
395     JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
396     return deferred->reject(exec, jsUndefined());
397 }
398
399 void JSDOMWindowBase::promiseRejectionTracker(JSGlobalObject* jsGlobalObject, ExecState* exec, JSPromise* promise, JSPromiseRejectionOperation operation)
400 {
401     // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
402
403     VM& vm = exec->vm();
404     auto& globalObject = *JSC::jsCast<JSDOMWindowBase*>(jsGlobalObject);
405     auto* context = globalObject.scriptExecutionContext();
406     if (!context)
407         return;
408
409     // InternalPromises should not be exposed to user scripts.
410     if (JSC::jsDynamicCast<JSC::JSInternalPromise*>(vm, promise))
411         return;
412
413     // FIXME: If script has muted errors (cross origin), terminate these steps.
414     // <https://webkit.org/b/171415> Implement the `muted-errors` property of Scripts to avoid onerror/onunhandledrejection for cross-origin scripts
415
416     switch (operation) {
417     case JSPromiseRejectionOperation::Reject:
418         context->ensureRejectedPromiseTracker().promiseRejected(*exec, globalObject, *promise);
419         break;
420     case JSPromiseRejectionOperation::Handle:
421         context->ensureRejectedPromiseTracker().promiseHandled(*exec, globalObject, *promise);
422         break;
423     }
424 }
425
426 } // namespace WebCore