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