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