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.
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.
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.
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
25 #include "JSDOMWindowBase.h"
27 #include "ActiveDOMCallbackMicrotask.h"
30 #include "DOMWindow.h"
33 #include "InspectorController.h"
34 #include "JSDOMBindingSecurity.h"
35 #include "JSDOMGlobalObjectTask.h"
36 #include "JSDOMWindowCustom.h"
37 #include "JSMainThreadExecState.h"
41 #include "RejectedPromiseTracker.h"
42 #include "RuntimeApplicationChecks.h"
43 #include "ScriptController.h"
44 #include "ScriptModuleLoader.h"
45 #include "SecurityOrigin.h"
47 #include "WebCoreJSClientData.h"
48 #include <JavaScriptCore/CodeBlock.h>
49 #include <JavaScriptCore/JSInternalPromise.h>
50 #include <JavaScriptCore/JSInternalPromiseDeferred.h>
51 #include <JavaScriptCore/Microtask.h>
52 #include <JavaScriptCore/StrongInlines.h>
53 #include <wtf/Language.h>
54 #include <wtf/MainThread.h>
57 #include "ChromeClient.h"
64 const ClassInfo JSDOMWindowBase::s_info = { "Window", &JSDOMGlobalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMWindowBase) };
66 const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = {
67 &supportsRichSourceInfo,
68 &shouldInterruptScript,
69 &javaScriptRuntimeFlags,
70 &queueTaskToEventLoop,
71 &shouldInterruptScriptBeforeTimeout,
72 &moduleLoaderImportModule,
75 &moduleLoaderCreateImportMetaProperties,
76 &moduleLoaderEvaluate,
77 &promiseRejectionTracker,
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))
89 void JSDOMWindowBase::finishCreation(VM& vm, JSDOMWindowProxy* proxy)
91 Base::finishCreation(vm, proxy);
92 ASSERT(inherits(vm, info()));
94 auto& builtinNames = static_cast<JSVMClientData*>(vm.clientData)->builtinNames();
96 GlobalPropertyInfo staticGlobals[] = {
97 GlobalPropertyInfo(builtinNames.documentPublicName(), jsNull(), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
98 GlobalPropertyInfo(builtinNames.windowPublicName(), m_proxy, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
101 addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));
103 if (m_wrapped && m_wrapped->frame() && m_wrapped->frame()->settings().needsSiteSpecificQuirks())
104 setNeedsSiteSpecificQuirks(true);
107 void JSDOMWindowBase::destroy(JSCell* cell)
109 static_cast<JSDOMWindowBase*>(cell)->JSDOMWindowBase::~JSDOMWindowBase();
112 void JSDOMWindowBase::updateDocument()
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);
125 ScriptExecutionContext* JSDOMWindowBase::scriptExecutionContext() const
127 return m_wrapped->document();
130 void JSDOMWindowBase::printErrorMessage(const String& message) const
132 printErrorMessageForFrame(wrapped().frame(), message);
135 bool JSDOMWindowBase::supportsRichSourceInfo(const JSGlobalObject* object)
137 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
138 Frame* frame = thisObject->wrapped().frame();
142 Page* page = frame->page();
146 bool enabled = page->inspectorController().enabled();
147 ASSERT(enabled || !thisObject->debugger());
151 static inline bool shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(Page* page)
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.
163 bool JSDOMWindowBase::shouldInterruptScript(const JSGlobalObject* object)
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);
171 bool JSDOMWindowBase::shouldInterruptScriptBeforeTimeout(const JSGlobalObject* object)
173 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
174 ASSERT(thisObject->wrapped().frame());
175 Page* page = thisObject->wrapped().frame()->page();
177 if (shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page))
181 if (page->chrome().client().isStopping())
185 return JSGlobalObject::shouldInterruptScriptBeforeTimeout(object);
188 RuntimeFlags JSDOMWindowBase::javaScriptRuntimeFlags(const JSGlobalObject* object)
190 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
191 Frame* frame = thisObject->wrapped().frame();
193 return RuntimeFlags();
194 return frame->settings().javaScriptRuntimeFlags();
197 class JSDOMWindowMicrotaskCallback : public RefCounted<JSDOMWindowMicrotaskCallback> {
199 static Ref<JSDOMWindowMicrotaskCallback> create(JSDOMWindowBase& globalObject, Ref<JSC::Microtask>&& task)
201 return adoptRef(*new JSDOMWindowMicrotaskCallback(globalObject, WTFMove(task)));
206 Ref<JSDOMWindowMicrotaskCallback> protectedThis(*this);
207 VM& vm = m_globalObject->vm();
208 JSLockHolder lock(vm);
209 auto scope = DECLARE_THROW_SCOPE(vm);
211 ExecState* exec = m_globalObject->globalExec();
213 JSMainThreadExecState::runTask(exec, m_task);
215 scope.assertNoException();
219 JSDOMWindowMicrotaskCallback(JSDOMWindowBase& globalObject, Ref<JSC::Microtask>&& task)
220 : m_globalObject { globalObject.vm(), &globalObject }
221 , m_task { WTFMove(task) }
225 Strong<JSDOMWindowBase> m_globalObject;
226 Ref<JSC::Microtask> m_task;
229 void JSDOMWindowBase::queueTaskToEventLoop(JSGlobalObject& object, Ref<JSC::Microtask>&& task)
231 JSDOMWindowBase& thisObject = static_cast<JSDOMWindowBase&>(object);
233 RefPtr<JSDOMWindowMicrotaskCallback> callback = JSDOMWindowMicrotaskCallback::create(thisObject, WTFMove(task));
234 auto microtask = std::make_unique<ActiveDOMCallbackMicrotask>(MicrotaskQueue::mainThreadQueue(), *thisObject.scriptExecutionContext(), [callback]() mutable {
238 MicrotaskQueue::mainThreadQueue().append(WTFMove(microtask));
241 void JSDOMWindowBase::willRemoveFromWindowProxy()
246 JSDOMWindowProxy* JSDOMWindowBase::proxy() const
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)
255 return toJS(state, domWindow);
258 JSValue toJS(JSC::ExecState* state, JSDOMGlobalObject*, Frame& frame)
260 return toJS(state, frame);
263 JSValue toJS(ExecState* state, DOMWindow& domWindow)
265 return toJS(state, domWindow.frame());
268 JSDOMWindow* toJSDOMWindow(Frame& frame, DOMWrapperWorld& world)
270 return frame.script().windowProxy(world)->window();
273 JSDOMWindow* toJSDOMWindow(JSC::VM& vm, JSValue value)
275 if (!value.isObject())
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(vm);
290 DOMWindow& incumbentDOMWindow(ExecState& state)
292 return asJSDOMWindow(&callerGlobalObject(state))->wrapped();
295 DOMWindow& activeDOMWindow(ExecState& state)
297 return asJSDOMWindow(state.lexicalGlobalObject())->wrapped();
300 DOMWindow& firstDOMWindow(ExecState& state)
302 return asJSDOMWindow(state.vmEntryGlobalObject())->wrapped();
305 Document* responsibleDocument(ExecState& state)
307 CallerFunctor functor;
308 state.iterate(functor);
309 auto* callerFrame = functor.callerFrame();
312 return asJSDOMWindow(callerFrame->lexicalGlobalObject())->wrapped().document();
315 void JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(DOMWindow* window)
317 JSC::VM& vm = commonVM();
318 JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData);
319 Vector<Ref<DOMWrapperWorld>> wrapperWorlds;
320 clientData->getAllWorlds(wrapperWorlds);
321 for (unsigned i = 0; i < wrapperWorlds.size(); ++i) {
322 auto& wrappers = wrapperWorlds[i]->wrappers();
323 auto result = wrappers.find(window);
324 if (result == wrappers.end())
326 JSC::JSObject* wrapper = result->value.get();
329 JSDOMWindowBase* jsWindow = JSC::jsCast<JSDOMWindowBase*>(wrapper);
330 jsWindow->m_windowCloseWatchpoints.fireAll(vm, "Frame cleared");
334 JSC::Identifier JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleName, JSC::JSValue importerModuleKey, JSC::JSValue scriptFetcher)
336 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
337 if (RefPtr<Document> document = thisObject->wrapped().document())
338 return document->moduleLoader()->resolve(globalObject, exec, moduleLoader, moduleName, importerModuleKey, scriptFetcher);
342 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderFetch(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue parameters, JSC::JSValue scriptFetcher)
344 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
345 if (RefPtr<Document> document = thisObject->wrapped().document())
346 return document->moduleLoader()->fetch(globalObject, exec, moduleLoader, moduleKey, parameters, scriptFetcher);
347 JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
348 return deferred->reject(exec, jsUndefined());
351 JSC::JSValue JSDOMWindowBase::moduleLoaderEvaluate(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue moduleRecord, JSC::JSValue scriptFetcher)
353 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
354 if (RefPtr<Document> document = thisObject->wrapped().document())
355 return document->moduleLoader()->evaluate(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
356 return JSC::jsUndefined();
359 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderImportModule(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin)
361 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
362 if (RefPtr<Document> document = thisObject->wrapped().document())
363 return document->moduleLoader()->importModule(globalObject, exec, moduleLoader, moduleName, parameters, sourceOrigin);
364 JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::create(exec, globalObject);
365 return deferred->reject(exec, jsUndefined());
368 JSC::JSObject* JSDOMWindowBase::moduleLoaderCreateImportMetaProperties(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSModuleRecord* moduleRecord, JSC::JSValue scriptFetcher)
370 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
371 if (RefPtr<Document> document = thisObject->wrapped().document())
372 return document->moduleLoader()->createImportMetaProperties(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
373 return constructEmptyObject(exec, globalObject->nullPrototypeObjectStructure());
376 void JSDOMWindowBase::promiseRejectionTracker(JSGlobalObject* jsGlobalObject, ExecState* exec, JSPromise* promise, JSPromiseRejectionOperation operation)
378 // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
381 auto& globalObject = *JSC::jsCast<JSDOMWindowBase*>(jsGlobalObject);
382 auto* context = globalObject.scriptExecutionContext();
386 // InternalPromises should not be exposed to user scripts.
387 if (JSC::jsDynamicCast<JSC::JSInternalPromise*>(vm, promise))
390 // FIXME: If script has muted errors (cross origin), terminate these steps.
391 // <https://webkit.org/b/171415> Implement the `muted-errors` property of Scripts to avoid onerror/onunhandledrejection for cross-origin scripts
394 case JSPromiseRejectionOperation::Reject:
395 context->ensureRejectedPromiseTracker().promiseRejected(*exec, globalObject, *promise);
397 case JSPromiseRejectionOperation::Handle:
398 context->ensureRejectedPromiseTracker().promiseHandled(*exec, globalObject, *promise);
403 } // namespace WebCore