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