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 "FetchResponse.h"
33#include "Frame.h"
34#include "InspectorController.h"
35#include "JSDOMBindingSecurity.h"
36#include "JSDOMGlobalObjectTask.h"
37#include "JSDOMWindowCustom.h"
38#include "JSFetchResponse.h"
39#include "JSMicrotaskCallback.h"
40#include "JSNode.h"
41#include "Logging.h"
42#include "Page.h"
43#include "RejectedPromiseTracker.h"
44#include "RuntimeApplicationChecks.h"
45#include "ScriptController.h"
46#include "ScriptModuleLoader.h"
47#include "SecurityOrigin.h"
48#include "Settings.h"
49#include "WebCoreJSClientData.h"
50#include <JavaScriptCore/CodeBlock.h>
51#include <JavaScriptCore/JSInternalPromise.h>
52#include <JavaScriptCore/JSInternalPromiseDeferred.h>
53#include <JavaScriptCore/Microtask.h>
54#include <JavaScriptCore/PromiseDeferredTimer.h>
55#include <JavaScriptCore/StrongInlines.h>
56#include <JavaScriptCore/WebAssemblyPrototype.h>
57#include <wtf/Language.h>
58#include <wtf/MainThread.h>
59
60#if PLATFORM(IOS_FAMILY)
61#include "ChromeClient.h"
62#endif
63
64
65namespace WebCore {
66using namespace JSC;
67
68const ClassInfo JSDOMWindowBase::s_info = { "Window", &JSDOMGlobalObject::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSDOMWindowBase) };
69
70const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = {
71 &supportsRichSourceInfo,
72 &shouldInterruptScript,
73 &javaScriptRuntimeFlags,
74 &queueTaskToEventLoop,
75 &shouldInterruptScriptBeforeTimeout,
76 &moduleLoaderImportModule,
77 &moduleLoaderResolve,
78 &moduleLoaderFetch,
79 &moduleLoaderCreateImportMetaProperties,
80 &moduleLoaderEvaluate,
81 &promiseRejectionTracker,
82 &defaultLanguage,
83#if ENABLE(WEBASSEMBLY)
84 &compileStreaming,
85 &instantiateStreaming,
86#else
87 nullptr,
88 nullptr,
89#endif
90};
91
92JSDOMWindowBase::JSDOMWindowBase(VM& vm, Structure* structure, RefPtr<DOMWindow>&& window, JSWindowProxy* proxy)
93 : JSDOMGlobalObject(vm, structure, proxy->world(), &s_globalObjectMethodTable)
94 , m_windowCloseWatchpoints((window && window->frame()) ? IsWatched : IsInvalidated)
95 , m_wrapped(WTFMove(window))
96 , m_proxy(proxy)
97{
98}
99
100void JSDOMWindowBase::finishCreation(VM& vm, JSWindowProxy* proxy)
101{
102 Base::finishCreation(vm, proxy);
103 ASSERT(inherits(vm, info()));
104
105 auto& builtinNames = static_cast<JSVMClientData*>(vm.clientData)->builtinNames();
106
107 GlobalPropertyInfo staticGlobals[] = {
108 GlobalPropertyInfo(builtinNames.documentPublicName(), jsNull(), PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
109 GlobalPropertyInfo(builtinNames.windowPublicName(), m_proxy, PropertyAttribute::DontDelete | PropertyAttribute::ReadOnly),
110 };
111
112 addStaticGlobals(staticGlobals, WTF_ARRAY_LENGTH(staticGlobals));
113
114 if (m_wrapped && m_wrapped->frame() && m_wrapped->frame()->settings().needsSiteSpecificQuirks())
115 setNeedsSiteSpecificQuirks(true);
116}
117
118void JSDOMWindowBase::destroy(JSCell* cell)
119{
120 static_cast<JSDOMWindowBase*>(cell)->JSDOMWindowBase::~JSDOMWindowBase();
121}
122
123void JSDOMWindowBase::updateDocument()
124{
125 // Since "document" property is defined as { configurable: false, writable: false, enumerable: true },
126 // users cannot change its attributes further.
127 // Reaching here, the attributes of "document" property should be never changed.
128 ASSERT(m_wrapped->document());
129 ExecState* exec = globalExec();
130 bool shouldThrowReadOnlyError = false;
131 bool ignoreReadOnlyErrors = true;
132 bool putResult = false;
133 symbolTablePutTouchWatchpointSet(this, exec, static_cast<JSVMClientData*>(exec->vm().clientData)->builtinNames().documentPublicName(), toJS(exec, this, m_wrapped->document()), shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
134}
135
136ScriptExecutionContext* JSDOMWindowBase::scriptExecutionContext() const
137{
138 return m_wrapped->document();
139}
140
141void JSDOMWindowBase::printErrorMessage(const String& message) const
142{
143 printErrorMessageForFrame(wrapped().frame(), message);
144}
145
146bool JSDOMWindowBase::supportsRichSourceInfo(const JSGlobalObject* object)
147{
148 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
149 Frame* frame = thisObject->wrapped().frame();
150 if (!frame)
151 return false;
152
153 Page* page = frame->page();
154 if (!page)
155 return false;
156
157 bool enabled = page->inspectorController().enabled();
158 ASSERT(enabled || !thisObject->debugger());
159 return enabled;
160}
161
162static inline bool shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(Page* page)
163{
164 // See <rdar://problem/5479443>. We don't think that page can ever be NULL
165 // in this case, but if it is, we've gotten into a state where we may have
166 // hung the UI, with no way to ask the client whether to cancel execution.
167 // For now, our solution is just to cancel execution no matter what,
168 // ensuring that we never hang. We might want to consider other solutions
169 // if we discover problems with this one.
170 ASSERT(page);
171 return !page;
172}
173
174bool JSDOMWindowBase::shouldInterruptScript(const JSGlobalObject* object)
175{
176 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
177 ASSERT(thisObject->wrapped().frame());
178 Page* page = thisObject->wrapped().frame()->page();
179 return shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page);
180}
181
182bool JSDOMWindowBase::shouldInterruptScriptBeforeTimeout(const JSGlobalObject* object)
183{
184 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
185 ASSERT(thisObject->wrapped().frame());
186 Page* page = thisObject->wrapped().frame()->page();
187
188 if (shouldInterruptScriptToPreventInfiniteRecursionWhenClosingPage(page))
189 return true;
190
191#if PLATFORM(IOS_FAMILY)
192 if (page->chrome().client().isStopping())
193 return true;
194#endif
195
196 return JSGlobalObject::shouldInterruptScriptBeforeTimeout(object);
197}
198
199RuntimeFlags JSDOMWindowBase::javaScriptRuntimeFlags(const JSGlobalObject* object)
200{
201 const JSDOMWindowBase* thisObject = static_cast<const JSDOMWindowBase*>(object);
202 Frame* frame = thisObject->wrapped().frame();
203 if (!frame)
204 return RuntimeFlags();
205 return frame->settings().javaScriptRuntimeFlags();
206}
207
208void JSDOMWindowBase::queueTaskToEventLoop(JSGlobalObject& object, Ref<JSC::Microtask>&& task)
209{
210 JSDOMWindowBase& thisObject = static_cast<JSDOMWindowBase&>(object);
211
212 auto callback = JSMicrotaskCallback::create(thisObject, WTFMove(task));
213 auto microtask = std::make_unique<ActiveDOMCallbackMicrotask>(MicrotaskQueue::mainThreadQueue(), *thisObject.scriptExecutionContext(), [callback = WTFMove(callback)]() mutable {
214 callback->call();
215 });
216
217 MicrotaskQueue::mainThreadQueue().append(WTFMove(microtask));
218}
219
220void JSDOMWindowBase::willRemoveFromWindowProxy()
221{
222 setCurrentEvent(0);
223}
224
225JSWindowProxy* JSDOMWindowBase::proxy() const
226{
227 return m_proxy;
228}
229
230JSValue toJS(ExecState* state, DOMWindow& domWindow)
231{
232 auto* frame = domWindow.frame();
233 if (!frame)
234 return jsNull();
235 return toJS(state, frame->windowProxy());
236}
237
238JSDOMWindow* toJSDOMWindow(Frame& frame, DOMWrapperWorld& world)
239{
240 return frame.script().globalObject(world);
241}
242
243JSDOMWindow* toJSDOMWindow(JSC::VM& vm, JSValue value)
244{
245 if (!value.isObject())
246 return nullptr;
247
248 while (!value.isNull()) {
249 JSObject* object = asObject(value);
250 const ClassInfo* classInfo = object->classInfo(vm);
251 if (classInfo == JSDOMWindow::info())
252 return jsCast<JSDOMWindow*>(object);
253 if (classInfo == JSWindowProxy::info())
254 return jsDynamicCast<JSDOMWindow*>(vm, jsCast<JSWindowProxy*>(object)->window());
255 value = object->getPrototypeDirect(vm);
256 }
257 return nullptr;
258}
259
260DOMWindow& incumbentDOMWindow(ExecState& state)
261{
262 return asJSDOMWindow(&callerGlobalObject(state))->wrapped();
263}
264
265DOMWindow& activeDOMWindow(ExecState& state)
266{
267 return asJSDOMWindow(state.lexicalGlobalObject())->wrapped();
268}
269
270DOMWindow& firstDOMWindow(ExecState& state)
271{
272 VM& vm = state.vm();
273 return asJSDOMWindow(vm.vmEntryGlobalObject(&state))->wrapped();
274}
275
276Document* responsibleDocument(ExecState& state)
277{
278 CallerFunctor functor;
279 state.iterate(functor);
280 auto* callerFrame = functor.callerFrame();
281 if (!callerFrame)
282 return nullptr;
283 return asJSDOMWindow(callerFrame->lexicalGlobalObject())->wrapped().document();
284}
285
286void JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(DOMWindow* window)
287{
288 JSC::VM& vm = commonVM();
289 JSVMClientData* clientData = static_cast<JSVMClientData*>(vm.clientData);
290 Vector<Ref<DOMWrapperWorld>> wrapperWorlds;
291 clientData->getAllWorlds(wrapperWorlds);
292 for (unsigned i = 0; i < wrapperWorlds.size(); ++i) {
293 auto& wrappers = wrapperWorlds[i]->wrappers();
294 auto result = wrappers.find(window);
295 if (result == wrappers.end())
296 continue;
297 JSC::JSObject* wrapper = result->value.get();
298 if (!wrapper)
299 continue;
300 JSDOMWindowBase* jsWindow = JSC::jsCast<JSDOMWindowBase*>(wrapper);
301 jsWindow->m_windowCloseWatchpoints.fireAll(vm, "Frame cleared");
302 }
303}
304
305JSC::Identifier JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleName, JSC::JSValue importerModuleKey, JSC::JSValue scriptFetcher)
306{
307 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
308 if (RefPtr<Document> document = thisObject->wrapped().document())
309 return document->moduleLoader().resolve(globalObject, exec, moduleLoader, moduleName, importerModuleKey, scriptFetcher);
310 return { };
311}
312
313JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderFetch(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue parameters, JSC::JSValue scriptFetcher)
314{
315 VM& vm = exec->vm();
316 auto scope = DECLARE_THROW_SCOPE(vm);
317 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
318 if (RefPtr<Document> document = thisObject->wrapped().document())
319 RELEASE_AND_RETURN(scope, document->moduleLoader().fetch(globalObject, exec, moduleLoader, moduleKey, parameters, scriptFetcher));
320 JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::tryCreate(exec, globalObject);
321 RETURN_IF_EXCEPTION(scope, nullptr);
322 RELEASE_AND_RETURN(scope, deferred->reject(exec, jsUndefined()));
323}
324
325JSC::JSValue JSDOMWindowBase::moduleLoaderEvaluate(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSValue moduleRecord, JSC::JSValue scriptFetcher)
326{
327 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
328 if (RefPtr<Document> document = thisObject->wrapped().document())
329 return document->moduleLoader().evaluate(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
330 return JSC::jsUndefined();
331}
332
333JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderImportModule(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSString* moduleName, JSC::JSValue parameters, const JSC::SourceOrigin& sourceOrigin)
334{
335 VM& vm = exec->vm();
336 auto scope = DECLARE_THROW_SCOPE(vm);
337 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
338 if (RefPtr<Document> document = thisObject->wrapped().document())
339 RELEASE_AND_RETURN(scope, document->moduleLoader().importModule(globalObject, exec, moduleLoader, moduleName, parameters, sourceOrigin));
340 JSC::JSInternalPromiseDeferred* deferred = JSC::JSInternalPromiseDeferred::tryCreate(exec, globalObject);
341 RETURN_IF_EXCEPTION(scope, nullptr);
342 RELEASE_AND_RETURN(scope, deferred->reject(exec, jsUndefined()));
343}
344
345JSC::JSObject* JSDOMWindowBase::moduleLoaderCreateImportMetaProperties(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleKey, JSC::JSModuleRecord* moduleRecord, JSC::JSValue scriptFetcher)
346{
347 JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
348 if (RefPtr<Document> document = thisObject->wrapped().document())
349 return document->moduleLoader().createImportMetaProperties(globalObject, exec, moduleLoader, moduleKey, moduleRecord, scriptFetcher);
350 return constructEmptyObject(exec, globalObject->nullPrototypeObjectStructure());
351}
352
353#if ENABLE(WEBASSEMBLY)
354static Optional<Vector<uint8_t>> tryAllocate(JSC::ExecState* exec, JSC::JSPromiseDeferred* promise, const char* data, size_t byteSize)
355{
356 Vector<uint8_t> arrayBuffer;
357 if (!arrayBuffer.tryReserveCapacity(byteSize)) {
358 promise->reject(exec, createOutOfMemoryError(exec));
359 return WTF::nullopt;
360 }
361
362 arrayBuffer.grow(byteSize);
363 memcpy(arrayBuffer.data(), data, byteSize);
364
365 return arrayBuffer;
366}
367
368static bool isResponseCorrect(JSC::ExecState* exec, FetchResponse* inputResponse, JSC::JSPromiseDeferred* promise)
369{
370 bool isResponseCorsSameOrigin = inputResponse->type() == ResourceResponse::Type::Basic || inputResponse->type() == ResourceResponse::Type::Cors || inputResponse->type() == ResourceResponse::Type::Default;
371
372 if (!isResponseCorsSameOrigin) {
373 promise->reject(exec, createTypeError(exec, "Response is not CORS-same-origin"_s));
374 return false;
375 }
376
377 if (!inputResponse->ok()) {
378 promise->reject(exec, createTypeError(exec, "Response has not returned OK status"_s));
379 return false;
380 }
381
382 auto contentType = inputResponse->headers().fastGet(HTTPHeaderName::ContentType);
383 if (!equalLettersIgnoringASCIICase(contentType, "application/wasm")) {
384 promise->reject(exec, createTypeError(exec, "Unexpected response MIME type. Expected 'application/wasm'"_s));
385 return false;
386 }
387
388 return true;
389}
390
391static void handleResponseOnStreamingAction(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, FetchResponse* inputResponse, JSC::JSPromiseDeferred* promise, Function<void(JSC::ExecState* exec, const char* data, size_t byteSize)>&& actionCallback)
392{
393 if (!isResponseCorrect(exec, inputResponse, promise))
394 return;
395
396 if (inputResponse->isBodyReceivedByChunk()) {
397 inputResponse->consumeBodyReceivedByChunk([promise, callback = WTFMove(actionCallback), globalObject, data = SharedBuffer::create()] (auto&& result) mutable {
398 ExecState* exec = globalObject->globalExec();
399 if (result.hasException()) {
400 promise->reject(exec, createTypeError(exec, result.exception().message()));
401 return;
402 }
403
404 if (auto chunk = result.returnValue())
405 data->append(reinterpret_cast<const char*>(chunk->data), chunk->size);
406 else {
407 VM& vm = exec->vm();
408 JSLockHolder lock(vm);
409
410 callback(exec, data->data(), data->size());
411 }
412 });
413 return;
414 }
415
416 auto body = inputResponse->consumeBody();
417 WTF::switchOn(body, [&] (Ref<FormData>& formData) {
418 if (auto buffer = formData->asSharedBuffer()) {
419 VM& vm = exec->vm();
420 JSLockHolder lock(vm);
421
422 actionCallback(exec, buffer->data(), buffer->size());
423 return;
424 }
425 // FIXME: http://webkit.org/b/184886> Implement loading for the Blob type
426 promise->reject(exec, createTypeError(exec, "Unexpected Response's Content-type"_s));
427 }, [&] (Ref<SharedBuffer>& buffer) {
428 VM& vm = exec->vm();
429 JSLockHolder lock(vm);
430
431 actionCallback(exec, buffer->data(), buffer->size());
432 }, [&] (std::nullptr_t&) {
433 promise->reject(exec, createTypeError(exec, "Unexpected Response's Content-type"_s));
434 });
435}
436
437void JSDOMWindowBase::compileStreaming(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSPromiseDeferred* promise, JSC::JSValue source)
438{
439 ASSERT(source);
440
441 VM& vm = exec->vm();
442
443 ASSERT(vm.promiseDeferredTimer->hasPendingPromise(promise));
444 ASSERT(vm.promiseDeferredTimer->hasDependancyInPendingPromise(promise, globalObject));
445
446 if (auto inputResponse = JSFetchResponse::toWrapped(vm, source)) {
447 handleResponseOnStreamingAction(globalObject, exec, inputResponse, promise, [promise] (JSC::ExecState* exec, const char* data, size_t byteSize) mutable {
448 if (auto arrayBuffer = tryAllocate(exec, promise, data, byteSize))
449 JSC::WebAssemblyPrototype::webAssemblyModuleValidateAsync(exec, promise, WTFMove(*arrayBuffer));
450 });
451 } else
452 promise->reject(exec, createTypeError(exec, "first argument must be an Response or Promise for Response"_s));
453}
454
455void JSDOMWindowBase::instantiateStreaming(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSPromiseDeferred* promise, JSC::JSValue source, JSC::JSObject* importedObject)
456{
457 ASSERT(source);
458
459 VM& vm = exec->vm();
460
461 ASSERT(vm.promiseDeferredTimer->hasPendingPromise(promise));
462 ASSERT(vm.promiseDeferredTimer->hasDependancyInPendingPromise(promise, globalObject));
463 ASSERT(vm.promiseDeferredTimer->hasDependancyInPendingPromise(promise, importedObject));
464
465 if (auto inputResponse = JSFetchResponse::toWrapped(vm, source)) {
466 handleResponseOnStreamingAction(globalObject, exec, inputResponse, promise, [promise, importedObject] (JSC::ExecState* exec, const char* data, size_t byteSize) mutable {
467 if (auto arrayBuffer = tryAllocate(exec, promise, data, byteSize))
468 JSC::WebAssemblyPrototype::webAssemblyModuleInstantinateAsync(exec, promise, WTFMove(*arrayBuffer), importedObject);
469 });
470 } else
471 promise->reject(exec, createTypeError(exec, "first argument must be an Response or Promise for Response"_s));
472}
473#endif
474
475} // namespace WebCore
476