496d2e7d6f2468feb6d81a2d84ed26d837a36eca
[WebKit-https.git] / Source / WebCore / bindings / js / JSDOMWindowCustom.cpp
1 /*
2  * Copyright (C) 2007-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2011 Google Inc. All rights reserved.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more details.
14  *
15  * You should have received a copy of the GNU Library General Public License
16  * along with this library; see the file COPYING.LIB.  If not, write to
17  * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #include "config.h"
22 #include "JSDOMWindowCustom.h"
23
24 #include "DOMWindowIndexedDatabase.h"
25 #include "Frame.h"
26 #include "HTMLCollection.h"
27 #include "HTMLDocument.h"
28 #include "HTMLFrameOwnerElement.h"
29 #include "HTTPParsers.h"
30 #include "JSDOMBindingSecurity.h"
31 #include "JSDOMConvertNullable.h"
32 #include "JSDOMConvertNumbers.h"
33 #include "JSDOMConvertStrings.h"
34 #include "JSEvent.h"
35 #include "JSEventListener.h"
36 #include "JSHTMLAudioElement.h"
37 #include "JSHTMLCollection.h"
38 #include "JSHTMLOptionElement.h"
39 #include "JSIDBFactory.h"
40 #include "JSRemoteDOMWindow.h"
41 #include "JSWindowProxy.h"
42 #include "JSWorker.h"
43 #include "Location.h"
44 #include "RuntimeEnabledFeatures.h"
45 #include "ScheduledAction.h"
46 #include "Settings.h"
47 #include "WebCoreJSClientData.h"
48 #include <JavaScriptCore/HeapSnapshotBuilder.h>
49 #include <JavaScriptCore/JSCInlines.h>
50 #include <JavaScriptCore/JSMicrotask.h>
51 #include <JavaScriptCore/Lookup.h>
52
53 #if ENABLE(USER_MESSAGE_HANDLERS)
54 #include "JSWebKitNamespace.h"
55 #endif
56
57
58 namespace WebCore {
59 using namespace JSC;
60
61 static CrossOriginWindowPolicy effectiveCrossOriginWindowPolicyForAccess(ExecState& state, AbstractDOMWindow& target)
62 {
63     static_assert(CrossOriginWindowPolicy::Deny < CrossOriginWindowPolicy::AllowPostMessage && CrossOriginWindowPolicy::AllowPostMessage < CrossOriginWindowPolicy::Allow, "More restrictive cross-origin options should have lower values");
64     return std::min(activeDOMWindow(state).crossOriginWindowPolicy(), target.crossOriginWindowPolicy());
65 }
66
67 EncodedJSValue JSC_HOST_CALL jsDOMWindowInstanceFunctionShowModalDialog(ExecState*);
68
69 void JSDOMWindow::visitAdditionalChildren(SlotVisitor& visitor)
70 {
71     if (Frame* frame = wrapped().frame())
72         visitor.addOpaqueRoot(frame);
73     
74     // Normally JSEventTargetCustom.cpp's JSEventTarget::visitAdditionalChildren() would call this. But
75     // even though DOMWindow is an EventTarget, JSDOMWindow does not subclass JSEventTarget, so we need
76     // to do this here.
77     wrapped().visitJSEventListeners(visitor);
78 }
79
80 #if ENABLE(USER_MESSAGE_HANDLERS)
81 static EncodedJSValue jsDOMWindowWebKit(ExecState* exec, EncodedJSValue thisValue, PropertyName)
82 {
83     VM& vm = exec->vm();
84     JSDOMWindow* castedThis = toJSDOMWindow(vm, JSValue::decode(thisValue));
85     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, castedThis->wrapped()))
86         return JSValue::encode(jsUndefined());
87     return JSValue::encode(toJS(exec, castedThis->globalObject(), castedThis->wrapped().webkitNamespace()));
88 }
89 #endif
90
91 template <DOMWindowType windowType>
92 bool jsDOMWindowGetOwnPropertySlotRestrictedAccess(JSDOMGlobalObject* thisObject, AbstractDOMWindow& window, ExecState& state, PropertyName propertyName, PropertySlot& slot, const String& errorMessage)
93 {
94     VM& vm = state.vm();
95     auto scope = DECLARE_THROW_SCOPE(vm);
96
97     auto& builtinNames = static_cast<JSVMClientData*>(vm.clientData)->builtinNames();
98
99     // https://html.spec.whatwg.org/#crossorigingetownpropertyhelper-(-o,-p-)
100     if (propertyName == vm.propertyNames->toStringTagSymbol || propertyName == vm.propertyNames->hasInstanceSymbol || propertyName == vm.propertyNames->isConcatSpreadableSymbol) {
101         slot.setValue(thisObject, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum, jsUndefined());
102         return true;
103     }
104
105     switch (effectiveCrossOriginWindowPolicyForAccess(state, window)) {
106     case CrossOriginWindowPolicy::AllowPostMessage:
107         if (propertyName == builtinNames.postMessagePublicName()) {
108             slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionPostMessage, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionPostMessage, 2>);
109             return true;
110         }
111         FALLTHROUGH;
112     case CrossOriginWindowPolicy::Deny:
113         throwSecurityError(state, scope, errorMessage);
114         slot.setUndefined();
115         return false;
116     case CrossOriginWindowPolicy::Allow:
117         break;
118     }
119
120     // These are the functions we allow access to cross-origin (DoNotCheckSecurity in IDL).
121     // Always provide the original function, on a fresh uncached function object.
122     if (propertyName == builtinNames.blurPublicName()) {
123         slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionBlur, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionBlur, 0>);
124         return true;
125     }
126     if (propertyName == builtinNames.closePublicName()) {
127         slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionClose, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionClose, 0>);
128         return true;
129     }
130     if (propertyName == builtinNames.focusPublicName()) {
131         slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionFocus, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionFocus, 0>);
132         return true;
133     }
134     if (propertyName == builtinNames.postMessagePublicName()) {
135         slot.setCustom(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontEnum), windowType == DOMWindowType::Remote ? nonCachingStaticFunctionGetter<jsRemoteDOMWindowInstanceFunctionPostMessage, 0> : nonCachingStaticFunctionGetter<jsDOMWindowInstanceFunctionPostMessage, 2>);
136         return true;
137     }
138
139     // When accessing cross-origin known Window properties, we always use the original property getter,
140     // even if the property was removed / redefined. As of early 2016, this matches Firefox and Chrome's
141     // behavior.
142     auto* classInfo = windowType == DOMWindowType::Remote ? JSRemoteDOMWindow::info() : JSDOMWindow::info();
143     if (auto* entry = classInfo->staticPropHashTable->entry(propertyName)) {
144         // Only allow access to these specific properties.
145         if (propertyName == builtinNames.locationPublicName()
146             || propertyName == builtinNames.closedPublicName()
147             || propertyName == vm.propertyNames->length
148             || propertyName == builtinNames.selfPublicName()
149             || propertyName == builtinNames.windowPublicName()
150             || propertyName == builtinNames.framesPublicName()
151             || propertyName == builtinNames.openerPublicName()
152             || propertyName == builtinNames.parentPublicName()
153             || propertyName == builtinNames.topPublicName()) {
154             bool shouldExposeSetter = propertyName == builtinNames.locationPublicName();
155             CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, entry->propertyGetter(), shouldExposeSetter ? entry->propertyPutter() : nullptr);
156             slot.setCustomGetterSetter(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::CustomAccessor | JSC::PropertyAttribute::DontEnum), customGetterSetter);
157             return true;
158         }
159
160         // For any other entries in the static property table, deny access. (Early return also prevents
161         // named getter from returning frames with matching names - this seems a little questionable, see
162         // FIXME comment on prototype search below.)
163         throwSecurityError(state, scope, errorMessage);
164         slot.setUndefined();
165         return false;
166     }
167
168     // Check for child frames by name before built-in properties to match Mozilla. This does
169     // not match IE, but some sites end up naming frames things that conflict with window
170     // properties that are in Moz but not IE. Since we have some of these, we have to do it
171     // the Moz way.
172     // FIXME: Add support to named attributes on RemoteFrames.
173     auto* frame = window.frame();
174     if (frame && is<Frame>(*frame)) {
175         if (auto* scopedChild = downcast<Frame>(*frame).tree().scopedChild(propertyNameToAtomicString(propertyName))) {
176             slot.setValue(thisObject, JSC::PropertyAttribute::ReadOnly | JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::DontEnum, toJS(&state, scopedChild->document()->domWindow()));
177             return true;
178         }
179     }
180
181     throwSecurityError(state, scope, errorMessage);
182     slot.setUndefined();
183     return false;
184 }
185 template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(JSDOMGlobalObject*, AbstractDOMWindow&, ExecState&, PropertyName, PropertySlot&, const String&);
186 template bool jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Remote>(JSDOMGlobalObject*, AbstractDOMWindow&, ExecState&, PropertyName, PropertySlot&, const String&);
187
188 // Property access sequence is:
189 // (1) indexed properties,
190 // (2) regular own properties,
191 // (3) named properties (in fact, these shouldn't be on the window, should be on the NPO).
192 bool JSDOMWindow::getOwnPropertySlot(JSObject* object, ExecState* state, PropertyName propertyName, PropertySlot& slot)
193 {
194     // (1) First, indexed properties.
195     // Hand off all indexed access to getOwnPropertySlotByIndex, which supports the indexed getter.
196     if (std::optional<unsigned> index = parseIndex(propertyName))
197         return getOwnPropertySlotByIndex(object, state, index.value(), slot);
198
199     auto* thisObject = jsCast<JSDOMWindow*>(object);
200
201     // Hand off all cross-domain access to jsDOMWindowGetOwnPropertySlotRestrictedAccess.
202     String errorMessage;
203     if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage))
204         return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, thisObject->wrapped(), *state, propertyName, slot, errorMessage);
205
206     // FIXME: this needs more explanation.
207     // (Particularly, is it correct that this exists here but not in getOwnPropertySlotByIndex?)
208     slot.setWatchpointSet(thisObject->m_windowCloseWatchpoints);
209
210     // (2) Regular own properties.
211     PropertySlot slotCopy = slot;
212     if (Base::getOwnPropertySlot(thisObject, state, propertyName, slot)) {
213         auto* frame = thisObject->wrapped().frame();
214
215         // Detect when we're getting the property 'showModalDialog', this is disabled, and has its original value.
216         bool isShowModalDialogAndShouldHide = propertyName == static_cast<JSVMClientData*>(state->vm().clientData)->builtinNames().showModalDialogPublicName()
217             && (!frame || !DOMWindow::canShowModalDialog(*frame))
218             && slot.isValue() && isHostFunction(slot.getValue(state, propertyName), jsDOMWindowInstanceFunctionShowModalDialog);
219         // Unless we're in the showModalDialog special case, we're done.
220         if (!isShowModalDialogAndShouldHide)
221             return true;
222         slot = slotCopy;
223     }
224
225 #if ENABLE(USER_MESSAGE_HANDLERS)
226     if (propertyName == static_cast<JSVMClientData*>(state->vm().clientData)->builtinNames().webkitPublicName() && thisObject->wrapped().shouldHaveWebKitNamespaceForWorld(thisObject->world())) {
227         slot.setCacheableCustom(thisObject, JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly, jsDOMWindowWebKit);
228         return true;
229     }
230 #endif
231
232     return false;
233 }
234
235 // Property access sequence is:
236 // (1) indexed properties,
237 // (2) regular own properties,
238 // (3) named properties (in fact, these shouldn't be on the window, should be on the NPO).
239 bool JSDOMWindow::getOwnPropertySlotByIndex(JSObject* object, ExecState* state, unsigned index, PropertySlot& slot)
240 {
241     auto* thisObject = jsCast<JSDOMWindow*>(object);
242     auto& window = thisObject->wrapped();
243     auto* frame = window.frame();
244
245     // Indexed getters take precendence over regular properties, so caching would be invalid.
246     slot.disableCaching();
247
248     String errorMessage;
249     std::optional<bool> cachedIsCrossOriginAccess;
250     auto isCrossOriginAccess = [&] {
251         if (!cachedIsCrossOriginAccess)
252             cachedIsCrossOriginAccess = !BindingSecurity::shouldAllowAccessToDOMWindow(*state, window, errorMessage);
253         return *cachedIsCrossOriginAccess;
254     };
255
256     // (1) First, indexed properties.
257     // These are also allowed cross-origin, so come before the access check.
258     switch (effectiveCrossOriginWindowPolicyForAccess(*state, window)) {
259     case CrossOriginWindowPolicy::Deny:
260     case CrossOriginWindowPolicy::AllowPostMessage:
261         if (isCrossOriginAccess())
262             break;
263         FALLTHROUGH;
264     case CrossOriginWindowPolicy::Allow:
265         if (frame && index < frame->tree().scopedChildCount()) {
266             slot.setValue(thisObject, static_cast<unsigned>(JSC::PropertyAttribute::ReadOnly), toJS(state, frame->tree().scopedChild(index)->document()->domWindow()));
267             return true;
268         }
269         break;
270     }
271
272     // Hand off all cross-domain/frameless access to jsDOMWindowGetOwnPropertySlotRestrictedAccess.
273     if (isCrossOriginAccess())
274         return jsDOMWindowGetOwnPropertySlotRestrictedAccess<DOMWindowType::Local>(thisObject, window, *state, Identifier::from(state, index), slot, errorMessage);
275
276     // (2) Regular own properties.
277     return Base::getOwnPropertySlotByIndex(thisObject, state, index, slot);
278 }
279
280 bool JSDOMWindow::put(JSCell* cell, ExecState* state, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
281 {
282     VM& vm = state->vm();
283     auto scope = DECLARE_THROW_SCOPE(vm);
284
285     auto* thisObject = jsCast<JSDOMWindow*>(cell);
286     if (!thisObject->wrapped().frame())
287         return false;
288
289     String errorMessage;
290     if (!BindingSecurity::shouldAllowAccessToDOMWindow(*state, thisObject->wrapped(), errorMessage)) {
291         // We only allow setting "location" attribute cross-origin.
292         if (propertyName == static_cast<JSVMClientData*>(vm.clientData)->builtinNames().locationPublicName()) {
293             bool putResult = false;
294             if (lookupPut(state, propertyName, thisObject, value, *s_info.staticPropHashTable, slot, putResult))
295                 return putResult;
296             return false;
297         }
298         throwSecurityError(*state, scope, errorMessage);
299         return false;
300     }
301
302     return Base::put(thisObject, state, propertyName, value, slot);
303 }
304
305 bool JSDOMWindow::putByIndex(JSCell* cell, ExecState* exec, unsigned index, JSValue value, bool shouldThrow)
306 {
307     auto* thisObject = jsCast<JSDOMWindow*>(cell);
308     if (!thisObject->wrapped().frame() || !BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped()))
309         return false;
310     
311     return Base::putByIndex(thisObject, exec, index, value, shouldThrow);
312 }
313
314 bool JSDOMWindow::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
315 {
316     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(cell);
317     // Only allow deleting properties by frames in the same origin.
318     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), ThrowSecurityError))
319         return false;
320     return Base::deleteProperty(thisObject, exec, propertyName);
321 }
322
323 bool JSDOMWindow::deletePropertyByIndex(JSCell* cell, ExecState* exec, unsigned propertyName)
324 {
325     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(cell);
326     // Only allow deleting properties by frames in the same origin.
327     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), ThrowSecurityError))
328         return false;
329     return Base::deletePropertyByIndex(thisObject, exec, propertyName);
330 }
331
332 void JSDOMWindow::heapSnapshot(JSCell* cell, HeapSnapshotBuilder& builder)
333 {
334     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(cell);
335     if (auto* location = thisObject->wrapped().location())
336         builder.setLabelForCell(cell, location->href());
337
338     Base::heapSnapshot(cell, builder);
339 }
340
341 // https://html.spec.whatwg.org/#crossoriginproperties-(-o-)
342 static void addCrossOriginWindowPropertyNames(ExecState& state, AbstractDOMWindow& window, PropertyNameArray& propertyNames)
343 {
344     auto& vm = state.vm();
345
346     static const Identifier* const properties[] = {
347         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().blurPublicName(),
348         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().closePublicName(),
349         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().closedPublicName(),
350         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().focusPublicName(),
351         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().framesPublicName(),
352         &vm.propertyNames->length,
353         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().locationPublicName(),
354         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().openerPublicName(),
355         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().parentPublicName(),
356         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().postMessagePublicName(),
357         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().selfPublicName(),
358         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().topPublicName(),
359         &static_cast<JSVMClientData*>(vm.clientData)->builtinNames().windowPublicName()
360     };
361
362     switch (effectiveCrossOriginWindowPolicyForAccess(state, window)) {
363     case CrossOriginWindowPolicy::Allow:
364         for (auto* property : properties)
365             propertyNames.add(*property);
366         break;
367     case CrossOriginWindowPolicy::AllowPostMessage:
368         propertyNames.add(static_cast<JSVMClientData*>(vm.clientData)->builtinNames().postMessagePublicName());
369         break;
370     case CrossOriginWindowPolicy::Deny:
371         break;
372     }
373 }
374
375 static void addScopedChildrenIndexes(ExecState& state, DOMWindow& window, PropertyNameArray& propertyNames)
376 {
377     auto* document = window.document();
378     if (!document)
379         return;
380
381     auto* frame = document->frame();
382     if (!frame)
383         return;
384
385     switch (effectiveCrossOriginWindowPolicyForAccess(state, window)) {
386     case CrossOriginWindowPolicy::Allow:
387         break;
388     case CrossOriginWindowPolicy::Deny:
389     case CrossOriginWindowPolicy::AllowPostMessage:
390         return;
391     }
392
393     unsigned scopedChildCount = frame->tree().scopedChildCount();
394     for (unsigned i = 0; i < scopedChildCount; ++i)
395         propertyNames.add(Identifier::from(&state, i));
396 }
397
398 // https://html.spec.whatwg.org/#crossoriginownpropertykeys-(-o-)
399 void addCrossOriginWindowOwnPropertyNames(ExecState& state, AbstractDOMWindow& window, PropertyNameArray& propertyNames)
400 {
401     addCrossOriginWindowPropertyNames(state, window, propertyNames);
402
403     auto& vm = state.vm();
404     propertyNames.add(vm.propertyNames->toStringTagSymbol);
405     propertyNames.add(vm.propertyNames->hasInstanceSymbol);
406     propertyNames.add(vm.propertyNames->isConcatSpreadableSymbol);
407 }
408
409 // https://html.spec.whatwg.org/#windowproxy-ownpropertykeys
410 void JSDOMWindow::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
411 {
412     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
413
414     addScopedChildrenIndexes(*exec, thisObject->wrapped(), propertyNames);
415
416     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), DoNotReportSecurityError)) {
417         if (mode.includeDontEnumProperties())
418             addCrossOriginWindowOwnPropertyNames(*exec, thisObject->wrapped(), propertyNames);
419         return;
420     }
421     Base::getOwnPropertyNames(thisObject, exec, propertyNames, mode);
422 }
423
424 bool JSDOMWindow::defineOwnProperty(JSC::JSObject* object, JSC::ExecState* exec, JSC::PropertyName propertyName, const JSC::PropertyDescriptor& descriptor, bool shouldThrow)
425 {
426     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
427     // Only allow defining properties in this way by frames in the same origin, as it allows setters to be introduced.
428     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), ThrowSecurityError))
429         return false;
430
431     // Don't allow shadowing location using accessor properties.
432     if (descriptor.isAccessorDescriptor() && propertyName == Identifier::fromString(exec, "location"))
433         return false;
434
435     return Base::defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
436 }
437
438 JSValue JSDOMWindow::getPrototype(JSObject* object, ExecState* exec)
439 {
440     JSDOMWindow* thisObject = jsCast<JSDOMWindow*>(object);
441     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), DoNotReportSecurityError))
442         return jsNull();
443
444     return Base::getPrototype(object, exec);
445 }
446
447 bool JSDOMWindow::preventExtensions(JSObject*, ExecState* exec)
448 {
449     auto scope = DECLARE_THROW_SCOPE(exec->vm());
450
451     throwTypeError(exec, scope, "Cannot prevent extensions on this object"_s);
452     return false;
453 }
454
455 String JSDOMWindow::toStringName(const JSObject* object, ExecState* exec)
456 {
457     auto* thisObject = jsCast<const JSDOMWindow*>(object);
458     if (!BindingSecurity::shouldAllowAccessToDOMWindow(exec, thisObject->wrapped(), DoNotReportSecurityError))
459         return "Object"_s;
460     return "Window"_s;
461 }
462
463 // Custom Attributes
464
465 JSValue JSDOMWindow::event(ExecState& state) const
466 {
467     Event* event = currentEvent();
468     if (!event)
469         return jsUndefined();
470     return toJS(&state, const_cast<JSDOMWindow*>(this), event);
471 }
472
473 // Custom functions
474
475 class DialogHandler {
476 public:
477     explicit DialogHandler(ExecState& exec)
478         : m_exec(exec)
479     {
480     }
481
482     void dialogCreated(DOMWindow&);
483     JSValue returnValue() const;
484
485 private:
486     ExecState& m_exec;
487     RefPtr<Frame> m_frame;
488 };
489
490 inline void DialogHandler::dialogCreated(DOMWindow& dialog)
491 {
492     m_frame = dialog.frame();
493     
494     // FIXME: This looks like a leak between the normal world and an isolated
495     //        world if dialogArguments comes from an isolated world.
496     JSDOMWindow* globalObject = toJSDOMWindow(m_frame.get(), normalWorld(m_exec.vm()));
497     if (JSValue dialogArguments = m_exec.argument(1))
498         globalObject->putDirect(m_exec.vm(), Identifier::fromString(&m_exec, "dialogArguments"), dialogArguments);
499 }
500
501 inline JSValue DialogHandler::returnValue() const
502 {
503     JSDOMWindow* globalObject = toJSDOMWindow(m_frame.get(), normalWorld(m_exec.vm()));
504     if (!globalObject)
505         return jsUndefined();
506     Identifier identifier = Identifier::fromString(&m_exec, "returnValue");
507     PropertySlot slot(globalObject, PropertySlot::InternalMethodType::Get);
508     if (!JSGlobalObject::getOwnPropertySlot(globalObject, &m_exec, identifier, slot))
509         return jsUndefined();
510     return slot.getValue(&m_exec, identifier);
511 }
512
513 JSValue JSDOMWindow::showModalDialog(ExecState& state)
514 {
515     VM& vm = state.vm();
516     auto scope = DECLARE_THROW_SCOPE(vm);
517
518     if (UNLIKELY(state.argumentCount() < 1))
519         return throwException(&state, scope, createNotEnoughArgumentsError(&state));
520
521     String urlString = convert<IDLNullable<IDLDOMString>>(state, state.argument(0));
522     RETURN_IF_EXCEPTION(scope, JSValue());
523     String dialogFeaturesString = convert<IDLNullable<IDLDOMString>>(state, state.argument(2));
524     RETURN_IF_EXCEPTION(scope, JSValue());
525
526     DialogHandler handler(state);
527
528     wrapped().showModalDialog(urlString, dialogFeaturesString, activeDOMWindow(state), firstDOMWindow(state), [&handler](DOMWindow& dialog) {
529         handler.dialogCreated(dialog);
530     });
531
532     return handler.returnValue();
533 }
534
535 JSValue JSDOMWindow::queueMicrotask(ExecState& state)
536 {
537     VM& vm = state.vm();
538     auto scope = DECLARE_THROW_SCOPE(vm);
539
540     if (UNLIKELY(state.argumentCount() < 1))
541         return throwException(&state, scope, createNotEnoughArgumentsError(&state));
542
543     JSValue functionValue = state.uncheckedArgument(0);
544     if (UNLIKELY(!functionValue.isFunction(vm)))
545         return JSValue::decode(throwArgumentMustBeFunctionError(state, scope, 0, "callback", "Window", "queueMicrotask"));
546
547     scope.release();
548     Base::queueMicrotask(JSC::createJSMicrotask(vm, functionValue));
549     return jsUndefined();
550 }
551
552 DOMWindow* JSDOMWindow::toWrapped(VM& vm, JSValue value)
553 {
554     if (!value.isObject())
555         return nullptr;
556     JSObject* object = asObject(value);
557     if (object->inherits<JSDOMWindow>(vm))
558         return &jsCast<JSDOMWindow*>(object)->wrapped();
559     if (object->inherits<JSWindowProxy>(vm)) {
560         if (auto* jsDOMWindow = jsDynamicCast<JSDOMWindow*>(vm, jsCast<JSWindowProxy*>(object)->window()))
561             return &jsDOMWindow->wrapped();
562     }
563     return nullptr;
564 }
565
566 void JSDOMWindow::setOpener(JSC::ExecState& state, JSC::JSValue value)
567 {
568     if (!BindingSecurity::shouldAllowAccessToDOMWindow(&state, wrapped(), ThrowSecurityError))
569         return;
570
571     if (value.isNull()) {
572         wrapped().disownOpener();
573         return;
574     }
575     replaceStaticPropertySlot(state.vm(), this, Identifier::fromString(&state.vm(), "opener"), value);
576 }
577
578 } // namespace WebCore