1 | /* |
2 | * Copyright (C) 2006-2017 Apple Inc. All rights reserved. |
3 | * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) |
4 | * |
5 | * Redistribution and use in source and binary forms, with or without |
6 | * modification, are permitted provided that the following conditions |
7 | * are met: |
8 | * 1. Redistributions of source code must retain the above copyright |
9 | * notice, this list of conditions and the following disclaimer. |
10 | * 2. Redistributions in binary form must reproduce the above copyright |
11 | * notice, this list of conditions and the following disclaimer in the |
12 | * documentation and/or other materials provided with the distribution. |
13 | * |
14 | * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
15 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
16 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
17 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
18 | * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
19 | * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
20 | * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
21 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
22 | * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
23 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
24 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
25 | */ |
26 | |
27 | #include "config.h" |
28 | #include "DOMWindow.h" |
29 | |
30 | #include "BackForwardController.h" |
31 | #include "BarProp.h" |
32 | #include "CSSComputedStyleDeclaration.h" |
33 | #include "CSSRule.h" |
34 | #include "CSSRuleList.h" |
35 | #include "Chrome.h" |
36 | #include "ChromeClient.h" |
37 | #include "ComposedTreeIterator.h" |
38 | #include "ContentExtensionActions.h" |
39 | #include "ContentExtensionRule.h" |
40 | #include "ContentRuleListResults.h" |
41 | #include "Crypto.h" |
42 | #include "CustomElementRegistry.h" |
43 | #include "DOMApplicationCache.h" |
44 | #include "DOMSelection.h" |
45 | #include "DOMStringList.h" |
46 | #include "DOMTimer.h" |
47 | #include "DOMTokenList.h" |
48 | #include "DOMURL.h" |
49 | #include "DeviceMotionController.h" |
50 | #include "DeviceMotionData.h" |
51 | #include "DeviceMotionEvent.h" |
52 | #include "DeviceOrientationAndMotionAccessController.h" |
53 | #include "DeviceOrientationController.h" |
54 | #include "Document.h" |
55 | #include "DocumentLoader.h" |
56 | #include "Editor.h" |
57 | #include "Element.h" |
58 | #include "EventHandler.h" |
59 | #include "EventListener.h" |
60 | #include "EventNames.h" |
61 | #include "FloatRect.h" |
62 | #include "FocusController.h" |
63 | #include "Frame.h" |
64 | #include "FrameLoadRequest.h" |
65 | #include "FrameLoader.h" |
66 | #include "FrameLoaderClient.h" |
67 | #include "FrameTree.h" |
68 | #include "FrameView.h" |
69 | #include "HTTPParsers.h" |
70 | #include "History.h" |
71 | #include "InspectorInstrumentation.h" |
72 | #include "JSDOMWindowBase.h" |
73 | #include "JSExecState.h" |
74 | #include "Location.h" |
75 | #include "MediaQueryList.h" |
76 | #include "MediaQueryMatcher.h" |
77 | #include "MessageEvent.h" |
78 | #include "MessageWithMessagePorts.h" |
79 | #include "NavigationScheduler.h" |
80 | #include "Navigator.h" |
81 | #include "Page.h" |
82 | #include "PageConsoleClient.h" |
83 | #include "PageTransitionEvent.h" |
84 | #include "Performance.h" |
85 | #include "RequestAnimationFrameCallback.h" |
86 | #include "ResourceLoadInfo.h" |
87 | #include "ResourceLoadObserver.h" |
88 | #include "RuntimeApplicationChecks.h" |
89 | #include "RuntimeEnabledFeatures.h" |
90 | #include "ScheduledAction.h" |
91 | #include "Screen.h" |
92 | #include "SecurityOrigin.h" |
93 | #include "SecurityOriginData.h" |
94 | #include "SecurityPolicy.h" |
95 | #include "SelectorQuery.h" |
96 | #include "SerializedScriptValue.h" |
97 | #include "Settings.h" |
98 | #include "StaticNodeList.h" |
99 | #include "Storage.h" |
100 | #include "StorageArea.h" |
101 | #include "StorageNamespace.h" |
102 | #include "StorageNamespaceProvider.h" |
103 | #include "StyleMedia.h" |
104 | #include "StyleResolver.h" |
105 | #include "StyleScope.h" |
106 | #include "SuddenTermination.h" |
107 | #include <wtf/URL.h> |
108 | #include "UserGestureIndicator.h" |
109 | #include "VisualViewport.h" |
110 | #include "WebKitPoint.h" |
111 | #include "WindowFeatures.h" |
112 | #include "WindowFocusAllowedIndicator.h" |
113 | #include "WindowProxy.h" |
114 | #include <JavaScriptCore/ScriptCallStack.h> |
115 | #include <JavaScriptCore/ScriptCallStackFactory.h> |
116 | #include <algorithm> |
117 | #include <memory> |
118 | #include <wtf/IsoMallocInlines.h> |
119 | #include <wtf/Language.h> |
120 | #include <wtf/MainThread.h> |
121 | #include <wtf/MathExtras.h> |
122 | #include <wtf/NeverDestroyed.h> |
123 | #include <wtf/Ref.h> |
124 | #include <wtf/SetForScope.h> |
125 | #include <wtf/Variant.h> |
126 | #include <wtf/text/WTFString.h> |
127 | |
128 | #if ENABLE(USER_MESSAGE_HANDLERS) |
129 | #include "UserContentController.h" |
130 | #include "UserMessageHandlerDescriptor.h" |
131 | #include "WebKitNamespace.h" |
132 | #endif |
133 | |
134 | #if ENABLE(GAMEPAD) |
135 | #include "GamepadManager.h" |
136 | #endif |
137 | |
138 | #if ENABLE(GEOLOCATION) |
139 | #include "NavigatorGeolocation.h" |
140 | #endif |
141 | |
142 | #if ENABLE(POINTER_LOCK) |
143 | #include "PointerLockController.h" |
144 | #endif |
145 | |
146 | namespace WebCore { |
147 | using namespace Inspector; |
148 | |
149 | WTF_MAKE_ISO_ALLOCATED_IMPL(DOMWindow); |
150 | |
151 | class PostMessageTimer : public TimerBase { |
152 | public: |
153 | PostMessageTimer(DOMWindow& window, MessageWithMessagePorts&& message, const String& sourceOrigin, RefPtr<WindowProxy>&& source, RefPtr<SecurityOrigin>&& targetOrigin, RefPtr<ScriptCallStack>&& stackTrace) |
154 | : m_window(window) |
155 | , m_message(WTFMove(message)) |
156 | , m_origin(sourceOrigin) |
157 | , m_source(source) |
158 | , m_targetOrigin(WTFMove(targetOrigin)) |
159 | , m_stackTrace(stackTrace) |
160 | , m_userGestureToForward(UserGestureIndicator::currentUserGesture()) |
161 | { |
162 | } |
163 | |
164 | Ref<MessageEvent> event(ScriptExecutionContext& context) |
165 | { |
166 | return MessageEvent::create(MessagePort::entanglePorts(context, WTFMove(m_message.transferredPorts)), m_message.message.releaseNonNull(), m_origin, { }, m_source ? makeOptional(MessageEventSource(WTFMove(m_source))) : WTF::nullopt); |
167 | } |
168 | |
169 | SecurityOrigin* targetOrigin() const { return m_targetOrigin.get(); } |
170 | ScriptCallStack* stackTrace() const { return m_stackTrace.get(); } |
171 | |
172 | private: |
173 | void fired() override |
174 | { |
175 | // This object gets deleted when std::unique_ptr falls out of scope.. |
176 | std::unique_ptr<PostMessageTimer> timer(this); |
177 | |
178 | UserGestureIndicator userGestureIndicator(m_userGestureToForward); |
179 | m_window->postMessageTimerFired(*timer); |
180 | } |
181 | |
182 | Ref<DOMWindow> m_window; |
183 | MessageWithMessagePorts m_message; |
184 | String m_origin; |
185 | RefPtr<WindowProxy> m_source; |
186 | RefPtr<SecurityOrigin> m_targetOrigin; |
187 | RefPtr<ScriptCallStack> m_stackTrace; |
188 | RefPtr<UserGestureToken> m_userGestureToForward; |
189 | }; |
190 | |
191 | typedef HashCountedSet<DOMWindow*> DOMWindowSet; |
192 | |
193 | static DOMWindowSet& windowsWithUnloadEventListeners() |
194 | { |
195 | static NeverDestroyed<DOMWindowSet> windowsWithUnloadEventListeners; |
196 | return windowsWithUnloadEventListeners; |
197 | } |
198 | |
199 | static DOMWindowSet& windowsWithBeforeUnloadEventListeners() |
200 | { |
201 | static NeverDestroyed<DOMWindowSet> windowsWithBeforeUnloadEventListeners; |
202 | return windowsWithBeforeUnloadEventListeners; |
203 | } |
204 | |
205 | static void addUnloadEventListener(DOMWindow* domWindow) |
206 | { |
207 | if (windowsWithUnloadEventListeners().add(domWindow).isNewEntry) |
208 | domWindow->disableSuddenTermination(); |
209 | } |
210 | |
211 | static void removeUnloadEventListener(DOMWindow* domWindow) |
212 | { |
213 | if (windowsWithUnloadEventListeners().remove(domWindow)) |
214 | domWindow->enableSuddenTermination(); |
215 | } |
216 | |
217 | static void removeAllUnloadEventListeners(DOMWindow* domWindow) |
218 | { |
219 | if (windowsWithUnloadEventListeners().removeAll(domWindow)) |
220 | domWindow->enableSuddenTermination(); |
221 | } |
222 | |
223 | static void addBeforeUnloadEventListener(DOMWindow* domWindow) |
224 | { |
225 | if (windowsWithBeforeUnloadEventListeners().add(domWindow).isNewEntry) |
226 | domWindow->disableSuddenTermination(); |
227 | } |
228 | |
229 | static void removeBeforeUnloadEventListener(DOMWindow* domWindow) |
230 | { |
231 | if (windowsWithBeforeUnloadEventListeners().remove(domWindow)) |
232 | domWindow->enableSuddenTermination(); |
233 | } |
234 | |
235 | static void removeAllBeforeUnloadEventListeners(DOMWindow* domWindow) |
236 | { |
237 | if (windowsWithBeforeUnloadEventListeners().removeAll(domWindow)) |
238 | domWindow->enableSuddenTermination(); |
239 | } |
240 | |
241 | static bool allowsBeforeUnloadListeners(DOMWindow* window) |
242 | { |
243 | ASSERT_ARG(window, window); |
244 | Frame* frame = window->frame(); |
245 | if (!frame) |
246 | return false; |
247 | if (!frame->page()) |
248 | return false; |
249 | return frame->isMainFrame(); |
250 | } |
251 | |
252 | bool DOMWindow::dispatchAllPendingBeforeUnloadEvents() |
253 | { |
254 | DOMWindowSet& set = windowsWithBeforeUnloadEventListeners(); |
255 | if (set.isEmpty()) |
256 | return true; |
257 | |
258 | static bool alreadyDispatched = false; |
259 | ASSERT(!alreadyDispatched); |
260 | if (alreadyDispatched) |
261 | return true; |
262 | |
263 | Vector<Ref<DOMWindow>> windows; |
264 | windows.reserveInitialCapacity(set.size()); |
265 | for (auto& window : set) |
266 | windows.uncheckedAppend(*window.key); |
267 | |
268 | for (auto& window : windows) { |
269 | if (!set.contains(window.ptr())) |
270 | continue; |
271 | |
272 | Frame* frame = window->frame(); |
273 | if (!frame) |
274 | continue; |
275 | |
276 | if (!frame->loader().shouldClose()) |
277 | return false; |
278 | |
279 | window->enableSuddenTermination(); |
280 | } |
281 | |
282 | alreadyDispatched = true; |
283 | return true; |
284 | } |
285 | |
286 | unsigned DOMWindow::pendingUnloadEventListeners() const |
287 | { |
288 | return windowsWithUnloadEventListeners().count(const_cast<DOMWindow*>(this)); |
289 | } |
290 | |
291 | void DOMWindow::dispatchAllPendingUnloadEvents() |
292 | { |
293 | DOMWindowSet& set = windowsWithUnloadEventListeners(); |
294 | if (set.isEmpty()) |
295 | return; |
296 | |
297 | static bool alreadyDispatched = false; |
298 | ASSERT(!alreadyDispatched); |
299 | if (alreadyDispatched) |
300 | return; |
301 | |
302 | auto windows = WTF::map(set, [] (auto& keyValue) { |
303 | return Ref<DOMWindow>(*(keyValue.key)); |
304 | }); |
305 | |
306 | for (auto& window : windows) { |
307 | if (!set.contains(window.ptr())) |
308 | continue; |
309 | |
310 | window->dispatchEvent(PageTransitionEvent::create(eventNames().pagehideEvent, false), window->document()); |
311 | window->dispatchEvent(Event::create(eventNames().unloadEvent, Event::CanBubble::No, Event::IsCancelable::No), window->document()); |
312 | |
313 | window->enableSuddenTermination(); |
314 | } |
315 | |
316 | alreadyDispatched = true; |
317 | } |
318 | |
319 | // This function: |
320 | // 1) Validates the pending changes are not changing any value to NaN; in that case keep original value. |
321 | // 2) Constrains the window rect to the minimum window size and no bigger than the float rect's dimensions. |
322 | // 3) Constrains the window rect to within the top and left boundaries of the available screen rect. |
323 | // 4) Constrains the window rect to within the bottom and right boundaries of the available screen rect. |
324 | // 5) Translate the window rect coordinates to be within the coordinate space of the screen. |
325 | FloatRect DOMWindow::adjustWindowRect(Page& page, const FloatRect& pendingChanges) |
326 | { |
327 | FloatRect screen = screenAvailableRect(page.mainFrame().view()); |
328 | FloatRect window = page.chrome().windowRect(); |
329 | |
330 | // Make sure we're in a valid state before adjusting dimensions. |
331 | ASSERT(std::isfinite(screen.x())); |
332 | ASSERT(std::isfinite(screen.y())); |
333 | ASSERT(std::isfinite(screen.width())); |
334 | ASSERT(std::isfinite(screen.height())); |
335 | ASSERT(std::isfinite(window.x())); |
336 | ASSERT(std::isfinite(window.y())); |
337 | ASSERT(std::isfinite(window.width())); |
338 | ASSERT(std::isfinite(window.height())); |
339 | |
340 | // Update window values if new requested values are not NaN. |
341 | if (!std::isnan(pendingChanges.x())) |
342 | window.setX(pendingChanges.x()); |
343 | if (!std::isnan(pendingChanges.y())) |
344 | window.setY(pendingChanges.y()); |
345 | if (!std::isnan(pendingChanges.width())) |
346 | window.setWidth(pendingChanges.width()); |
347 | if (!std::isnan(pendingChanges.height())) |
348 | window.setHeight(pendingChanges.height()); |
349 | |
350 | FloatSize minimumSize = page.chrome().client().minimumWindowSize(); |
351 | window.setWidth(std::min(std::max(minimumSize.width(), window.width()), screen.width())); |
352 | window.setHeight(std::min(std::max(minimumSize.height(), window.height()), screen.height())); |
353 | |
354 | // Constrain the window position within the valid screen area. |
355 | window.setX(std::max(screen.x(), std::min(window.x(), screen.maxX() - window.width()))); |
356 | window.setY(std::max(screen.y(), std::min(window.y(), screen.maxY() - window.height()))); |
357 | |
358 | return window; |
359 | } |
360 | |
361 | bool DOMWindow::(Frame& firstFrame) |
362 | { |
363 | if (DocumentLoader* documentLoader = firstFrame.loader().documentLoader()) { |
364 | // If pop-up policy was set during navigation, use it. If not, use the global settings. |
365 | PopUpPolicy = documentLoader->popUpPolicy(); |
366 | if (popUpPolicy == PopUpPolicy::Allow) |
367 | return true; |
368 | |
369 | if (popUpPolicy == PopUpPolicy::Block) |
370 | return false; |
371 | } |
372 | |
373 | return UserGestureIndicator::processingUserGesture() |
374 | || firstFrame.settings().javaScriptCanOpenWindowsAutomatically(); |
375 | } |
376 | |
377 | bool DOMWindow::() |
378 | { |
379 | auto* frame = this->frame(); |
380 | return frame && allowPopUp(*frame); |
381 | } |
382 | |
383 | bool DOMWindow::canShowModalDialog(const Frame& frame) |
384 | { |
385 | // Override support for layout testing purposes. |
386 | if (auto* document = frame.document()) { |
387 | if (auto* window = document->domWindow()) { |
388 | if (window->m_canShowModalDialogOverride) |
389 | return window->m_canShowModalDialogOverride.value(); |
390 | } |
391 | } |
392 | |
393 | auto* page = frame.page(); |
394 | return page && page->chrome().canRunModal(); |
395 | } |
396 | |
397 | static void languagesChangedCallback(void* context) |
398 | { |
399 | static_cast<DOMWindow*>(context)->languagesChanged(); |
400 | } |
401 | |
402 | void DOMWindow::setCanShowModalDialogOverride(bool allow) |
403 | { |
404 | m_canShowModalDialogOverride = allow; |
405 | } |
406 | |
407 | DOMWindow::DOMWindow(Document& document) |
408 | : AbstractDOMWindow(GlobalWindowIdentifier { Process::identifier(), WindowIdentifier::generate() }) |
409 | , ContextDestructionObserver(&document) |
410 | { |
411 | ASSERT(frame()); |
412 | addLanguageChangeObserver(this, &languagesChangedCallback); |
413 | } |
414 | |
415 | void DOMWindow::didSecureTransitionTo(Document& document) |
416 | { |
417 | observeContext(&document); |
418 | |
419 | // The Window is being transferred from one document to another so we need to reset data |
420 | // members that store the window's document (rather than the window itself). |
421 | m_crypto = nullptr; |
422 | m_navigator = nullptr; |
423 | m_performance = nullptr; |
424 | } |
425 | |
426 | DOMWindow::~DOMWindow() |
427 | { |
428 | if (m_suspendedForDocumentSuspension) |
429 | willDestroyCachedFrame(); |
430 | else |
431 | willDestroyDocumentInFrame(); |
432 | |
433 | removeAllUnloadEventListeners(this); |
434 | removeAllBeforeUnloadEventListeners(this); |
435 | |
436 | #if ENABLE(GAMEPAD) |
437 | if (m_gamepadEventListenerCount) |
438 | GamepadManager::singleton().unregisterDOMWindow(this); |
439 | #endif |
440 | |
441 | removeLanguageChangeObserver(this); |
442 | } |
443 | |
444 | RefPtr<MediaQueryList> DOMWindow::matchMedia(const String& media) |
445 | { |
446 | return document() ? document()->mediaQueryMatcher().matchMedia(media) : nullptr; |
447 | } |
448 | |
449 | Page* DOMWindow::page() |
450 | { |
451 | return frame() ? frame()->page() : nullptr; |
452 | } |
453 | |
454 | void DOMWindow::frameDestroyed() |
455 | { |
456 | Ref<DOMWindow> protectedThis(*this); |
457 | |
458 | willDestroyDocumentInFrame(); |
459 | JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(this); |
460 | } |
461 | |
462 | void DOMWindow::willDestroyCachedFrame() |
463 | { |
464 | // It is necessary to copy m_observers to a separate vector because the Observer may |
465 | // unregister themselves from the DOMWindow as a result of the call to willDestroyGlobalObjectInCachedFrame. |
466 | for (auto* observer : copyToVector(m_observers)) { |
467 | if (m_observers.contains(observer)) |
468 | observer->willDestroyGlobalObjectInCachedFrame(); |
469 | } |
470 | } |
471 | |
472 | void DOMWindow::willDestroyDocumentInFrame() |
473 | { |
474 | // It is necessary to copy m_observers to a separate vector because the Observer may |
475 | // unregister themselves from the DOMWindow as a result of the call to willDestroyGlobalObjectInFrame. |
476 | for (auto* observer : copyToVector(m_observers)) { |
477 | if (m_observers.contains(observer)) |
478 | observer->willDestroyGlobalObjectInFrame(); |
479 | } |
480 | } |
481 | |
482 | void DOMWindow::willDetachDocumentFromFrame() |
483 | { |
484 | if (!frame()) |
485 | return; |
486 | |
487 | RELEASE_ASSERT(!m_isSuspendingObservers); |
488 | |
489 | // It is necessary to copy m_observers to a separate vector because the Observer may |
490 | // unregister themselves from the DOMWindow as a result of the call to willDetachGlobalObjectFromFrame. |
491 | for (auto& observer : copyToVector(m_observers)) { |
492 | if (m_observers.contains(observer)) |
493 | observer->willDetachGlobalObjectFromFrame(); |
494 | } |
495 | |
496 | if (m_performance) |
497 | m_performance->clearResourceTimings(); |
498 | |
499 | JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(this); |
500 | InspectorInstrumentation::frameWindowDiscarded(*frame(), this); |
501 | } |
502 | |
503 | #if ENABLE(GAMEPAD) |
504 | |
505 | void DOMWindow::incrementGamepadEventListenerCount() |
506 | { |
507 | if (++m_gamepadEventListenerCount == 1) |
508 | GamepadManager::singleton().registerDOMWindow(this); |
509 | } |
510 | |
511 | void DOMWindow::decrementGamepadEventListenerCount() |
512 | { |
513 | ASSERT(m_gamepadEventListenerCount); |
514 | |
515 | if (!--m_gamepadEventListenerCount) |
516 | GamepadManager::singleton().unregisterDOMWindow(this); |
517 | } |
518 | |
519 | #endif |
520 | |
521 | void DOMWindow::registerObserver(Observer& observer) |
522 | { |
523 | m_observers.add(&observer); |
524 | } |
525 | |
526 | void DOMWindow::unregisterObserver(Observer& observer) |
527 | { |
528 | m_observers.remove(&observer); |
529 | } |
530 | |
531 | void DOMWindow::resetUnlessSuspendedForDocumentSuspension() |
532 | { |
533 | if (m_suspendedForDocumentSuspension) |
534 | return; |
535 | willDestroyDocumentInFrame(); |
536 | } |
537 | |
538 | void DOMWindow::suspendForPageCache() |
539 | { |
540 | SetForScope<bool> isSuspendingObservers(m_isSuspendingObservers, true); |
541 | RELEASE_ASSERT(frame()); |
542 | |
543 | for (auto* observer : copyToVector(m_observers)) { |
544 | if (m_observers.contains(observer)) |
545 | observer->suspendForPageCache(); |
546 | } |
547 | RELEASE_ASSERT(frame()); |
548 | |
549 | m_suspendedForDocumentSuspension = true; |
550 | } |
551 | |
552 | void DOMWindow::resumeFromPageCache() |
553 | { |
554 | for (auto* observer : copyToVector(m_observers)) { |
555 | if (m_observers.contains(observer)) |
556 | observer->resumeFromPageCache(); |
557 | } |
558 | |
559 | m_suspendedForDocumentSuspension = false; |
560 | } |
561 | |
562 | bool DOMWindow::isCurrentlyDisplayedInFrame() const |
563 | { |
564 | auto* frame = this->frame(); |
565 | return frame && frame->document()->domWindow() == this; |
566 | } |
567 | |
568 | CustomElementRegistry& DOMWindow::ensureCustomElementRegistry() |
569 | { |
570 | if (!m_customElementRegistry) |
571 | m_customElementRegistry = CustomElementRegistry::create(*this, scriptExecutionContext()); |
572 | return *m_customElementRegistry; |
573 | } |
574 | |
575 | static ExceptionOr<SelectorQuery&> selectorQueryInFrame(Frame* frame, const String& selectors) |
576 | { |
577 | if (!frame) |
578 | return Exception { NotSupportedError }; |
579 | |
580 | Document* document = frame->document(); |
581 | if (!document) |
582 | return Exception { NotSupportedError }; |
583 | |
584 | return document->selectorQueryForString(selectors); |
585 | } |
586 | |
587 | ExceptionOr<Ref<NodeList>> DOMWindow::collectMatchingElementsInFlatTree(Node& scope, const String& selectors) |
588 | { |
589 | auto queryOrException = selectorQueryInFrame(frame(), selectors); |
590 | if (queryOrException.hasException()) |
591 | return queryOrException.releaseException(); |
592 | |
593 | if (!is<ContainerNode>(scope)) |
594 | return Ref<NodeList> { StaticElementList::create() }; |
595 | |
596 | SelectorQuery& query = queryOrException.releaseReturnValue(); |
597 | |
598 | Vector<Ref<Element>> result; |
599 | for (auto& node : composedTreeDescendants(downcast<ContainerNode>(scope))) { |
600 | if (is<Element>(node) && query.matches(downcast<Element>(node)) && !node.isInUserAgentShadowTree()) |
601 | result.append(downcast<Element>(node)); |
602 | } |
603 | |
604 | return Ref<NodeList> { StaticElementList::create(WTFMove(result)) }; |
605 | } |
606 | |
607 | ExceptionOr<RefPtr<Element>> DOMWindow::matchingElementInFlatTree(Node& scope, const String& selectors) |
608 | { |
609 | auto queryOrException = selectorQueryInFrame(frame(), selectors); |
610 | if (queryOrException.hasException()) |
611 | return queryOrException.releaseException(); |
612 | |
613 | if (!is<ContainerNode>(scope)) |
614 | return RefPtr<Element> { nullptr }; |
615 | |
616 | SelectorQuery& query = queryOrException.releaseReturnValue(); |
617 | |
618 | for (auto& node : composedTreeDescendants(downcast<ContainerNode>(scope))) { |
619 | if (is<Element>(node) && query.matches(downcast<Element>(node)) && !node.isInUserAgentShadowTree()) |
620 | return &downcast<Element>(node); |
621 | } |
622 | |
623 | return RefPtr<Element> { nullptr }; |
624 | } |
625 | |
626 | #if ENABLE(ORIENTATION_EVENTS) |
627 | |
628 | int DOMWindow::orientation() const |
629 | { |
630 | auto* frame = this->frame(); |
631 | if (!frame) |
632 | return 0; |
633 | |
634 | return frame->orientation(); |
635 | } |
636 | |
637 | #endif |
638 | |
639 | Screen& DOMWindow::screen() |
640 | { |
641 | if (!m_screen) |
642 | m_screen = Screen::create(*this); |
643 | return *m_screen; |
644 | } |
645 | |
646 | History& DOMWindow::history() |
647 | { |
648 | if (!m_history) |
649 | m_history = History::create(*this); |
650 | return *m_history; |
651 | } |
652 | |
653 | Crypto& DOMWindow::crypto() const |
654 | { |
655 | if (!m_crypto) |
656 | m_crypto = Crypto::create(document()); |
657 | ASSERT(m_crypto->scriptExecutionContext() == document()); |
658 | return *m_crypto; |
659 | } |
660 | |
661 | BarProp& DOMWindow::locationbar() |
662 | { |
663 | if (!m_locationbar) |
664 | m_locationbar = BarProp::create(*this, BarProp::Locationbar); |
665 | return *m_locationbar; |
666 | } |
667 | |
668 | BarProp& DOMWindow::() |
669 | { |
670 | if (!m_menubar) |
671 | m_menubar = BarProp::create(*this, BarProp::Menubar); |
672 | return *m_menubar; |
673 | } |
674 | |
675 | BarProp& DOMWindow::personalbar() |
676 | { |
677 | if (!m_personalbar) |
678 | m_personalbar = BarProp::create(*this, BarProp::Personalbar); |
679 | return *m_personalbar; |
680 | } |
681 | |
682 | BarProp& DOMWindow::scrollbars() |
683 | { |
684 | if (!m_scrollbars) |
685 | m_scrollbars = BarProp::create(*this, BarProp::Scrollbars); |
686 | return *m_scrollbars; |
687 | } |
688 | |
689 | BarProp& DOMWindow::statusbar() |
690 | { |
691 | if (!m_statusbar) |
692 | m_statusbar = BarProp::create(*this, BarProp::Statusbar); |
693 | return *m_statusbar; |
694 | } |
695 | |
696 | BarProp& DOMWindow::toolbar() |
697 | { |
698 | if (!m_toolbar) |
699 | m_toolbar = BarProp::create(*this, BarProp::Toolbar); |
700 | return *m_toolbar; |
701 | } |
702 | |
703 | PageConsoleClient* DOMWindow::console() const |
704 | { |
705 | // FIXME: This should not return nullptr when frameless. |
706 | if (!isCurrentlyDisplayedInFrame()) |
707 | return nullptr; |
708 | auto* frame = this->frame(); |
709 | return frame->page() ? &frame->page()->console() : nullptr; |
710 | } |
711 | |
712 | DOMApplicationCache& DOMWindow::applicationCache() |
713 | { |
714 | if (!m_applicationCache) |
715 | m_applicationCache = DOMApplicationCache::create(*this); |
716 | return *m_applicationCache; |
717 | } |
718 | |
719 | Navigator& DOMWindow::navigator() |
720 | { |
721 | if (!m_navigator) |
722 | m_navigator = Navigator::create(scriptExecutionContext(), *this); |
723 | ASSERT(m_navigator->scriptExecutionContext() == document()); |
724 | |
725 | return *m_navigator; |
726 | } |
727 | |
728 | Performance& DOMWindow::performance() const |
729 | { |
730 | if (!m_performance) { |
731 | MonotonicTime timeOrigin = document() && document()->loader() ? document()->loader()->timing().referenceMonotonicTime() : MonotonicTime::now(); |
732 | m_performance = Performance::create(document(), timeOrigin); |
733 | } |
734 | ASSERT(m_performance->scriptExecutionContext() == document()); |
735 | return *m_performance; |
736 | } |
737 | |
738 | double DOMWindow::nowTimestamp() const |
739 | { |
740 | return performance().now() / 1000.; |
741 | } |
742 | |
743 | Location& DOMWindow::location() |
744 | { |
745 | if (!m_location) |
746 | m_location = Location::create(*this); |
747 | return *m_location; |
748 | } |
749 | |
750 | VisualViewport& DOMWindow::visualViewport() |
751 | { |
752 | if (!m_visualViewport) |
753 | m_visualViewport = VisualViewport::create(*this); |
754 | return *m_visualViewport; |
755 | } |
756 | |
757 | #if ENABLE(USER_MESSAGE_HANDLERS) |
758 | |
759 | bool DOMWindow::shouldHaveWebKitNamespaceForWorld(DOMWrapperWorld& world) |
760 | { |
761 | auto* frame = this->frame(); |
762 | if (!frame) |
763 | return false; |
764 | |
765 | auto* page = frame->page(); |
766 | if (!page) |
767 | return false; |
768 | |
769 | bool hasUserMessageHandler = false; |
770 | page->userContentProvider().forEachUserMessageHandler([&](const UserMessageHandlerDescriptor& descriptor) { |
771 | if (&descriptor.world() == &world) { |
772 | hasUserMessageHandler = true; |
773 | return; |
774 | } |
775 | }); |
776 | |
777 | return hasUserMessageHandler; |
778 | } |
779 | |
780 | WebKitNamespace* DOMWindow::webkitNamespace() |
781 | { |
782 | if (!isCurrentlyDisplayedInFrame()) |
783 | return nullptr; |
784 | auto* page = frame()->page(); |
785 | if (!page) |
786 | return nullptr; |
787 | if (!m_webkitNamespace) |
788 | m_webkitNamespace = WebKitNamespace::create(*this, page->userContentProvider()); |
789 | return m_webkitNamespace.get(); |
790 | } |
791 | |
792 | #endif |
793 | |
794 | ExceptionOr<Storage*> DOMWindow::sessionStorage() |
795 | { |
796 | if (!isCurrentlyDisplayedInFrame()) |
797 | return nullptr; |
798 | |
799 | auto* document = this->document(); |
800 | if (!document) |
801 | return nullptr; |
802 | |
803 | if (!document->securityOrigin().canAccessSessionStorage(document->topOrigin())) |
804 | return Exception { SecurityError }; |
805 | |
806 | if (m_sessionStorage) |
807 | return m_sessionStorage.get(); |
808 | |
809 | auto* page = document->page(); |
810 | if (!page) |
811 | return nullptr; |
812 | |
813 | auto storageArea = page->sessionStorage()->storageArea(document->securityOrigin().data()); |
814 | m_sessionStorage = Storage::create(*this, WTFMove(storageArea)); |
815 | return m_sessionStorage.get(); |
816 | } |
817 | |
818 | ExceptionOr<Storage*> DOMWindow::localStorage() |
819 | { |
820 | if (!isCurrentlyDisplayedInFrame()) |
821 | return nullptr; |
822 | |
823 | auto* document = this->document(); |
824 | if (!document) |
825 | return nullptr; |
826 | |
827 | if (!document->securityOrigin().canAccessLocalStorage(nullptr)) |
828 | return Exception { SecurityError }; |
829 | |
830 | auto* page = document->page(); |
831 | // FIXME: We should consider supporting access/modification to local storage |
832 | // after calling window.close(). See <https://bugs.webkit.org/show_bug.cgi?id=135330>. |
833 | if (!page || !page->isClosing()) { |
834 | if (m_localStorage) |
835 | return m_localStorage.get(); |
836 | } |
837 | |
838 | if (!page) |
839 | return nullptr; |
840 | |
841 | if (page->isClosing()) |
842 | return nullptr; |
843 | |
844 | if (!page->settings().localStorageEnabled()) |
845 | return nullptr; |
846 | |
847 | auto storageArea = page->storageNamespaceProvider().localStorageArea(*document); |
848 | m_localStorage = Storage::create(*this, WTFMove(storageArea)); |
849 | return m_localStorage.get(); |
850 | } |
851 | |
852 | ExceptionOr<void> DOMWindow::postMessage(JSC::ExecState& state, DOMWindow& incumbentWindow, JSC::JSValue messageValue, const String& targetOrigin, Vector<JSC::Strong<JSC::JSObject>>&& transfer) |
853 | { |
854 | if (!isCurrentlyDisplayedInFrame()) |
855 | return { }; |
856 | |
857 | Document* sourceDocument = incumbentWindow.document(); |
858 | |
859 | // Compute the target origin. We need to do this synchronously in order |
860 | // to generate the SyntaxError exception correctly. |
861 | RefPtr<SecurityOrigin> target; |
862 | if (targetOrigin == "/" ) { |
863 | if (!sourceDocument) |
864 | return { }; |
865 | target = &sourceDocument->securityOrigin(); |
866 | } else if (targetOrigin != "*" ) { |
867 | target = SecurityOrigin::createFromString(targetOrigin); |
868 | // It doesn't make sense target a postMessage at a unique origin |
869 | // because there's no way to represent a unique origin in a string. |
870 | if (target->isUnique()) |
871 | return Exception { SyntaxError }; |
872 | } |
873 | |
874 | Vector<RefPtr<MessagePort>> ports; |
875 | auto messageData = SerializedScriptValue::create(state, messageValue, WTFMove(transfer), ports, SerializationContext::WindowPostMessage); |
876 | if (messageData.hasException()) |
877 | return messageData.releaseException(); |
878 | |
879 | auto disentangledPorts = MessagePort::disentanglePorts(WTFMove(ports)); |
880 | if (disentangledPorts.hasException()) |
881 | return disentangledPorts.releaseException(); |
882 | |
883 | // Capture the source of the message. We need to do this synchronously |
884 | // in order to capture the source of the message correctly. |
885 | if (!sourceDocument) |
886 | return { }; |
887 | auto sourceOrigin = sourceDocument->securityOrigin().toString(); |
888 | |
889 | // Capture stack trace only when inspector front-end is loaded as it may be time consuming. |
890 | RefPtr<ScriptCallStack> stackTrace; |
891 | if (InspectorInstrumentation::consoleAgentEnabled(sourceDocument)) |
892 | stackTrace = createScriptCallStack(JSExecState::currentState()); |
893 | |
894 | MessageWithMessagePorts message { messageData.releaseReturnValue(), disentangledPorts.releaseReturnValue() }; |
895 | |
896 | // Schedule the message. |
897 | RefPtr<WindowProxy> incumbentWindowProxy = incumbentWindow.frame() ? &incumbentWindow.frame()->windowProxy() : nullptr; |
898 | auto* timer = new PostMessageTimer(*this, WTFMove(message), sourceOrigin, WTFMove(incumbentWindowProxy), WTFMove(target), WTFMove(stackTrace)); |
899 | timer->startOneShot(0_s); |
900 | |
901 | InspectorInstrumentation::didPostMessage(*frame(), *timer, state); |
902 | |
903 | return { }; |
904 | } |
905 | |
906 | void DOMWindow::postMessageTimerFired(PostMessageTimer& timer) |
907 | { |
908 | if (!document() || !isCurrentlyDisplayedInFrame()) |
909 | return; |
910 | |
911 | Ref<Frame> frame = *this->frame(); |
912 | if (auto* intendedTargetOrigin = timer.targetOrigin()) { |
913 | // Check target origin now since the target document may have changed since the timer was scheduled. |
914 | if (!intendedTargetOrigin->isSameSchemeHostPort(document()->securityOrigin())) { |
915 | if (auto* pageConsole = console()) { |
916 | String message = makeString("Unable to post message to " , intendedTargetOrigin->toString(), ". Recipient has origin " , document()->securityOrigin().toString(), ".\n" ); |
917 | if (timer.stackTrace()) |
918 | pageConsole->addMessage(MessageSource::Security, MessageLevel::Error, message, *timer.stackTrace()); |
919 | else |
920 | pageConsole->addMessage(MessageSource::Security, MessageLevel::Error, message); |
921 | } |
922 | |
923 | InspectorInstrumentation::didFailPostMessage(frame, timer); |
924 | return; |
925 | } |
926 | } |
927 | |
928 | InspectorInstrumentation::willDispatchPostMessage(frame, timer); |
929 | |
930 | dispatchEvent(timer.event(*document())); |
931 | |
932 | InspectorInstrumentation::didDispatchPostMessage(frame, timer); |
933 | } |
934 | |
935 | DOMSelection* DOMWindow::getSelection() |
936 | { |
937 | if (!isCurrentlyDisplayedInFrame()) |
938 | return nullptr; |
939 | if (!m_selection) |
940 | m_selection = DOMSelection::create(*this); |
941 | return m_selection.get(); |
942 | } |
943 | |
944 | Element* DOMWindow::frameElement() const |
945 | { |
946 | auto* frame = this->frame(); |
947 | if (!frame) |
948 | return nullptr; |
949 | |
950 | return frame->ownerElement(); |
951 | } |
952 | |
953 | void DOMWindow::focus(DOMWindow& incumbentWindow) |
954 | { |
955 | auto* frame = this->frame(); |
956 | auto* openerFrame = frame ? frame->loader().opener() : nullptr; |
957 | focus(openerFrame && openerFrame != frame && incumbentWindow.frame() == openerFrame); |
958 | } |
959 | |
960 | void DOMWindow::focus(bool allowFocus) |
961 | { |
962 | if (!frame()) |
963 | return; |
964 | |
965 | Page* page = frame()->page(); |
966 | if (!page) |
967 | return; |
968 | |
969 | allowFocus = allowFocus || WindowFocusAllowedIndicator::windowFocusAllowed() || !frame()->settings().windowFocusRestricted(); |
970 | |
971 | // If we're a top level window, bring the window to the front. |
972 | if (frame()->isMainFrame() && allowFocus) |
973 | page->chrome().focus(); |
974 | |
975 | if (!frame()) |
976 | return; |
977 | |
978 | // Clear the current frame's focused node if a new frame is about to be focused. |
979 | Frame* focusedFrame = page->focusController().focusedFrame(); |
980 | if (focusedFrame && focusedFrame != frame()) |
981 | focusedFrame->document()->setFocusedElement(nullptr); |
982 | |
983 | // setFocusedElement may clear frame(), so recheck before using it. |
984 | if (auto* frame = this->frame()) |
985 | frame->eventHandler().focusDocumentView(); |
986 | } |
987 | |
988 | void DOMWindow::blur() |
989 | { |
990 | auto* frame = this->frame(); |
991 | if (!frame) |
992 | return; |
993 | |
994 | Page* page = frame->page(); |
995 | if (!page) |
996 | return; |
997 | |
998 | if (frame->settings().windowFocusRestricted()) |
999 | return; |
1000 | |
1001 | if (!frame->isMainFrame()) |
1002 | return; |
1003 | |
1004 | page->chrome().unfocus(); |
1005 | } |
1006 | |
1007 | void DOMWindow::close(Document& document) |
1008 | { |
1009 | if (!document.canNavigate(frame())) |
1010 | return; |
1011 | close(); |
1012 | } |
1013 | |
1014 | void DOMWindow::close() |
1015 | { |
1016 | auto* frame = this->frame(); |
1017 | if (!frame) |
1018 | return; |
1019 | |
1020 | Page* page = frame->page(); |
1021 | if (!page) |
1022 | return; |
1023 | |
1024 | if (!frame->isMainFrame()) |
1025 | return; |
1026 | |
1027 | if (!(page->openedByDOM() || page->backForward().count() <= 1)) { |
1028 | console()->addMessage(MessageSource::JS, MessageLevel::Warning, "Can't close the window since it was not opened by JavaScript"_s ); |
1029 | return; |
1030 | } |
1031 | |
1032 | if (!frame->loader().shouldClose()) |
1033 | return; |
1034 | |
1035 | page->setIsClosing(); |
1036 | page->chrome().closeWindowSoon(); |
1037 | } |
1038 | |
1039 | void DOMWindow::print() |
1040 | { |
1041 | auto* frame = this->frame(); |
1042 | if (!frame) |
1043 | return; |
1044 | |
1045 | auto* page = frame->page(); |
1046 | if (!page) |
1047 | return; |
1048 | |
1049 | if (!page->arePromptsAllowed()) { |
1050 | printErrorMessage("Use of window.print is not allowed while unloading a page." ); |
1051 | return; |
1052 | } |
1053 | |
1054 | if (frame->loader().activeDocumentLoader()->isLoading()) { |
1055 | m_shouldPrintWhenFinishedLoading = true; |
1056 | return; |
1057 | } |
1058 | m_shouldPrintWhenFinishedLoading = false; |
1059 | page->chrome().print(*frame); |
1060 | } |
1061 | |
1062 | void DOMWindow::stop() |
1063 | { |
1064 | auto* frame = this->frame(); |
1065 | if (!frame) |
1066 | return; |
1067 | |
1068 | // We must check whether the load is complete asynchronously, because we might still be parsing |
1069 | // the document until the callstack unwinds. |
1070 | frame->loader().stopForUserCancel(true); |
1071 | } |
1072 | |
1073 | void DOMWindow::alert(const String& message) |
1074 | { |
1075 | auto* frame = this->frame(); |
1076 | if (!frame) |
1077 | return; |
1078 | |
1079 | if (document()->isSandboxed(SandboxModals)) { |
1080 | printErrorMessage("Use of window.alert is not allowed in a sandboxed frame when the allow-modals flag is not set." ); |
1081 | return; |
1082 | } |
1083 | |
1084 | auto* page = frame->page(); |
1085 | if (!page) |
1086 | return; |
1087 | |
1088 | if (!page->arePromptsAllowed()) { |
1089 | printErrorMessage("Use of window.alert is not allowed while unloading a page." ); |
1090 | return; |
1091 | } |
1092 | |
1093 | frame->document()->updateStyleIfNeeded(); |
1094 | #if ENABLE(POINTER_LOCK) |
1095 | page->pointerLockController().requestPointerUnlock(); |
1096 | #endif |
1097 | |
1098 | page->chrome().runJavaScriptAlert(*frame, message); |
1099 | } |
1100 | |
1101 | bool DOMWindow::confirm(const String& message) |
1102 | { |
1103 | auto* frame = this->frame(); |
1104 | if (!frame) |
1105 | return false; |
1106 | |
1107 | if (document()->isSandboxed(SandboxModals)) { |
1108 | printErrorMessage("Use of window.confirm is not allowed in a sandboxed frame when the allow-modals flag is not set." ); |
1109 | return false; |
1110 | } |
1111 | |
1112 | auto* page = frame->page(); |
1113 | if (!page) |
1114 | return false; |
1115 | |
1116 | if (!page->arePromptsAllowed()) { |
1117 | printErrorMessage("Use of window.confirm is not allowed while unloading a page." ); |
1118 | return false; |
1119 | } |
1120 | |
1121 | frame->document()->updateStyleIfNeeded(); |
1122 | #if ENABLE(POINTER_LOCK) |
1123 | page->pointerLockController().requestPointerUnlock(); |
1124 | #endif |
1125 | |
1126 | return page->chrome().runJavaScriptConfirm(*frame, message); |
1127 | } |
1128 | |
1129 | String DOMWindow::prompt(const String& message, const String& defaultValue) |
1130 | { |
1131 | auto* frame = this->frame(); |
1132 | if (!frame) |
1133 | return String(); |
1134 | |
1135 | if (document()->isSandboxed(SandboxModals)) { |
1136 | printErrorMessage("Use of window.prompt is not allowed in a sandboxed frame when the allow-modals flag is not set." ); |
1137 | return String(); |
1138 | } |
1139 | |
1140 | auto* page = frame->page(); |
1141 | if (!page) |
1142 | return String(); |
1143 | |
1144 | if (!page->arePromptsAllowed()) { |
1145 | printErrorMessage("Use of window.prompt is not allowed while unloading a page." ); |
1146 | return String(); |
1147 | } |
1148 | |
1149 | frame->document()->updateStyleIfNeeded(); |
1150 | #if ENABLE(POINTER_LOCK) |
1151 | page->pointerLockController().requestPointerUnlock(); |
1152 | #endif |
1153 | |
1154 | String returnValue; |
1155 | if (page->chrome().runJavaScriptPrompt(*frame, message, defaultValue, returnValue)) |
1156 | return returnValue; |
1157 | |
1158 | return String(); |
1159 | } |
1160 | |
1161 | bool DOMWindow::find(const String& string, bool caseSensitive, bool backwards, bool wrap, bool /*wholeWord*/, bool /*searchInFrames*/, bool /*showDialog*/) const |
1162 | { |
1163 | if (!isCurrentlyDisplayedInFrame()) |
1164 | return false; |
1165 | |
1166 | // FIXME (13016): Support wholeWord, searchInFrames and showDialog. |
1167 | FindOptions options { DoNotTraverseFlatTree }; |
1168 | if (backwards) |
1169 | options.add(Backwards); |
1170 | if (!caseSensitive) |
1171 | options.add(CaseInsensitive); |
1172 | if (wrap) |
1173 | options.add(WrapAround); |
1174 | return frame()->editor().findString(string, options); |
1175 | } |
1176 | |
1177 | bool DOMWindow::offscreenBuffering() const |
1178 | { |
1179 | return true; |
1180 | } |
1181 | |
1182 | int DOMWindow::outerHeight() const |
1183 | { |
1184 | #if PLATFORM(IOS_FAMILY) |
1185 | if (!frame()) |
1186 | return 0; |
1187 | |
1188 | auto* view = frame()->isMainFrame() ? frame()->view() : frame()->mainFrame().view(); |
1189 | if (!view) |
1190 | return 0; |
1191 | |
1192 | return view->frameRect().height(); |
1193 | #else |
1194 | auto* frame = this->frame(); |
1195 | if (!frame) |
1196 | return 0; |
1197 | |
1198 | Page* page = frame->page(); |
1199 | if (!page) |
1200 | return 0; |
1201 | |
1202 | return static_cast<int>(page->chrome().windowRect().height()); |
1203 | #endif |
1204 | } |
1205 | |
1206 | int DOMWindow::outerWidth() const |
1207 | { |
1208 | #if PLATFORM(IOS_FAMILY) |
1209 | if (!frame()) |
1210 | return 0; |
1211 | |
1212 | auto* view = frame()->isMainFrame() ? frame()->view() : frame()->mainFrame().view(); |
1213 | if (!view) |
1214 | return 0; |
1215 | |
1216 | return view->frameRect().width(); |
1217 | #else |
1218 | auto* frame = this->frame(); |
1219 | if (!frame) |
1220 | return 0; |
1221 | |
1222 | Page* page = frame->page(); |
1223 | if (!page) |
1224 | return 0; |
1225 | |
1226 | return static_cast<int>(page->chrome().windowRect().width()); |
1227 | #endif |
1228 | } |
1229 | |
1230 | int DOMWindow::innerHeight() const |
1231 | { |
1232 | auto* frame = this->frame(); |
1233 | if (!frame) |
1234 | return 0; |
1235 | |
1236 | // Force enough layout in the parent document to ensure that the FrameView has been resized. |
1237 | if (auto* frameElement = this->frameElement()) |
1238 | frameElement->document().updateLayoutIfDimensionsOutOfDate(*frameElement, HeightDimensionsCheck); |
1239 | |
1240 | FrameView* view = frame->view(); |
1241 | if (!view) |
1242 | return 0; |
1243 | |
1244 | return view->mapFromLayoutToCSSUnits(static_cast<int>(view->unobscuredContentRectIncludingScrollbars().height())); |
1245 | } |
1246 | |
1247 | int DOMWindow::innerWidth() const |
1248 | { |
1249 | auto* frame = this->frame(); |
1250 | if (!frame) |
1251 | return 0; |
1252 | |
1253 | // Force enough layout in the parent document to ensure that the FrameView has been resized. |
1254 | if (auto* frameElement = this->frameElement()) |
1255 | frameElement->document().updateLayoutIfDimensionsOutOfDate(*frameElement, WidthDimensionsCheck); |
1256 | |
1257 | FrameView* view = frame->view(); |
1258 | if (!view) |
1259 | return 0; |
1260 | |
1261 | return view->mapFromLayoutToCSSUnits(static_cast<int>(view->unobscuredContentRectIncludingScrollbars().width())); |
1262 | } |
1263 | |
1264 | int DOMWindow::screenX() const |
1265 | { |
1266 | auto* frame = this->frame(); |
1267 | if (!frame) |
1268 | return 0; |
1269 | |
1270 | Page* page = frame->page(); |
1271 | if (!page) |
1272 | return 0; |
1273 | |
1274 | return static_cast<int>(page->chrome().windowRect().x()); |
1275 | } |
1276 | |
1277 | int DOMWindow::screenY() const |
1278 | { |
1279 | auto* frame = this->frame(); |
1280 | if (!frame) |
1281 | return 0; |
1282 | |
1283 | Page* page = frame->page(); |
1284 | if (!page) |
1285 | return 0; |
1286 | |
1287 | return static_cast<int>(page->chrome().windowRect().y()); |
1288 | } |
1289 | |
1290 | int DOMWindow::scrollX() const |
1291 | { |
1292 | auto* frame = this->frame(); |
1293 | if (!frame) |
1294 | return 0; |
1295 | |
1296 | FrameView* view = frame->view(); |
1297 | if (!view) |
1298 | return 0; |
1299 | |
1300 | int scrollX = view->contentsScrollPosition().x(); |
1301 | if (!scrollX) |
1302 | return 0; |
1303 | |
1304 | frame->document()->updateLayoutIgnorePendingStylesheets(); |
1305 | |
1306 | return view->mapFromLayoutToCSSUnits(view->contentsScrollPosition().x()); |
1307 | } |
1308 | |
1309 | int DOMWindow::scrollY() const |
1310 | { |
1311 | auto* frame = this->frame(); |
1312 | if (!frame) |
1313 | return 0; |
1314 | |
1315 | FrameView* view = frame->view(); |
1316 | if (!view) |
1317 | return 0; |
1318 | |
1319 | int scrollY = view->contentsScrollPosition().y(); |
1320 | if (!scrollY) |
1321 | return 0; |
1322 | |
1323 | frame->document()->updateLayoutIgnorePendingStylesheets(); |
1324 | |
1325 | return view->mapFromLayoutToCSSUnits(view->contentsScrollPosition().y()); |
1326 | } |
1327 | |
1328 | bool DOMWindow::closed() const |
1329 | { |
1330 | auto* frame = this->frame(); |
1331 | if (!frame) |
1332 | return true; |
1333 | |
1334 | auto* page = frame->page(); |
1335 | return !page || page->isClosing(); |
1336 | } |
1337 | |
1338 | unsigned DOMWindow::length() const |
1339 | { |
1340 | if (!isCurrentlyDisplayedInFrame()) |
1341 | return 0; |
1342 | |
1343 | return frame()->tree().scopedChildCount(); |
1344 | } |
1345 | |
1346 | String DOMWindow::name() const |
1347 | { |
1348 | auto* frame = this->frame(); |
1349 | if (!frame) |
1350 | return String(); |
1351 | |
1352 | return frame->tree().name(); |
1353 | } |
1354 | |
1355 | void DOMWindow::setName(const String& string) |
1356 | { |
1357 | auto* frame = this->frame(); |
1358 | if (!frame) |
1359 | return; |
1360 | |
1361 | frame->tree().setName(string); |
1362 | } |
1363 | |
1364 | void DOMWindow::setStatus(const String& string) |
1365 | { |
1366 | m_status = string; |
1367 | |
1368 | auto* frame = this->frame(); |
1369 | if (!frame) |
1370 | return; |
1371 | |
1372 | Page* page = frame->page(); |
1373 | if (!page) |
1374 | return; |
1375 | |
1376 | ASSERT(frame->document()); // Client calls shouldn't be made when the frame is in inconsistent state. |
1377 | page->chrome().setStatusbarText(*frame, m_status); |
1378 | } |
1379 | |
1380 | void DOMWindow::setDefaultStatus(const String& string) |
1381 | { |
1382 | m_defaultStatus = string; |
1383 | |
1384 | auto* frame = this->frame(); |
1385 | if (!frame) |
1386 | return; |
1387 | |
1388 | Page* page = frame->page(); |
1389 | if (!page) |
1390 | return; |
1391 | |
1392 | ASSERT(frame->document()); // Client calls shouldn't be made when the frame is in inconsistent state. |
1393 | page->chrome().setStatusbarText(*frame, m_defaultStatus); |
1394 | } |
1395 | |
1396 | WindowProxy* DOMWindow::opener() const |
1397 | { |
1398 | auto* frame = this->frame(); |
1399 | if (!frame) |
1400 | return nullptr; |
1401 | |
1402 | auto* openerFrame = frame->loader().opener(); |
1403 | if (!openerFrame) |
1404 | return nullptr; |
1405 | |
1406 | return &openerFrame->windowProxy(); |
1407 | } |
1408 | |
1409 | void DOMWindow::disownOpener() |
1410 | { |
1411 | if (auto* frame = this->frame()) |
1412 | frame->loader().setOpener(nullptr); |
1413 | } |
1414 | |
1415 | WindowProxy* DOMWindow::parent() const |
1416 | { |
1417 | auto* frame = this->frame(); |
1418 | if (!frame) |
1419 | return nullptr; |
1420 | |
1421 | auto* parentFrame = frame->tree().parent(); |
1422 | if (parentFrame) |
1423 | return &parentFrame->windowProxy(); |
1424 | |
1425 | return &frame->windowProxy(); |
1426 | } |
1427 | |
1428 | WindowProxy* DOMWindow::top() const |
1429 | { |
1430 | auto* frame = this->frame(); |
1431 | if (!frame) |
1432 | return nullptr; |
1433 | |
1434 | if (!frame->page()) |
1435 | return nullptr; |
1436 | |
1437 | return &frame->tree().top().windowProxy(); |
1438 | } |
1439 | |
1440 | String DOMWindow::origin() const |
1441 | { |
1442 | auto document = this->document(); |
1443 | return document ? document->securityOrigin().toString() : emptyString(); |
1444 | } |
1445 | |
1446 | Document* DOMWindow::document() const |
1447 | { |
1448 | return downcast<Document>(ContextDestructionObserver::scriptExecutionContext()); |
1449 | } |
1450 | |
1451 | StyleMedia& DOMWindow::styleMedia() |
1452 | { |
1453 | if (!m_media) |
1454 | m_media = StyleMedia::create(*this); |
1455 | return *m_media; |
1456 | } |
1457 | |
1458 | Ref<CSSStyleDeclaration> DOMWindow::getComputedStyle(Element& element, const String& pseudoElt) const |
1459 | { |
1460 | return CSSComputedStyleDeclaration::create(element, false, pseudoElt); |
1461 | } |
1462 | |
1463 | RefPtr<CSSRuleList> DOMWindow::getMatchedCSSRules(Element* element, const String& pseudoElement, bool authorOnly) const |
1464 | { |
1465 | if (!isCurrentlyDisplayedInFrame()) |
1466 | return nullptr; |
1467 | |
1468 | unsigned colonStart = pseudoElement[0] == ':' ? (pseudoElement[1] == ':' ? 2 : 1) : 0; |
1469 | CSSSelector::PseudoElementType pseudoType = CSSSelector::parsePseudoElementType(pseudoElement.substringSharingImpl(colonStart)); |
1470 | if (pseudoType == CSSSelector::PseudoElementUnknown && !pseudoElement.isEmpty()) |
1471 | return nullptr; |
1472 | |
1473 | auto* frame = this->frame(); |
1474 | frame->document()->styleScope().flushPendingUpdate(); |
1475 | |
1476 | unsigned rulesToInclude = StyleResolver::AuthorCSSRules; |
1477 | if (!authorOnly) |
1478 | rulesToInclude |= StyleResolver::UAAndUserCSSRules; |
1479 | |
1480 | PseudoId pseudoId = CSSSelector::pseudoId(pseudoType); |
1481 | |
1482 | auto matchedRules = frame->document()->styleScope().resolver().pseudoStyleRulesForElement(element, pseudoId, rulesToInclude); |
1483 | if (matchedRules.isEmpty()) |
1484 | return nullptr; |
1485 | |
1486 | bool allowCrossOrigin = frame->settings().crossOriginCheckInGetMatchedCSSRulesDisabled(); |
1487 | |
1488 | auto ruleList = StaticCSSRuleList::create(); |
1489 | for (auto& rule : matchedRules) { |
1490 | if (!allowCrossOrigin && !rule->hasDocumentSecurityOrigin()) |
1491 | continue; |
1492 | ruleList->rules().append(rule->createCSSOMWrapper()); |
1493 | } |
1494 | |
1495 | if (ruleList->rules().isEmpty()) |
1496 | return nullptr; |
1497 | |
1498 | return ruleList; |
1499 | } |
1500 | |
1501 | RefPtr<WebKitPoint> DOMWindow::webkitConvertPointFromNodeToPage(Node* node, const WebKitPoint* p) const |
1502 | { |
1503 | if (!node || !p) |
1504 | return nullptr; |
1505 | |
1506 | if (!document()) |
1507 | return nullptr; |
1508 | |
1509 | document()->updateLayoutIgnorePendingStylesheets(); |
1510 | |
1511 | FloatPoint pagePoint(p->x(), p->y()); |
1512 | pagePoint = node->convertToPage(pagePoint); |
1513 | return WebKitPoint::create(pagePoint.x(), pagePoint.y()); |
1514 | } |
1515 | |
1516 | RefPtr<WebKitPoint> DOMWindow::webkitConvertPointFromPageToNode(Node* node, const WebKitPoint* p) const |
1517 | { |
1518 | if (!node || !p) |
1519 | return nullptr; |
1520 | |
1521 | if (!document()) |
1522 | return nullptr; |
1523 | |
1524 | document()->updateLayoutIgnorePendingStylesheets(); |
1525 | |
1526 | FloatPoint nodePoint(p->x(), p->y()); |
1527 | nodePoint = node->convertFromPage(nodePoint); |
1528 | return WebKitPoint::create(nodePoint.x(), nodePoint.y()); |
1529 | } |
1530 | |
1531 | double DOMWindow::devicePixelRatio() const |
1532 | { |
1533 | auto* frame = this->frame(); |
1534 | if (!frame) |
1535 | return 0.0; |
1536 | |
1537 | Page* page = frame->page(); |
1538 | if (!page) |
1539 | return 0.0; |
1540 | |
1541 | return page->deviceScaleFactor(); |
1542 | } |
1543 | |
1544 | void DOMWindow::scrollBy(double x, double y) const |
1545 | { |
1546 | scrollBy({ x, y }); |
1547 | } |
1548 | |
1549 | void DOMWindow::scrollBy(const ScrollToOptions& options) const |
1550 | { |
1551 | if (!isCurrentlyDisplayedInFrame()) |
1552 | return; |
1553 | |
1554 | document()->updateLayoutIgnorePendingStylesheets(); |
1555 | |
1556 | FrameView* view = frame()->view(); |
1557 | if (!view) |
1558 | return; |
1559 | |
1560 | ScrollToOptions scrollToOptions = normalizeNonFiniteCoordinatesOrFallBackTo(options, 0, 0); |
1561 | scrollToOptions.left.value() += view->mapFromLayoutToCSSUnits(view->contentsScrollPosition().x()); |
1562 | scrollToOptions.top.value() += view->mapFromLayoutToCSSUnits(view->contentsScrollPosition().y()); |
1563 | scrollTo(scrollToOptions); |
1564 | } |
1565 | |
1566 | void DOMWindow::scrollTo(double x, double y, ScrollClamping clamping) const |
1567 | { |
1568 | scrollTo({ x, y }, clamping); |
1569 | } |
1570 | |
1571 | void DOMWindow::scrollTo(const ScrollToOptions& options, ScrollClamping) const |
1572 | { |
1573 | if (!isCurrentlyDisplayedInFrame()) |
1574 | return; |
1575 | |
1576 | RefPtr<FrameView> view = frame()->view(); |
1577 | if (!view) |
1578 | return; |
1579 | |
1580 | ScrollToOptions scrollToOptions = normalizeNonFiniteCoordinatesOrFallBackTo(options, |
1581 | view->contentsScrollPosition().x(), view->contentsScrollPosition().y() |
1582 | ); |
1583 | |
1584 | if (!scrollToOptions.left.value() && !scrollToOptions.top.value() && view->contentsScrollPosition() == IntPoint(0, 0)) |
1585 | return; |
1586 | |
1587 | document()->updateLayoutIgnorePendingStylesheets(); |
1588 | |
1589 | IntPoint layoutPos(view->mapFromCSSToLayoutUnits(scrollToOptions.left.value()), view->mapFromCSSToLayoutUnits(scrollToOptions.top.value())); |
1590 | view->setContentsScrollPosition(layoutPos); |
1591 | } |
1592 | |
1593 | bool DOMWindow::allowedToChangeWindowGeometry() const |
1594 | { |
1595 | auto* frame = this->frame(); |
1596 | if (!frame) |
1597 | return false; |
1598 | if (!frame->page()) |
1599 | return false; |
1600 | if (!frame->isMainFrame()) |
1601 | return false; |
1602 | // Prevent web content from tricking the user into initiating a drag. |
1603 | if (frame->eventHandler().mousePressed()) |
1604 | return false; |
1605 | return true; |
1606 | } |
1607 | |
1608 | void DOMWindow::moveBy(float x, float y) const |
1609 | { |
1610 | if (!allowedToChangeWindowGeometry()) |
1611 | return; |
1612 | |
1613 | auto* page = frame()->page(); |
1614 | FloatRect fr = page->chrome().windowRect(); |
1615 | FloatRect update = fr; |
1616 | update.move(x, y); |
1617 | page->chrome().setWindowRect(adjustWindowRect(*page, update)); |
1618 | } |
1619 | |
1620 | void DOMWindow::moveTo(float x, float y) const |
1621 | { |
1622 | if (!allowedToChangeWindowGeometry()) |
1623 | return; |
1624 | |
1625 | auto* page = frame()->page(); |
1626 | FloatRect fr = page->chrome().windowRect(); |
1627 | FloatRect sr = screenAvailableRect(page->mainFrame().view()); |
1628 | fr.setLocation(sr.location()); |
1629 | FloatRect update = fr; |
1630 | update.move(x, y); |
1631 | page->chrome().setWindowRect(adjustWindowRect(*page, update)); |
1632 | } |
1633 | |
1634 | void DOMWindow::resizeBy(float x, float y) const |
1635 | { |
1636 | if (!allowedToChangeWindowGeometry()) |
1637 | return; |
1638 | |
1639 | auto* page = frame()->page(); |
1640 | FloatRect fr = page->chrome().windowRect(); |
1641 | FloatSize dest = fr.size() + FloatSize(x, y); |
1642 | FloatRect update(fr.location(), dest); |
1643 | page->chrome().setWindowRect(adjustWindowRect(*page, update)); |
1644 | } |
1645 | |
1646 | void DOMWindow::resizeTo(float width, float height) const |
1647 | { |
1648 | if (!allowedToChangeWindowGeometry()) |
1649 | return; |
1650 | |
1651 | auto* page = frame()->page(); |
1652 | FloatRect fr = page->chrome().windowRect(); |
1653 | FloatSize dest = FloatSize(width, height); |
1654 | FloatRect update(fr.location(), dest); |
1655 | page->chrome().setWindowRect(adjustWindowRect(*page, update)); |
1656 | } |
1657 | |
1658 | ExceptionOr<int> DOMWindow::setTimeout(JSC::ExecState& state, std::unique_ptr<ScheduledAction> action, int timeout, Vector<JSC::Strong<JSC::Unknown>>&& arguments) |
1659 | { |
1660 | auto* context = scriptExecutionContext(); |
1661 | if (!context) |
1662 | return Exception { InvalidAccessError }; |
1663 | |
1664 | // FIXME: Should this check really happen here? Or should it happen when code is about to eval? |
1665 | if (action->type() == ScheduledAction::Type::Code) { |
1666 | if (!context->contentSecurityPolicy()->allowEval(&state)) |
1667 | return 0; |
1668 | } |
1669 | |
1670 | action->addArguments(WTFMove(arguments)); |
1671 | |
1672 | return DOMTimer::install(*context, WTFMove(action), Seconds::fromMilliseconds(timeout), true); |
1673 | } |
1674 | |
1675 | void DOMWindow::clearTimeout(int timeoutId) |
1676 | { |
1677 | ScriptExecutionContext* context = scriptExecutionContext(); |
1678 | if (!context) |
1679 | return; |
1680 | DOMTimer::removeById(*context, timeoutId); |
1681 | } |
1682 | |
1683 | ExceptionOr<int> DOMWindow::setInterval(JSC::ExecState& state, std::unique_ptr<ScheduledAction> action, int timeout, Vector<JSC::Strong<JSC::Unknown>>&& arguments) |
1684 | { |
1685 | auto* context = scriptExecutionContext(); |
1686 | if (!context) |
1687 | return Exception { InvalidAccessError }; |
1688 | |
1689 | // FIXME: Should this check really happen here? Or should it happen when code is about to eval? |
1690 | if (action->type() == ScheduledAction::Type::Code) { |
1691 | if (!context->contentSecurityPolicy()->allowEval(&state)) |
1692 | return 0; |
1693 | } |
1694 | |
1695 | action->addArguments(WTFMove(arguments)); |
1696 | |
1697 | return DOMTimer::install(*context, WTFMove(action), Seconds::fromMilliseconds(timeout), false); |
1698 | } |
1699 | |
1700 | void DOMWindow::clearInterval(int timeoutId) |
1701 | { |
1702 | ScriptExecutionContext* context = scriptExecutionContext(); |
1703 | if (!context) |
1704 | return; |
1705 | DOMTimer::removeById(*context, timeoutId); |
1706 | } |
1707 | |
1708 | int DOMWindow::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callback) |
1709 | { |
1710 | auto* document = this->document(); |
1711 | if (!document) |
1712 | return 0; |
1713 | return document->requestAnimationFrame(WTFMove(callback)); |
1714 | } |
1715 | |
1716 | int DOMWindow::webkitRequestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callback) |
1717 | { |
1718 | static bool firstTime = true; |
1719 | if (firstTime && document()) { |
1720 | document()->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, "webkitRequestAnimationFrame() is deprecated and will be removed. Please use requestAnimationFrame() instead."_s ); |
1721 | firstTime = false; |
1722 | } |
1723 | return requestAnimationFrame(WTFMove(callback)); |
1724 | } |
1725 | |
1726 | void DOMWindow::cancelAnimationFrame(int id) |
1727 | { |
1728 | auto* document = this->document(); |
1729 | if (!document) |
1730 | return; |
1731 | document->cancelAnimationFrame(id); |
1732 | } |
1733 | |
1734 | void DOMWindow::createImageBitmap(ImageBitmap::Source&& source, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise) |
1735 | { |
1736 | auto* document = this->document(); |
1737 | if (!document) { |
1738 | promise.reject(InvalidStateError); |
1739 | return; |
1740 | } |
1741 | ImageBitmap::createPromise(*document, WTFMove(source), WTFMove(options), WTFMove(promise)); |
1742 | } |
1743 | |
1744 | void DOMWindow::createImageBitmap(ImageBitmap::Source&& source, int sx, int sy, int sw, int sh, ImageBitmapOptions&& options, ImageBitmap::Promise&& promise) |
1745 | { |
1746 | auto* document = this->document(); |
1747 | if (!document) { |
1748 | promise.reject(InvalidStateError); |
1749 | return; |
1750 | } |
1751 | ImageBitmap::createPromise(*document, WTFMove(source), WTFMove(options), sx, sy, sw, sh, WTFMove(promise)); |
1752 | } |
1753 | |
1754 | bool DOMWindow::isSecureContext() const |
1755 | { |
1756 | auto* document = this->document(); |
1757 | if (!document) |
1758 | return false; |
1759 | return document->isSecureContext(); |
1760 | } |
1761 | |
1762 | static void didAddStorageEventListener(DOMWindow& window) |
1763 | { |
1764 | // Creating these WebCore::Storage objects informs the system that we'd like to receive |
1765 | // notifications about storage events that might be triggered in other processes. Rather |
1766 | // than subscribe to these notifications explicitly, we subscribe to them implicitly to |
1767 | // simplify the work done by the system. |
1768 | window.localStorage(); |
1769 | window.sessionStorage(); |
1770 | } |
1771 | |
1772 | bool DOMWindow::isSameSecurityOriginAsMainFrame() const |
1773 | { |
1774 | auto* frame = this->frame(); |
1775 | if (!frame || !frame->page() || !document()) |
1776 | return false; |
1777 | |
1778 | if (frame->isMainFrame()) |
1779 | return true; |
1780 | |
1781 | Document* mainFrameDocument = frame->mainFrame().document(); |
1782 | |
1783 | if (mainFrameDocument && document()->securityOrigin().canAccess(mainFrameDocument->securityOrigin())) |
1784 | return true; |
1785 | |
1786 | return false; |
1787 | } |
1788 | |
1789 | bool DOMWindow::addEventListener(const AtomicString& eventType, Ref<EventListener>&& listener, const AddEventListenerOptions& options) |
1790 | { |
1791 | if (!EventTarget::addEventListener(eventType, WTFMove(listener), options)) |
1792 | return false; |
1793 | |
1794 | auto* document = this->document(); |
1795 | if (document) { |
1796 | document->addListenerTypeIfNeeded(eventType); |
1797 | if (eventNames().isWheelEventType(eventType)) |
1798 | document->didAddWheelEventHandler(*document); |
1799 | else if (eventNames().isTouchRelatedEventType(*document, eventType)) |
1800 | document->didAddTouchEventHandler(*document); |
1801 | else if (eventType == eventNames().storageEvent) |
1802 | didAddStorageEventListener(*this); |
1803 | } |
1804 | |
1805 | if (eventType == eventNames().unloadEvent) |
1806 | addUnloadEventListener(this); |
1807 | else if (eventType == eventNames().beforeunloadEvent && allowsBeforeUnloadListeners(this)) |
1808 | addBeforeUnloadEventListener(this); |
1809 | #if PLATFORM(IOS_FAMILY) |
1810 | else if (eventType == eventNames().scrollEvent) |
1811 | incrementScrollEventListenersCount(); |
1812 | #endif |
1813 | #if ENABLE(IOS_TOUCH_EVENTS) |
1814 | else if (document && eventNames().isTouchRelatedEventType(*document, eventType)) |
1815 | ++m_touchAndGestureEventListenerCount; |
1816 | #endif |
1817 | #if ENABLE(IOS_GESTURE_EVENTS) |
1818 | else if (eventNames().isGestureEventType(eventType)) |
1819 | ++m_touchAndGestureEventListenerCount; |
1820 | #endif |
1821 | #if ENABLE(GAMEPAD) |
1822 | else if (eventNames().isGamepadEventType(eventType)) |
1823 | incrementGamepadEventListenerCount(); |
1824 | #endif |
1825 | #if ENABLE(DEVICE_ORIENTATION) |
1826 | else if (eventType == eventNames().deviceorientationEvent) |
1827 | startListeningForDeviceOrientationIfNecessary(); |
1828 | else if (eventType == eventNames().devicemotionEvent) |
1829 | startListeningForDeviceMotionIfNecessary(); |
1830 | #endif |
1831 | |
1832 | return true; |
1833 | } |
1834 | |
1835 | #if ENABLE(DEVICE_ORIENTATION) |
1836 | |
1837 | DeviceOrientationController* DOMWindow::deviceOrientationController() const |
1838 | { |
1839 | #if PLATFORM(IOS_FAMILY) |
1840 | return document() ? &document()->deviceOrientationController() : nullptr; |
1841 | #else |
1842 | return DeviceOrientationController::from(page()); |
1843 | #endif |
1844 | } |
1845 | |
1846 | DeviceMotionController* DOMWindow::deviceMotionController() const |
1847 | { |
1848 | #if PLATFORM(IOS_FAMILY) |
1849 | return document() ? &document()->deviceMotionController() : nullptr; |
1850 | #else |
1851 | return DeviceMotionController::from(page()); |
1852 | #endif |
1853 | } |
1854 | |
1855 | bool DOMWindow::isAllowedToUseDeviceMotionOrientation(String& message) const |
1856 | { |
1857 | if (!frame() || !frame()->settings().deviceOrientationEventEnabled()) { |
1858 | message = "API is disabled"_s ; |
1859 | return false; |
1860 | } |
1861 | |
1862 | if (!isSecureContext()) { |
1863 | message = "Browsing context is not secure"_s ; |
1864 | return false; |
1865 | } |
1866 | |
1867 | if (!isSameSecurityOriginAsMainFrame()) { |
1868 | message = "Source frame did not have the same security origin as the main page"_s ; |
1869 | return false; |
1870 | } |
1871 | return true; |
1872 | } |
1873 | |
1874 | bool DOMWindow::isAllowedToAddDeviceMotionOrientationListener(String& message) const |
1875 | { |
1876 | String innerMessage; |
1877 | if (!isAllowedToUseDeviceMotionOrientation(innerMessage)) { |
1878 | message = makeString("Blocked attempt to add a device motion or orientation event listener, reason: " , innerMessage, "." ); |
1879 | return false; |
1880 | } |
1881 | |
1882 | if (frame()->settings().deviceOrientationPermissionAPIEnabled()) { |
1883 | auto accessState = document()->deviceOrientationAndMotionAccessController().accessState(); |
1884 | switch (accessState) { |
1885 | case DeviceOrientationOrMotionPermissionState::Denied: |
1886 | message = "No device motion or orientation events will be fired because permission to use the API was denied."_s ; |
1887 | return false; |
1888 | case DeviceOrientationOrMotionPermissionState::Prompt: |
1889 | message = "No device motion or orientation events will be fired until permission has been requested and granted."_s ; |
1890 | return false; |
1891 | case DeviceOrientationOrMotionPermissionState::Granted: |
1892 | break; |
1893 | } |
1894 | } |
1895 | |
1896 | return true; |
1897 | } |
1898 | |
1899 | void DOMWindow::startListeningForDeviceOrientationIfNecessary() |
1900 | { |
1901 | if (!hasEventListeners(eventNames().deviceorientationEvent)) |
1902 | return; |
1903 | |
1904 | auto* deviceController = deviceOrientationController(); |
1905 | if (!deviceController || deviceController->hasDeviceEventListener(*this)) |
1906 | return; |
1907 | |
1908 | String errorMessage; |
1909 | if (!isAllowedToAddDeviceMotionOrientationListener(errorMessage)) { |
1910 | if (auto* document = this->document()) |
1911 | document->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, errorMessage); |
1912 | return; |
1913 | } |
1914 | |
1915 | deviceController->addDeviceEventListener(*this); |
1916 | } |
1917 | |
1918 | void DOMWindow::stopListeningForDeviceOrientationIfNecessary() |
1919 | { |
1920 | if (hasEventListeners(eventNames().deviceorientationEvent)) |
1921 | return; |
1922 | |
1923 | if (auto* deviceController = deviceOrientationController()) |
1924 | deviceController->removeDeviceEventListener(*this); |
1925 | } |
1926 | |
1927 | void DOMWindow::startListeningForDeviceMotionIfNecessary() |
1928 | { |
1929 | if (!hasEventListeners(eventNames().devicemotionEvent)) |
1930 | return; |
1931 | |
1932 | auto* deviceController = deviceMotionController(); |
1933 | if (!deviceController || deviceController->hasDeviceEventListener(*this)) |
1934 | return; |
1935 | |
1936 | String errorMessage; |
1937 | if (!isAllowedToAddDeviceMotionOrientationListener(errorMessage)) { |
1938 | failedToRegisterDeviceMotionEventListener(); |
1939 | if (auto* document = this->document()) |
1940 | document->addConsoleMessage(MessageSource::JS, MessageLevel::Warning, errorMessage); |
1941 | return; |
1942 | } |
1943 | |
1944 | deviceController->addDeviceEventListener(*this); |
1945 | } |
1946 | |
1947 | void DOMWindow::stopListeningForDeviceMotionIfNecessary() |
1948 | { |
1949 | if (hasEventListeners(eventNames().devicemotionEvent)) |
1950 | return; |
1951 | |
1952 | if (auto* deviceController = deviceMotionController()) |
1953 | deviceController->removeDeviceEventListener(*this); |
1954 | } |
1955 | |
1956 | void DOMWindow::failedToRegisterDeviceMotionEventListener() |
1957 | { |
1958 | #if PLATFORM(IOS_FAMILY) |
1959 | if (!isSameSecurityOriginAsMainFrame() || !isSecureContext()) |
1960 | return; |
1961 | |
1962 | // FIXME: This is a quirk for chase.com on iPad (<rdar://problem/48423023>). |
1963 | if (RegistrableDomain::uncheckedCreateFromRegistrableDomainString("chase.com"_s ).matches(document()->url())) { |
1964 | // Fire a fake DeviceMotionEvent with acceleration data to unblock the site's login flow. |
1965 | document()->postTask([](auto& context) { |
1966 | if (auto* window = downcast<Document>(context).domWindow()) { |
1967 | auto acceleration = DeviceMotionData::Acceleration::create(); |
1968 | window->dispatchEvent(DeviceMotionEvent::create(eventNames().devicemotionEvent, DeviceMotionData::create(acceleration.copyRef(), acceleration.copyRef(), DeviceMotionData::RotationRate::create(), WTF::nullopt).ptr())); |
1969 | } |
1970 | }); |
1971 | } |
1972 | #endif // PLATFORM(IOS_FAMILY) |
1973 | } |
1974 | |
1975 | #endif // ENABLE(DEVICE_ORIENTATION) |
1976 | |
1977 | #if PLATFORM(IOS_FAMILY) |
1978 | |
1979 | void DOMWindow::incrementScrollEventListenersCount() |
1980 | { |
1981 | Document* document = this->document(); |
1982 | if (++m_scrollEventListenerCount == 1 && document == &document->topDocument()) { |
1983 | Frame* frame = this->frame(); |
1984 | if (frame && frame->page()) |
1985 | frame->page()->chrome().client().setNeedsScrollNotifications(*frame, true); |
1986 | } |
1987 | } |
1988 | |
1989 | void DOMWindow::decrementScrollEventListenersCount() |
1990 | { |
1991 | Document* document = this->document(); |
1992 | if (!--m_scrollEventListenerCount && document == &document->topDocument()) { |
1993 | Frame* frame = this->frame(); |
1994 | if (frame && frame->page() && document->pageCacheState() == Document::NotInPageCache) |
1995 | frame->page()->chrome().client().setNeedsScrollNotifications(*frame, false); |
1996 | } |
1997 | } |
1998 | |
1999 | #endif |
2000 | |
2001 | void DOMWindow::resetAllGeolocationPermission() |
2002 | { |
2003 | // FIXME: Can we remove the PLATFORM(IOS_FAMILY)-guard? |
2004 | #if ENABLE(GEOLOCATION) && PLATFORM(IOS_FAMILY) |
2005 | if (m_navigator) |
2006 | NavigatorGeolocation::from(m_navigator.get())->resetAllGeolocationPermission(); |
2007 | #endif |
2008 | } |
2009 | |
2010 | bool DOMWindow::removeEventListener(const AtomicString& eventType, EventListener& listener, const ListenerOptions& options) |
2011 | { |
2012 | if (!EventTarget::removeEventListener(eventType, listener, options.capture)) |
2013 | return false; |
2014 | |
2015 | auto* document = this->document(); |
2016 | if (document) { |
2017 | if (eventNames().isWheelEventType(eventType)) |
2018 | document->didRemoveWheelEventHandler(*document); |
2019 | else if (eventNames().isTouchRelatedEventType(*document, eventType)) |
2020 | document->didRemoveTouchEventHandler(*document); |
2021 | } |
2022 | |
2023 | if (eventType == eventNames().unloadEvent) |
2024 | removeUnloadEventListener(this); |
2025 | else if (eventType == eventNames().beforeunloadEvent && allowsBeforeUnloadListeners(this)) |
2026 | removeBeforeUnloadEventListener(this); |
2027 | #if PLATFORM(IOS_FAMILY) |
2028 | else if (eventType == eventNames().scrollEvent) |
2029 | decrementScrollEventListenersCount(); |
2030 | #endif |
2031 | #if ENABLE(IOS_TOUCH_EVENTS) |
2032 | else if (document && eventNames().isTouchRelatedEventType(*document, eventType)) { |
2033 | ASSERT(m_touchAndGestureEventListenerCount > 0); |
2034 | --m_touchAndGestureEventListenerCount; |
2035 | } |
2036 | #endif |
2037 | #if ENABLE(IOS_GESTURE_EVENTS) |
2038 | else if (eventNames().isGestureEventType(eventType)) { |
2039 | ASSERT(m_touchAndGestureEventListenerCount > 0); |
2040 | --m_touchAndGestureEventListenerCount; |
2041 | } |
2042 | #endif |
2043 | #if ENABLE(GAMEPAD) |
2044 | else if (eventNames().isGamepadEventType(eventType)) |
2045 | decrementGamepadEventListenerCount(); |
2046 | #endif |
2047 | #if ENABLE(DEVICE_ORIENTATION) |
2048 | else if (eventType == eventNames().deviceorientationEvent) |
2049 | stopListeningForDeviceOrientationIfNecessary(); |
2050 | else if (eventType == eventNames().devicemotionEvent) |
2051 | stopListeningForDeviceMotionIfNecessary(); |
2052 | #endif |
2053 | |
2054 | return true; |
2055 | } |
2056 | |
2057 | void DOMWindow::languagesChanged() |
2058 | { |
2059 | if (auto* document = this->document()) |
2060 | document->enqueueWindowEvent(Event::create(eventNames().languagechangeEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
2061 | } |
2062 | |
2063 | void DOMWindow::dispatchLoadEvent() |
2064 | { |
2065 | // If we did not protect it, the document loader and its timing subobject might get destroyed |
2066 | // as a side effect of what event handling code does. |
2067 | auto protectedThis = makeRef(*this); |
2068 | auto protectedLoader = makeRefPtr(frame() ? frame()->loader().documentLoader() : nullptr); |
2069 | bool shouldMarkLoadEventTimes = protectedLoader && !protectedLoader->timing().loadEventStart(); |
2070 | |
2071 | if (shouldMarkLoadEventTimes) |
2072 | protectedLoader->timing().markLoadEventStart(); |
2073 | |
2074 | dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No), document()); |
2075 | |
2076 | if (shouldMarkLoadEventTimes) |
2077 | protectedLoader->timing().markLoadEventEnd(); |
2078 | |
2079 | // Send a separate load event to the element that owns this frame. |
2080 | if (frame()) { |
2081 | if (auto* owner = frame()->ownerElement()) |
2082 | owner->dispatchEvent(Event::create(eventNames().loadEvent, Event::CanBubble::No, Event::IsCancelable::No)); |
2083 | } |
2084 | |
2085 | InspectorInstrumentation::loadEventFired(frame()); |
2086 | } |
2087 | |
2088 | void DOMWindow::dispatchEvent(Event& event, EventTarget* target) |
2089 | { |
2090 | // FIXME: It's confusing to have both the inherited EventTarget::dispatchEvent function |
2091 | // and this function, which does something nearly identical but subtly different if |
2092 | // called with a target of null. Most callers pass the document as the target, though. |
2093 | // Fixing this could allow us to remove the special case in DocumentEventQueue::dispatchEvent. |
2094 | |
2095 | auto protectedThis = makeRef(*this); |
2096 | |
2097 | // Pausing a page may trigger pagehide and pageshow events. WebCore also implicitly fires these |
2098 | // events when closing a WebView. Here we keep track of the state of the page to prevent duplicate, |
2099 | // unbalanced events per the definition of the pageshow event: |
2100 | // <http://www.whatwg.org/specs/web-apps/current-work/multipage/history.html#event-pageshow>. |
2101 | // FIXME: This code should go at call sites where pageshowEvent and pagehideEvents are |
2102 | // generated, not here inside the event dispatching process. |
2103 | if (event.eventInterface() == PageTransitionEventInterfaceType) { |
2104 | if (event.type() == eventNames().pageshowEvent) { |
2105 | if (m_lastPageStatus == PageStatus::Shown) |
2106 | return; // Event was previously dispatched; do not fire a duplicate event. |
2107 | m_lastPageStatus = PageStatus::Shown; |
2108 | } else if (event.type() == eventNames().pagehideEvent) { |
2109 | if (m_lastPageStatus == PageStatus::Hidden) |
2110 | return; // Event was previously dispatched; do not fire a duplicate event. |
2111 | m_lastPageStatus = PageStatus::Hidden; |
2112 | } |
2113 | } |
2114 | |
2115 | // FIXME: It doesn't seem right to have the inspector instrumentation here since not all |
2116 | // events dispatched to the window object are guaranteed to flow through this function. |
2117 | // But the instrumentation prevents us from calling EventDispatcher::dispatchEvent here. |
2118 | event.setTarget(target ? target : this); |
2119 | event.setCurrentTarget(this); |
2120 | event.setEventPhase(Event::AT_TARGET); |
2121 | event.resetBeforeDispatch(); |
2122 | auto cookie = InspectorInstrumentation::willDispatchEventOnWindow(frame(), event, *this); |
2123 | // FIXME: We should use EventDispatcher everywhere. |
2124 | fireEventListeners(event, EventInvokePhase::Capturing); |
2125 | fireEventListeners(event, EventInvokePhase::Bubbling); |
2126 | InspectorInstrumentation::didDispatchEventOnWindow(cookie, event.defaultPrevented()); |
2127 | event.resetAfterDispatch(); |
2128 | } |
2129 | |
2130 | void DOMWindow::removeAllEventListeners() |
2131 | { |
2132 | EventTarget::removeAllEventListeners(); |
2133 | |
2134 | #if ENABLE(DEVICE_ORIENTATION) |
2135 | stopListeningForDeviceOrientationIfNecessary(); |
2136 | stopListeningForDeviceMotionIfNecessary(); |
2137 | #endif |
2138 | |
2139 | #if PLATFORM(IOS_FAMILY) |
2140 | if (m_scrollEventListenerCount) { |
2141 | m_scrollEventListenerCount = 1; |
2142 | decrementScrollEventListenersCount(); |
2143 | } |
2144 | #endif |
2145 | |
2146 | #if ENABLE(IOS_TOUCH_EVENTS) || ENABLE(IOS_GESTURE_EVENTS) |
2147 | m_touchAndGestureEventListenerCount = 0; |
2148 | #endif |
2149 | |
2150 | #if ENABLE(TOUCH_EVENTS) |
2151 | if (Document* document = this->document()) |
2152 | document->didRemoveEventTargetNode(*document); |
2153 | #endif |
2154 | |
2155 | if (m_performance) { |
2156 | m_performance->removeAllEventListeners(); |
2157 | m_performance->removeAllObservers(); |
2158 | } |
2159 | |
2160 | removeAllUnloadEventListeners(this); |
2161 | removeAllBeforeUnloadEventListeners(this); |
2162 | } |
2163 | |
2164 | void DOMWindow::captureEvents() |
2165 | { |
2166 | // Not implemented. |
2167 | } |
2168 | |
2169 | void DOMWindow::releaseEvents() |
2170 | { |
2171 | // Not implemented. |
2172 | } |
2173 | |
2174 | void DOMWindow::finishedLoading() |
2175 | { |
2176 | if (m_shouldPrintWhenFinishedLoading) { |
2177 | m_shouldPrintWhenFinishedLoading = false; |
2178 | if (frame()->loader().activeDocumentLoader()->mainDocumentError().isNull()) |
2179 | print(); |
2180 | } |
2181 | } |
2182 | |
2183 | void DOMWindow::setLocation(DOMWindow& activeWindow, const URL& completedURL, SetLocationLocking locking) |
2184 | { |
2185 | if (!isCurrentlyDisplayedInFrame()) |
2186 | return; |
2187 | |
2188 | Document* activeDocument = activeWindow.document(); |
2189 | if (!activeDocument) |
2190 | return; |
2191 | |
2192 | auto* frame = this->frame(); |
2193 | if (!activeDocument->canNavigate(frame, completedURL)) |
2194 | return; |
2195 | |
2196 | if (isInsecureScriptAccess(activeWindow, completedURL)) |
2197 | return; |
2198 | |
2199 | // We want a new history item if we are processing a user gesture. |
2200 | LockHistory lockHistory = (locking != LockHistoryBasedOnGestureState || !UserGestureIndicator::processingUserGesture()) ? LockHistory::Yes : LockHistory::No; |
2201 | LockBackForwardList lockBackForwardList = (locking != LockHistoryBasedOnGestureState) ? LockBackForwardList::Yes : LockBackForwardList::No; |
2202 | frame->navigationScheduler().scheduleLocationChange(*activeDocument, activeDocument->securityOrigin(), |
2203 | // FIXME: What if activeDocument()->frame() is 0? |
2204 | completedURL, activeDocument->frame()->loader().outgoingReferrer(), |
2205 | lockHistory, lockBackForwardList); |
2206 | } |
2207 | |
2208 | void DOMWindow::printErrorMessage(const String& message) |
2209 | { |
2210 | if (message.isEmpty()) |
2211 | return; |
2212 | |
2213 | if (PageConsoleClient* pageConsole = console()) |
2214 | pageConsole->addMessage(MessageSource::JS, MessageLevel::Error, message); |
2215 | } |
2216 | |
2217 | String DOMWindow::crossDomainAccessErrorMessage(const DOMWindow& activeWindow, IncludeTargetOrigin includeTargetOrigin) |
2218 | { |
2219 | const URL& activeWindowURL = activeWindow.document()->url(); |
2220 | if (activeWindowURL.isNull()) |
2221 | return String(); |
2222 | |
2223 | ASSERT(!activeWindow.document()->securityOrigin().canAccess(document()->securityOrigin())); |
2224 | |
2225 | // FIXME: This message, and other console messages, have extra newlines. Should remove them. |
2226 | SecurityOrigin& activeOrigin = activeWindow.document()->securityOrigin(); |
2227 | SecurityOrigin& targetOrigin = document()->securityOrigin(); |
2228 | String message; |
2229 | if (includeTargetOrigin == IncludeTargetOrigin::Yes) |
2230 | message = makeString("Blocked a frame with origin \"" , activeOrigin.toString(), "\" from accessing a frame with origin \"" , targetOrigin.toString(), "\". " ); |
2231 | else |
2232 | message = makeString("Blocked a frame with origin \"" , activeOrigin.toString(), "\" from accessing a cross-origin frame. " ); |
2233 | |
2234 | // Sandbox errors: Use the origin of the frames' location, rather than their actual origin (since we know that at least one will be "null"). |
2235 | URL activeURL = activeWindow.document()->url(); |
2236 | URL targetURL = document()->url(); |
2237 | if (document()->isSandboxed(SandboxOrigin) || activeWindow.document()->isSandboxed(SandboxOrigin)) { |
2238 | if (includeTargetOrigin == IncludeTargetOrigin::Yes) |
2239 | message = makeString("Blocked a frame at \"" , SecurityOrigin::create(activeURL).get().toString(), "\" from accessing a frame at \"" , SecurityOrigin::create(targetURL).get().toString(), "\". " ); |
2240 | else |
2241 | message = makeString("Blocked a frame at \"" , SecurityOrigin::create(activeURL).get().toString(), "\" from accessing a cross-origin frame. " ); |
2242 | |
2243 | if (document()->isSandboxed(SandboxOrigin) && activeWindow.document()->isSandboxed(SandboxOrigin)) |
2244 | return makeString("Sandbox access violation: " , message, " Both frames are sandboxed and lack the \"allow-same-origin\" flag." ); |
2245 | if (document()->isSandboxed(SandboxOrigin)) |
2246 | return makeString("Sandbox access violation: " , message, " The frame being accessed is sandboxed and lacks the \"allow-same-origin\" flag." ); |
2247 | return makeString("Sandbox access violation: " , message, " The frame requesting access is sandboxed and lacks the \"allow-same-origin\" flag." ); |
2248 | } |
2249 | |
2250 | if (includeTargetOrigin == IncludeTargetOrigin::Yes) { |
2251 | // Protocol errors: Use the URL's protocol rather than the origin's protocol so that we get a useful message for non-heirarchal URLs like 'data:'. |
2252 | if (targetOrigin.protocol() != activeOrigin.protocol()) |
2253 | return message + " The frame requesting access has a protocol of \"" + activeURL.protocol() + "\", the frame being accessed has a protocol of \"" + targetURL.protocol() + "\". Protocols must match.\n" ; |
2254 | |
2255 | // 'document.domain' errors. |
2256 | if (targetOrigin.domainWasSetInDOM() && activeOrigin.domainWasSetInDOM()) |
2257 | return message + "The frame requesting access set \"document.domain\" to \"" + activeOrigin.domain() + "\", the frame being accessed set it to \"" + targetOrigin.domain() + "\". Both must set \"document.domain\" to the same value to allow access." ; |
2258 | if (activeOrigin.domainWasSetInDOM()) |
2259 | return message + "The frame requesting access set \"document.domain\" to \"" + activeOrigin.domain() + "\", but the frame being accessed did not. Both must set \"document.domain\" to the same value to allow access." ; |
2260 | if (targetOrigin.domainWasSetInDOM()) |
2261 | return message + "The frame being accessed set \"document.domain\" to \"" + targetOrigin.domain() + "\", but the frame requesting access did not. Both must set \"document.domain\" to the same value to allow access." ; |
2262 | } |
2263 | |
2264 | // Default. |
2265 | return message + "Protocols, domains, and ports must match." ; |
2266 | } |
2267 | |
2268 | bool DOMWindow::isInsecureScriptAccess(DOMWindow& activeWindow, const String& urlString) |
2269 | { |
2270 | if (!WTF::protocolIsJavaScript(urlString)) |
2271 | return false; |
2272 | |
2273 | // If this DOMWindow isn't currently active in the Frame, then there's no |
2274 | // way we should allow the access. |
2275 | // FIXME: Remove this check if we're able to disconnect DOMWindow from |
2276 | // Frame on navigation: https://bugs.webkit.org/show_bug.cgi?id=62054 |
2277 | if (isCurrentlyDisplayedInFrame()) { |
2278 | // FIXME: Is there some way to eliminate the need for a separate "activeWindow == this" check? |
2279 | if (&activeWindow == this) |
2280 | return false; |
2281 | |
2282 | // FIXME: The name canAccess seems to be a roundabout way to ask "can execute script". |
2283 | // Can we name the SecurityOrigin function better to make this more clear? |
2284 | if (activeWindow.document()->securityOrigin().canAccess(document()->securityOrigin())) |
2285 | return false; |
2286 | } |
2287 | |
2288 | printErrorMessage(crossDomainAccessErrorMessage(activeWindow, IncludeTargetOrigin::Yes)); |
2289 | return true; |
2290 | } |
2291 | |
2292 | ExceptionOr<RefPtr<Frame>> DOMWindow::createWindow(const String& urlString, const AtomicString& frameName, const WindowFeatures& windowFeatures, DOMWindow& activeWindow, Frame& firstFrame, Frame& openerFrame, const WTF::Function<void(DOMWindow&)>& prepareDialogFunction) |
2293 | { |
2294 | Frame* activeFrame = activeWindow.frame(); |
2295 | if (!activeFrame) |
2296 | return RefPtr<Frame> { nullptr }; |
2297 | |
2298 | Document* activeDocument = activeWindow.document(); |
2299 | if (!activeDocument) |
2300 | return RefPtr<Frame> { nullptr }; |
2301 | |
2302 | URL completedURL = urlString.isEmpty() ? URL({ }, emptyString()) : firstFrame.document()->completeURL(urlString); |
2303 | if (!completedURL.isEmpty() && !completedURL.isValid()) |
2304 | return Exception { SyntaxError }; |
2305 | |
2306 | // For whatever reason, Firefox uses the first frame to determine the outgoingReferrer. We replicate that behavior here. |
2307 | String referrer = windowFeatures.noreferrer ? String() : SecurityPolicy::generateReferrerHeader(firstFrame.document()->referrerPolicy(), completedURL, firstFrame.loader().outgoingReferrer()); |
2308 | auto initiatedByMainFrame = activeFrame->isMainFrame() ? InitiatedByMainFrame::Yes : InitiatedByMainFrame::Unknown; |
2309 | |
2310 | ResourceRequest resourceRequest { completedURL, referrer }; |
2311 | FrameLoader::addHTTPOriginIfNeeded(resourceRequest, firstFrame.loader().outgoingOrigin()); |
2312 | FrameLoadRequest frameLoadRequest { *activeDocument, activeDocument->securityOrigin(), resourceRequest, frameName, LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, activeDocument->shouldOpenExternalURLsPolicyToPropagate(), initiatedByMainFrame }; |
2313 | |
2314 | // We pass the opener frame for the lookupFrame in case the active frame is different from |
2315 | // the opener frame, and the name references a frame relative to the opener frame. |
2316 | bool created; |
2317 | auto newFrame = WebCore::createWindow(*activeFrame, openerFrame, WTFMove(frameLoadRequest), windowFeatures, created); |
2318 | if (!newFrame) |
2319 | return RefPtr<Frame> { nullptr }; |
2320 | |
2321 | bool noopener = windowFeatures.noopener || windowFeatures.noreferrer; |
2322 | if (!noopener) |
2323 | newFrame->loader().setOpener(&openerFrame); |
2324 | |
2325 | if (created) |
2326 | newFrame->page()->setOpenedByDOM(); |
2327 | |
2328 | if (newFrame->document()->domWindow()->isInsecureScriptAccess(activeWindow, completedURL)) |
2329 | return noopener ? RefPtr<Frame> { nullptr } : newFrame; |
2330 | |
2331 | if (prepareDialogFunction) |
2332 | prepareDialogFunction(*newFrame->document()->domWindow()); |
2333 | |
2334 | if (created) { |
2335 | ResourceRequest resourceRequest { completedURL, referrer, ResourceRequestCachePolicy::UseProtocolCachePolicy }; |
2336 | FrameLoader::addSameSiteInfoToRequestIfNeeded(resourceRequest, openerFrame.document()); |
2337 | FrameLoadRequest frameLoadRequest { *activeWindow.document(), activeWindow.document()->securityOrigin(), resourceRequest, "_self"_s , LockHistory::No, LockBackForwardList::No, MaybeSendReferrer, AllowNavigationToInvalidURL::Yes, NewFrameOpenerPolicy::Allow, activeDocument->shouldOpenExternalURLsPolicyToPropagate(), initiatedByMainFrame }; |
2338 | newFrame->loader().changeLocation(WTFMove(frameLoadRequest)); |
2339 | } else if (!urlString.isEmpty()) { |
2340 | LockHistory lockHistory = UserGestureIndicator::processingUserGesture() ? LockHistory::No : LockHistory::Yes; |
2341 | newFrame->navigationScheduler().scheduleLocationChange(*activeDocument, activeDocument->securityOrigin(), completedURL, referrer, lockHistory, LockBackForwardList::No); |
2342 | } |
2343 | |
2344 | // Navigating the new frame could result in it being detached from its page by a navigation policy delegate. |
2345 | if (!newFrame->page()) |
2346 | return RefPtr<Frame> { nullptr }; |
2347 | |
2348 | return noopener ? RefPtr<Frame> { nullptr } : newFrame; |
2349 | } |
2350 | |
2351 | ExceptionOr<RefPtr<WindowProxy>> DOMWindow::open(DOMWindow& activeWindow, DOMWindow& firstWindow, const String& urlString, const AtomicString& frameName, const String& windowFeaturesString) |
2352 | { |
2353 | if (!isCurrentlyDisplayedInFrame()) |
2354 | return RefPtr<WindowProxy> { nullptr }; |
2355 | |
2356 | auto* activeDocument = activeWindow.document(); |
2357 | if (!activeDocument) |
2358 | return RefPtr<WindowProxy> { nullptr }; |
2359 | |
2360 | auto* firstFrame = firstWindow.frame(); |
2361 | if (!firstFrame) |
2362 | return RefPtr<WindowProxy> { nullptr }; |
2363 | |
2364 | #if ENABLE(CONTENT_EXTENSIONS) |
2365 | if (firstFrame->document() |
2366 | && firstFrame->page() |
2367 | && firstFrame->mainFrame().document() |
2368 | && firstFrame->mainFrame().document()->loader()) { |
2369 | auto results = firstFrame->page()->userContentProvider().processContentRuleListsForLoad(firstFrame->document()->completeURL(urlString), ContentExtensions::ResourceType::Popup, *firstFrame->mainFrame().document()->loader()); |
2370 | if (results.summary.blockedLoad) |
2371 | return RefPtr<WindowProxy> { nullptr }; |
2372 | } |
2373 | #endif |
2374 | |
2375 | auto* frame = this->frame(); |
2376 | if (!firstWindow.allowPopUp()) { |
2377 | // Because FrameTree::findFrameForNavigation() returns true for empty strings, we must check for empty frame names. |
2378 | // Otherwise, illegitimate window.open() calls with no name will pass right through the popup blocker. |
2379 | if (frameName.isEmpty() || !frame->loader().findFrameForNavigation(frameName, activeDocument)) |
2380 | return RefPtr<WindowProxy> { nullptr }; |
2381 | } |
2382 | |
2383 | // Get the target frame for the special cases of _top and _parent. |
2384 | // In those cases, we schedule a location change right now and return early. |
2385 | Frame* targetFrame = nullptr; |
2386 | if (equalIgnoringASCIICase(frameName, "_top" )) |
2387 | targetFrame = &frame->tree().top(); |
2388 | else if (equalIgnoringASCIICase(frameName, "_parent" )) { |
2389 | if (Frame* parent = frame->tree().parent()) |
2390 | targetFrame = parent; |
2391 | else |
2392 | targetFrame = frame; |
2393 | } |
2394 | if (targetFrame) { |
2395 | if (!activeDocument->canNavigate(targetFrame)) |
2396 | return RefPtr<WindowProxy> { nullptr }; |
2397 | |
2398 | URL completedURL = firstFrame->document()->completeURL(urlString); |
2399 | |
2400 | if (targetFrame->document()->domWindow()->isInsecureScriptAccess(activeWindow, completedURL)) |
2401 | return &targetFrame->windowProxy(); |
2402 | |
2403 | if (urlString.isEmpty()) |
2404 | return &targetFrame->windowProxy(); |
2405 | |
2406 | // For whatever reason, Firefox uses the first window rather than the active window to |
2407 | // determine the outgoing referrer. We replicate that behavior here. |
2408 | LockHistory lockHistory = UserGestureIndicator::processingUserGesture() ? LockHistory::No : LockHistory::Yes; |
2409 | targetFrame->navigationScheduler().scheduleLocationChange(*activeDocument, activeDocument->securityOrigin(), completedURL, firstFrame->loader().outgoingReferrer(), |
2410 | lockHistory, LockBackForwardList::No); |
2411 | return &targetFrame->windowProxy(); |
2412 | } |
2413 | |
2414 | auto newFrameOrException = createWindow(urlString, frameName, parseWindowFeatures(windowFeaturesString), activeWindow, *firstFrame, *frame); |
2415 | if (newFrameOrException.hasException()) |
2416 | return newFrameOrException.releaseException(); |
2417 | |
2418 | auto newFrame = newFrameOrException.releaseReturnValue(); |
2419 | return newFrame ? &newFrame->windowProxy() : RefPtr<WindowProxy> { nullptr }; |
2420 | } |
2421 | |
2422 | void DOMWindow::showModalDialog(const String& urlString, const String& dialogFeaturesString, DOMWindow& activeWindow, DOMWindow& firstWindow, const WTF::Function<void (DOMWindow&)>& prepareDialogFunction) |
2423 | { |
2424 | if (!isCurrentlyDisplayedInFrame()) |
2425 | return; |
2426 | if (!activeWindow.frame()) |
2427 | return; |
2428 | Frame* firstFrame = firstWindow.frame(); |
2429 | if (!firstFrame) |
2430 | return; |
2431 | |
2432 | auto* frame = this->frame(); |
2433 | auto* page = frame->page(); |
2434 | if (!page) |
2435 | return; |
2436 | |
2437 | if (!page->arePromptsAllowed()) { |
2438 | printErrorMessage("Use of window.showModalDialog is not allowed while unloading a page." ); |
2439 | return; |
2440 | } |
2441 | |
2442 | if (!canShowModalDialog(*frame) || !firstWindow.allowPopUp()) |
2443 | return; |
2444 | |
2445 | auto dialogFrameOrException = createWindow(urlString, emptyAtom(), parseDialogFeatures(dialogFeaturesString, screenAvailableRect(frame->view())), activeWindow, *firstFrame, *frame, prepareDialogFunction); |
2446 | if (dialogFrameOrException.hasException()) |
2447 | return; |
2448 | RefPtr<Frame> dialogFrame = dialogFrameOrException.releaseReturnValue(); |
2449 | if (!dialogFrame) |
2450 | return; |
2451 | dialogFrame->page()->chrome().runModal(); |
2452 | } |
2453 | |
2454 | void DOMWindow::enableSuddenTermination() |
2455 | { |
2456 | if (Page* page = this->page()) |
2457 | page->chrome().enableSuddenTermination(); |
2458 | } |
2459 | |
2460 | void DOMWindow::disableSuddenTermination() |
2461 | { |
2462 | if (Page* page = this->page()) |
2463 | page->chrome().disableSuddenTermination(); |
2464 | } |
2465 | |
2466 | Frame* DOMWindow::frame() const |
2467 | { |
2468 | auto* document = this->document(); |
2469 | return document ? document->frame() : nullptr; |
2470 | } |
2471 | |
2472 | } // namespace WebCore |
2473 | |