/* * Copyright (C) 2006, 2007, 2008, 2010 Apple Inc. All rights reserved. * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "core/frame/LocalDOMWindow.h" #include "bindings/core/v8/ScriptCallStack.h" #include "bindings/core/v8/ScriptController.h" #include "core/css/CSSComputedStyleDeclaration.h" #include "core/css/CSSRuleList.h" #include "core/css/DOMWindowCSS.h" #include "core/css/MediaQueryList.h" #include "core/css/MediaQueryMatcher.h" #include "core/css/StyleMedia.h" #include "core/css/resolver/StyleResolver.h" #include "core/dom/DOMImplementation.h" #include "core/dom/ExecutionContextTask.h" #include "core/dom/FrameRequestCallback.h" #include "core/dom/SandboxFlags.h" #include "core/editing/Editor.h" #include "core/events/DOMWindowEventQueue.h" #include "core/events/HashChangeEvent.h" #include "core/events/MessageEvent.h" #include "core/events/PageTransitionEvent.h" #include "core/events/PopStateEvent.h" #include "core/events/ScopedEventQueue.h" #include "core/frame/BarProp.h" #include "core/frame/Console.h" #include "core/frame/EventHandlerRegistry.h" #include "core/frame/FrameConsole.h" #include "core/frame/FrameView.h" #include "core/frame/History.h" #include "core/frame/Navigator.h" #include "core/frame/Screen.h" #include "core/frame/ScrollToOptions.h" #include "core/frame/Settings.h" #include "core/frame/SuspendableTimer.h" #include "core/html/HTMLFrameOwnerElement.h" #include "core/input/EventHandler.h" #include "core/inspector/ConsoleMessageStorage.h" #include "core/inspector/InspectorInstrumentation.h" #include "core/loader/DocumentLoader.h" #include "core/loader/FrameLoaderClient.h" #include "core/loader/SinkDocument.h" #include "core/loader/appcache/ApplicationCache.h" #include "core/page/ChromeClient.h" #include "core/page/CreateWindow.h" #include "core/page/Page.h" #include "core/page/WindowFeatures.h" #include "core/page/scrolling/ScrollingCoordinator.h" #include "platform/EventDispatchForbiddenScope.h" #include "platform/weborigin/SecurityOrigin.h" #include "platform/weborigin/Suborigin.h" #include "public/platform/Platform.h" #include "public/platform/WebFrameScheduler.h" #include "public/platform/WebScreenInfo.h" namespace blink { LocalDOMWindow::WindowFrameObserver::WindowFrameObserver(LocalDOMWindow* window, LocalFrame& frame) : LocalFrameLifecycleObserver(&frame) , m_window(window) { } PassOwnPtrWillBeRawPtr LocalDOMWindow::WindowFrameObserver::create(LocalDOMWindow* window, LocalFrame& frame) { return adoptPtrWillBeNoop(new WindowFrameObserver(window, frame)); } #if !ENABLE(OILPAN) LocalDOMWindow::WindowFrameObserver::~WindowFrameObserver() { } #endif DEFINE_TRACE(LocalDOMWindow::WindowFrameObserver) { visitor->trace(m_window); LocalFrameLifecycleObserver::trace(visitor); } void LocalDOMWindow::WindowFrameObserver::willDetachFrameHost() { m_window->willDetachFrameHost(); } void LocalDOMWindow::WindowFrameObserver::contextDestroyed() { m_window->frameDestroyed(); LocalFrameLifecycleObserver::contextDestroyed(); } class PostMessageTimer final : public NoBaseWillBeGarbageCollectedFinalized, public SuspendableTimer { WILL_BE_USING_GARBAGE_COLLECTED_MIXIN(PostMessageTimer); public: PostMessageTimer(LocalDOMWindow& window, PassRefPtrWillBeRawPtr event, SecurityOrigin* targetOrigin, PassRefPtr stackTrace, UserGestureToken* userGestureToken) : SuspendableTimer(window.document()) , m_event(event) , m_window(&window) , m_targetOrigin(targetOrigin) , m_stackTrace(stackTrace) , m_userGestureToken(userGestureToken) , m_disposalAllowed(true) { m_asyncOperationId = InspectorInstrumentation::traceAsyncOperationStarting(getExecutionContext(), "postMessage"); } PassRefPtrWillBeRawPtr event() const { return m_event.get(); } SecurityOrigin* targetOrigin() const { return m_targetOrigin.get(); } ScriptCallStack* stackTrace() const { return m_stackTrace.get(); } UserGestureToken* userGestureToken() const { return m_userGestureToken.get(); } void stop() override { SuspendableTimer::stop(); if (m_disposalAllowed) dispose(); } // Eager finalization is needed to promptly stop this timer object. // (see DOMTimer comment for more.) EAGERLY_FINALIZE(); DEFINE_INLINE_VIRTUAL_TRACE() { visitor->trace(m_event); visitor->trace(m_window); SuspendableTimer::trace(visitor); } // TODO(alexclarke): Override timerTaskRunner() to pass in a document specific default task runner. private: void fired() override { InspectorInstrumentationCookie cookie = InspectorInstrumentation::traceAsyncOperationCompletedCallbackStarting(getExecutionContext(), m_asyncOperationId); m_disposalAllowed = false; m_window->postMessageTimerFired(this); dispose(); InspectorInstrumentation::traceAsyncCallbackCompleted(cookie); } void dispose() { // Oilpan optimization: unregister as an observer right away. clearContext(); // Will destroy this object, now or after the next GC depending // on whether Oilpan is disabled or not. m_window->removePostMessageTimer(this); } RefPtrWillBeMember m_event; RawPtrWillBeMember m_window; RefPtr m_targetOrigin; RefPtr m_stackTrace; RefPtr m_userGestureToken; int m_asyncOperationId; bool m_disposalAllowed; }; static void updateSuddenTerminationStatus(LocalDOMWindow* domWindow, bool addedListener, FrameLoaderClient::SuddenTerminationDisablerType disablerType) { Platform::current()->suddenTerminationChanged(!addedListener); if (domWindow->frame() && domWindow->frame()->loader().client()) domWindow->frame()->loader().client()->suddenTerminationDisablerChanged(addedListener, disablerType); } using DOMWindowSet = WillBePersistentHeapHashCountedSet>; static DOMWindowSet& windowsWithUnloadEventListeners() { DEFINE_STATIC_LOCAL(DOMWindowSet, windowsWithUnloadEventListeners, ()); return windowsWithUnloadEventListeners; } static DOMWindowSet& windowsWithBeforeUnloadEventListeners() { DEFINE_STATIC_LOCAL(DOMWindowSet, windowsWithBeforeUnloadEventListeners, ()); return windowsWithBeforeUnloadEventListeners; } static void addUnloadEventListener(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithUnloadEventListeners(); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, true, FrameLoaderClient::UnloadHandler); set.add(domWindow); } static void removeUnloadEventListener(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithUnloadEventListeners(); DOMWindowSet::iterator it = set.find(domWindow); if (it == set.end()) return; set.remove(it); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, false, FrameLoaderClient::UnloadHandler); } static void removeAllUnloadEventListeners(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithUnloadEventListeners(); DOMWindowSet::iterator it = set.find(domWindow); if (it == set.end()) return; set.removeAll(it); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, false, FrameLoaderClient::UnloadHandler); } static void addBeforeUnloadEventListener(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithBeforeUnloadEventListeners(); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, true, FrameLoaderClient::BeforeUnloadHandler); set.add(domWindow); } static void removeBeforeUnloadEventListener(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithBeforeUnloadEventListeners(); DOMWindowSet::iterator it = set.find(domWindow); if (it == set.end()) return; set.remove(it); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, false, FrameLoaderClient::BeforeUnloadHandler); } static void removeAllBeforeUnloadEventListeners(LocalDOMWindow* domWindow) { DOMWindowSet& set = windowsWithBeforeUnloadEventListeners(); DOMWindowSet::iterator it = set.find(domWindow); if (it == set.end()) return; set.removeAll(it); if (set.isEmpty()) updateSuddenTerminationStatus(domWindow, false, FrameLoaderClient::BeforeUnloadHandler); } static bool allowsBeforeUnloadListeners(LocalDOMWindow* window) { DCHECK(window); LocalFrame* frame = window->frame(); if (!frame) return false; return frame->isMainFrame(); } unsigned LocalDOMWindow::pendingUnloadEventListeners() const { return windowsWithUnloadEventListeners().count(const_cast(this)); } bool LocalDOMWindow::allowPopUp(LocalFrame& firstFrame) { if (UserGestureIndicator::processingUserGesture()) return true; Settings* settings = firstFrame.settings(); return settings && settings->javaScriptCanOpenWindowsAutomatically(); } bool LocalDOMWindow::allowPopUp() { return frame() && allowPopUp(*frame()); } LocalDOMWindow::LocalDOMWindow(LocalFrame& frame) : m_frameObserver(WindowFrameObserver::create(this, frame)) , m_shouldPrintWhenFinishedLoading(false) #if ENABLE(ASSERT) , m_hasBeenReset(false) #endif { #if ENABLE(OILPAN) ThreadState::current()->registerPreFinalizer(this); #endif } void LocalDOMWindow::clearDocument() { if (!m_document) return; ASSERT(!m_document->isActive()); // FIXME: This should be part of ActiveDOMObject shutdown clearEventQueue(); m_document->clearDOMWindow(); m_document = nullptr; } void LocalDOMWindow::clearEventQueue() { if (!m_eventQueue) return; m_eventQueue->close(); m_eventQueue.clear(); } void LocalDOMWindow::acceptLanguagesChanged() { if (m_navigator) m_navigator->setLanguagesChanged(); dispatchEvent(Event::create(EventTypeNames::languagechange)); } PassRefPtrWillBeRawPtr LocalDOMWindow::createDocument(const String& mimeType, const DocumentInit& init, bool forceXHTML) { RefPtrWillBeRawPtr document = nullptr; if (forceXHTML) { // This is a hack for XSLTProcessor. See XSLTProcessor::createDocumentFromSource(). document = Document::create(init); } else { document = DOMImplementation::createDocument(mimeType, init, init.frame() ? init.frame()->inViewSourceMode() : false); if (document->isPluginDocument() && document->isSandboxed(SandboxPlugins)) document = SinkDocument::create(init); } return document.release(); } PassRefPtrWillBeRawPtr LocalDOMWindow::installNewDocument(const String& mimeType, const DocumentInit& init, bool forceXHTML) { ASSERT(init.frame() == frame()); clearDocument(); m_document = createDocument(mimeType, init, forceXHTML); m_eventQueue = DOMWindowEventQueue::create(m_document.get()); m_document->attach(); if (!frame()) return m_document; frame()->script().updateDocument(); m_document->updateViewportDescription(); if (frame()->page() && frame()->view()) { if (ScrollingCoordinator* scrollingCoordinator = frame()->page()->scrollingCoordinator()) { scrollingCoordinator->scrollableAreaScrollbarLayerDidChange(frame()->view(), HorizontalScrollbar); scrollingCoordinator->scrollableAreaScrollbarLayerDidChange(frame()->view(), VerticalScrollbar); scrollingCoordinator->scrollableAreaScrollLayerDidChange(frame()->view()); } } frame()->selection().updateSecureKeyboardEntryIfActive(); return m_document; } EventQueue* LocalDOMWindow::getEventQueue() const { return m_eventQueue.get(); } void LocalDOMWindow::enqueueWindowEvent(PassRefPtrWillBeRawPtr event) { if (!m_eventQueue) return; event->setTarget(this); m_eventQueue->enqueueEvent(event); } void LocalDOMWindow::enqueueDocumentEvent(PassRefPtrWillBeRawPtr event) { if (!m_eventQueue) return; event->setTarget(m_document.get()); m_eventQueue->enqueueEvent(event); } void LocalDOMWindow::dispatchWindowLoadEvent() { ASSERT(!EventDispatchForbiddenScope::isEventDispatchForbidden()); // Delay 'load' event if we are in EventQueueScope. This is a short-term // workaround to avoid Editing code crashes. We should always dispatch // 'load' event asynchronously. crbug.com/569511. if (ScopedEventQueue::instance()->shouldQueueEvents() && m_document) { m_document->postTask(BLINK_FROM_HERE, createSameThreadTask(&LocalDOMWindow::dispatchLoadEvent, PassRefPtrWillBeRawPtr(this))); return; } dispatchLoadEvent(); } void LocalDOMWindow::documentWasClosed() { dispatchWindowLoadEvent(); enqueuePageshowEvent(PageshowEventNotPersisted); if (m_pendingStateObject) enqueuePopstateEvent(m_pendingStateObject.release()); } void LocalDOMWindow::enqueuePageshowEvent(PageshowEventPersistence persisted) { // FIXME: https://bugs.webkit.org/show_bug.cgi?id=36334 Pageshow event needs to fire asynchronously. // As per spec pageshow must be triggered asynchronously. // However to be compatible with other browsers blink fires pageshow synchronously. dispatchEvent(PageTransitionEvent::create(EventTypeNames::pageshow, persisted), m_document.get()); } void LocalDOMWindow::enqueueHashchangeEvent(const String& oldURL, const String& newURL) { enqueueWindowEvent(HashChangeEvent::create(oldURL, newURL)); } void LocalDOMWindow::enqueuePopstateEvent(PassRefPtr stateObject) { // FIXME: https://bugs.webkit.org/show_bug.cgi?id=36202 Popstate event needs to fire asynchronously dispatchEvent(PopStateEvent::create(stateObject, history())); } void LocalDOMWindow::statePopped(PassRefPtr stateObject) { if (!frame()) return; // Per step 11 of section 6.5.9 (history traversal) of the HTML5 spec, we // defer firing of popstate until we're in the complete state. if (document()->isLoadCompleted()) enqueuePopstateEvent(stateObject); else m_pendingStateObject = stateObject; } LocalDOMWindow::~LocalDOMWindow() { #if ENABLE(OILPAN) // Cleared when detaching document. ASSERT(!m_eventQueue); #else ASSERT(m_hasBeenReset); ASSERT(m_document->isStopped()); clearDocument(); #endif } void LocalDOMWindow::dispose() { // Oilpan: should the LocalDOMWindow be GCed along with its LocalFrame without the // frame having first notified its observers of imminent destruction, the // LocalDOMWindow will not have had an opportunity to remove event listeners. // // Arrange for that removal to happen using a prefinalizer action. Making LocalDOMWindow // eager finalizable is problematic as other eagerly finalized objects may well // want to access their associated LocalDOMWindow from their destructors. // // (Non-Oilpan, LocalDOMWindow::reset() will always be invoked, the last opportunity // being via ~LocalFrame's setDOMWindow() call. Asserted for in the destructor.) if (!frame()) return; removeAllEventListeners(); } ExecutionContext* LocalDOMWindow::getExecutionContext() const { return m_document.get(); } const LocalDOMWindow* LocalDOMWindow::toDOMWindow() const { return this; } LocalDOMWindow* LocalDOMWindow::toDOMWindow() { return this; } PassRefPtrWillBeRawPtr LocalDOMWindow::matchMedia(const String& media) { return document() ? document()->mediaQueryMatcher().matchMedia(media) : nullptr; } void LocalDOMWindow::willDetachFrameHost() { frame()->host()->eventHandlerRegistry().didRemoveAllEventHandlers(*this); frame()->host()->consoleMessageStorage().frameWindowDiscarded(this); LocalDOMWindow::notifyContextDestroyed(); } void LocalDOMWindow::frameDestroyed() { willDestroyDocumentInFrame(); resetLocation(); m_properties.clear(); removeAllEventListeners(); } void LocalDOMWindow::willDestroyDocumentInFrame() { for (const auto& domWindowProperty : m_properties) domWindowProperty->willDestroyGlobalObjectInFrame(); } void LocalDOMWindow::willDetachDocumentFromFrame() { for (const auto& domWindowProperty : m_properties) domWindowProperty->willDetachGlobalObjectFromFrame(); } void LocalDOMWindow::registerProperty(DOMWindowProperty* property) { m_properties.add(property); } void LocalDOMWindow::unregisterProperty(DOMWindowProperty* property) { m_properties.remove(property); } void LocalDOMWindow::reset() { m_frameObserver->contextDestroyed(); m_screen = nullptr; m_history = nullptr; m_locationbar = nullptr; m_menubar = nullptr; m_personalbar = nullptr; m_scrollbars = nullptr; m_statusbar = nullptr; m_toolbar = nullptr; m_console = nullptr; m_navigator = nullptr; m_media = nullptr; m_applicationCache = nullptr; #if ENABLE(ASSERT) m_hasBeenReset = true; #endif LocalDOMWindow::notifyContextDestroyed(); } void LocalDOMWindow::sendOrientationChangeEvent() { ASSERT(RuntimeEnabledFeatures::orientationEventEnabled()); ASSERT(frame()->isMainFrame()); // Before dispatching the event, build a list of all frames in the page // to send the event to, to mitigate side effects from event handlers // potentially interfering with others. WillBeHeapVector> frames; for (Frame* f = frame(); f; f = f->tree().traverseNext()) frames.append(f); for (size_t i = 0; i < frames.size(); ++i) { if (!frames[i]->isLocalFrame()) continue; toLocalFrame(frames[i].get())->localDOMWindow()->dispatchEvent(Event::create(EventTypeNames::orientationchange)); } } int LocalDOMWindow::orientation() const { ASSERT(RuntimeEnabledFeatures::orientationEventEnabled()); if (!frame() || !frame()->host()) return 0; int orientation = frame()->host()->chromeClient().screenInfo().orientationAngle; // For backward compatibility, we want to return a value in the range of // [-90; 180] instead of [0; 360[ because window.orientation used to behave // like that in WebKit (this is a WebKit proprietary API). if (orientation == 270) return -90; return orientation; } Screen* LocalDOMWindow::screen() const { if (!m_screen) m_screen = Screen::create(frame()); return m_screen.get(); } History* LocalDOMWindow::history() const { if (!m_history) m_history = History::create(frame()); return m_history.get(); } BarProp* LocalDOMWindow::locationbar() const { if (!m_locationbar) m_locationbar = BarProp::create(frame(), BarProp::Locationbar); return m_locationbar.get(); } BarProp* LocalDOMWindow::menubar() const { if (!m_menubar) m_menubar = BarProp::create(frame(), BarProp::Menubar); return m_menubar.get(); } BarProp* LocalDOMWindow::personalbar() const { if (!m_personalbar) m_personalbar = BarProp::create(frame(), BarProp::Personalbar); return m_personalbar.get(); } BarProp* LocalDOMWindow::scrollbars() const { if (!m_scrollbars) m_scrollbars = BarProp::create(frame(), BarProp::Scrollbars); return m_scrollbars.get(); } BarProp* LocalDOMWindow::statusbar() const { if (!m_statusbar) m_statusbar = BarProp::create(frame(), BarProp::Statusbar); return m_statusbar.get(); } BarProp* LocalDOMWindow::toolbar() const { if (!m_toolbar) m_toolbar = BarProp::create(frame(), BarProp::Toolbar); return m_toolbar.get(); } Console* LocalDOMWindow::console() const { if (!m_console) m_console = Console::create(frame()); return m_console.get(); } FrameConsole* LocalDOMWindow::frameConsole() const { if (!isCurrentlyDisplayedInFrame()) return nullptr; return &frame()->console(); } ApplicationCache* LocalDOMWindow::applicationCache() const { if (!isCurrentlyDisplayedInFrame()) return nullptr; if (!m_applicationCache) m_applicationCache = ApplicationCache::create(frame()); return m_applicationCache.get(); } Navigator* LocalDOMWindow::navigator() const { if (!m_navigator) m_navigator = Navigator::create(frame()); return m_navigator.get(); } void LocalDOMWindow::schedulePostMessage(PassRefPtrWillBeRawPtr event, SecurityOrigin* target, PassRefPtr stackTrace) { // Allowing unbounded amounts of messages to build up for a suspended context // is problematic; consider imposing a limit or other restriction if this // surfaces often as a problem (see crbug.com/587012). // Schedule the message. OwnPtrWillBeRawPtr timer = adoptPtrWillBeNoop(new PostMessageTimer(*this, event, target, stackTrace, UserGestureIndicator::currentToken())); timer->startOneShot(0, BLINK_FROM_HERE); timer->suspendIfNeeded(); m_postMessageTimers.add(timer.release()); } void LocalDOMWindow::postMessageTimerFired(PostMessageTimer* timer) { if (!isCurrentlyDisplayedInFrame()) return; RefPtrWillBeRawPtr event = timer->event(); UserGestureIndicator gestureIndicator(timer->userGestureToken()); event->entangleMessagePorts(document()); dispatchMessageEventWithOriginCheck(timer->targetOrigin(), event, timer->stackTrace()); } void LocalDOMWindow::removePostMessageTimer(PostMessageTimer* timer) { m_postMessageTimers.remove(timer); } void LocalDOMWindow::dispatchMessageEventWithOriginCheck(SecurityOrigin* intendedTargetOrigin, PassRefPtrWillBeRawPtr event, PassRefPtr stackTrace) { if (intendedTargetOrigin) { // Check target origin now since the target document may have changed since the timer was scheduled. SecurityOrigin* securityOrigin = document()->getSecurityOrigin(); bool validTarget = intendedTargetOrigin->isSameSchemeHostPortAndSuborigin(securityOrigin); if (securityOrigin->hasSuborigin() && securityOrigin->suborigin()->policyContains(Suborigin::SuboriginPolicyOptions::UnsafePostMessageReceive)) validTarget = intendedTargetOrigin->isSameSchemeHostPort(securityOrigin); if (!validTarget) { String message = ExceptionMessages::failedToExecute("postMessage", "DOMWindow", "The target origin provided ('" + intendedTargetOrigin->toString() + "') does not match the recipient window's origin ('" + document()->getSecurityOrigin()->toString() + "')."); RefPtrWillBeRawPtr consoleMessage = ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message); consoleMessage->setCallStack(stackTrace); frameConsole()->addMessage(consoleMessage.release()); return; } } dispatchEvent(event); } DOMSelection* LocalDOMWindow::getSelection() { if (!isCurrentlyDisplayedInFrame()) return nullptr; return frame()->document()->getSelection(); } Element* LocalDOMWindow::frameElement() const { if (!(frame() && frame()->owner() && frame()->owner()->isLocal())) return nullptr; return toHTMLFrameOwnerElement(frame()->owner()); } void LocalDOMWindow::blur() { } void LocalDOMWindow::print() { if (!frame()) return; FrameHost* host = frame()->host(); if (!host) return; if (frame()->document()->isSandboxed(SandboxModals)) { UseCounter::count(frame()->document(), UseCounter::DialogInSandboxedContext); if (RuntimeEnabledFeatures::sandboxBlocksModalsEnabled()) { frameConsole()->addMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Ignored call to 'print()'. The document is sandboxed, and the 'allow-modals' keyword is not set.")); return; } } if (frame()->isLoading()) { m_shouldPrintWhenFinishedLoading = true; return; } m_shouldPrintWhenFinishedLoading = false; host->chromeClient().print(frame()); } void LocalDOMWindow::stop() { if (!frame()) return; frame()->loader().stopAllLoaders(); } void LocalDOMWindow::alert(const String& message) { if (!frame()) return; if (frame()->document()->isSandboxed(SandboxModals)) { UseCounter::count(frame()->document(), UseCounter::DialogInSandboxedContext); if (RuntimeEnabledFeatures::sandboxBlocksModalsEnabled()) { frameConsole()->addMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Ignored call to 'alert()'. The document is sandboxed, and the 'allow-modals' keyword is not set.")); return; } } frame()->document()->updateLayoutTree(); FrameHost* host = frame()->host(); if (!host) return; host->chromeClient().openJavaScriptAlert(frame(), message); } bool LocalDOMWindow::confirm(const String& message) { if (!frame()) return false; if (frame()->document()->isSandboxed(SandboxModals)) { UseCounter::count(frame()->document(), UseCounter::DialogInSandboxedContext); if (RuntimeEnabledFeatures::sandboxBlocksModalsEnabled()) { frameConsole()->addMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Ignored call to 'confirm()'. The document is sandboxed, and the 'allow-modals' keyword is not set.")); return false; } } frame()->document()->updateLayoutTree(); FrameHost* host = frame()->host(); if (!host) return false; return host->chromeClient().openJavaScriptConfirm(frame(), message); } String LocalDOMWindow::prompt(const String& message, const String& defaultValue) { if (!frame()) return String(); if (frame()->document()->isSandboxed(SandboxModals)) { UseCounter::count(frame()->document(), UseCounter::DialogInSandboxedContext); if (RuntimeEnabledFeatures::sandboxBlocksModalsEnabled()) { frameConsole()->addMessage(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, "Ignored call to 'prompt()'. The document is sandboxed, and the 'allow-modals' keyword is not set.")); return String(); } } frame()->document()->updateLayoutTree(); FrameHost* host = frame()->host(); if (!host) return String(); String returnValue; if (host->chromeClient().openJavaScriptPrompt(frame(), message, defaultValue, returnValue)) return returnValue; return String(); } bool LocalDOMWindow::find(const String& string, bool caseSensitive, bool backwards, bool wrap, bool wholeWord, bool /*searchInFrames*/, bool /*showDialog*/) const { if (!isCurrentlyDisplayedInFrame()) return false; // |frame()| can be destructed during |Editor::findString()| via // |Document::updateLayout()|, e.g. event handler removes a frame. RefPtrWillBeRawPtr protectFrame(frame()); // FIXME (13016): Support searchInFrames and showDialog FindOptions options = (backwards ? Backwards : 0) | (caseSensitive ? 0 : CaseInsensitive) | (wrap ? WrapAround : 0) | (wholeWord ? WholeWord | AtWordStarts : 0); return frame()->editor().findString(string, options); } bool LocalDOMWindow::offscreenBuffering() const { return true; } int LocalDOMWindow::outerHeight() const { if (!frame()) return 0; FrameHost* host = frame()->host(); if (!host) return 0; if (host->settings().reportScreenSizeInPhysicalPixelsQuirk()) return lroundf(host->chromeClient().windowRect().height() * host->deviceScaleFactor()); return host->chromeClient().windowRect().height(); } int LocalDOMWindow::outerWidth() const { if (!frame()) return 0; FrameHost* host = frame()->host(); if (!host) return 0; if (host->settings().reportScreenSizeInPhysicalPixelsQuirk()) return lroundf(host->chromeClient().windowRect().width() * host->deviceScaleFactor()); return host->chromeClient().windowRect().width(); } static FloatSize getViewportSize(LocalFrame* frame) { FrameView* view = frame->view(); if (!view) return FloatSize(); FrameHost* host = frame->host(); if (!host) return FloatSize(); // The main frame's viewport size depends on the page scale. Since the // initial page scale depends on the content width and is set after a // layout, perform one now so queries during page load will use the up to // date viewport. if (host->settings().viewportEnabled() && frame->isMainFrame()) frame->document()->updateLayoutIgnorePendingStylesheets(); // FIXME: This is potentially too much work. We really only need to know the dimensions of the parent frame's layoutObject. if (Frame* parent = frame->tree().parent()) { if (parent && parent->isLocalFrame()) toLocalFrame(parent)->document()->updateLayoutIgnorePendingStylesheets(); } return frame->isMainFrame() && !host->settings().inertVisualViewport() ? FloatSize(host->visualViewport().visibleRect().size()) : FloatSize(view->visibleContentRect(IncludeScrollbars).size()); } int LocalDOMWindow::innerHeight() const { if (!frame()) return 0; FloatSize viewportSize = getViewportSize(frame()); return adjustForAbsoluteZoom(expandedIntSize(viewportSize).height(), frame()->pageZoomFactor()); } int LocalDOMWindow::innerWidth() const { if (!frame()) return 0; FloatSize viewportSize = getViewportSize(frame()); return adjustForAbsoluteZoom(expandedIntSize(viewportSize).width(), frame()->pageZoomFactor()); } int LocalDOMWindow::screenX() const { if (!frame()) return 0; FrameHost* host = frame()->host(); if (!host) return 0; if (host->settings().reportScreenSizeInPhysicalPixelsQuirk()) return lroundf(host->chromeClient().windowRect().x() * host->deviceScaleFactor()); return host->chromeClient().windowRect().x(); } int LocalDOMWindow::screenY() const { if (!frame()) return 0; FrameHost* host = frame()->host(); if (!host) return 0; if (host->settings().reportScreenSizeInPhysicalPixelsQuirk()) return lroundf(host->chromeClient().windowRect().y() * host->deviceScaleFactor()); return host->chromeClient().windowRect().y(); } double LocalDOMWindow::scrollX() const { if (!frame()) return 0; FrameView* view = frame()->view(); if (!view) return 0; FrameHost* host = frame()->host(); if (!host) return 0; frame()->document()->updateLayoutIgnorePendingStylesheets(); ScrollableArea* viewport = host->settings().inertVisualViewport() ? view->layoutViewportScrollableArea() : view->getScrollableArea(); double viewportX = viewport->scrollPositionDouble().x(); return adjustScrollForAbsoluteZoom(viewportX, frame()->pageZoomFactor()); } double LocalDOMWindow::scrollY() const { if (!frame()) return 0; FrameView* view = frame()->view(); if (!view) return 0; FrameHost* host = frame()->host(); if (!host) return 0; frame()->document()->updateLayoutIgnorePendingStylesheets(); ScrollableArea* viewport = host->settings().inertVisualViewport() ? view->layoutViewportScrollableArea() : view->getScrollableArea(); double viewportY = viewport->scrollPositionDouble().y(); return adjustScrollForAbsoluteZoom(viewportY, frame()->pageZoomFactor()); } const AtomicString& LocalDOMWindow::name() const { if (!isCurrentlyDisplayedInFrame()) return nullAtom; return frame()->tree().name(); } void LocalDOMWindow::setName(const AtomicString& name) { if (!isCurrentlyDisplayedInFrame()) return; frame()->tree().setName(name); ASSERT(frame()->loader().client()); frame()->loader().client()->didChangeName(name, frame()->tree().uniqueName()); } void LocalDOMWindow::setStatus(const String& string) { m_status = string; if (!frame()) return; FrameHost* host = frame()->host(); if (!host) return; ASSERT(frame()->document()); // Client calls shouldn't be made when the frame is in inconsistent state. host->chromeClient().setStatusbarText(m_status); } void LocalDOMWindow::setDefaultStatus(const String& string) { m_defaultStatus = string; if (!frame()) return; FrameHost* host = frame()->host(); if (!host) return; ASSERT(frame()->document()); // Client calls shouldn't be made when the frame is in inconsistent state. host->chromeClient().setStatusbarText(m_defaultStatus); } Document* LocalDOMWindow::document() const { return m_document.get(); } StyleMedia* LocalDOMWindow::styleMedia() const { if (!m_media) m_media = StyleMedia::create(frame()); return m_media.get(); } PassRefPtrWillBeRawPtr LocalDOMWindow::getComputedStyle(Element* elt, const String& pseudoElt) const { ASSERT(elt); return CSSComputedStyleDeclaration::create(elt, false, pseudoElt); } PassRefPtrWillBeRawPtr LocalDOMWindow::getMatchedCSSRules(Element* element, const String& pseudoElement) const { if (!element) return nullptr; if (!isCurrentlyDisplayedInFrame()) return nullptr; unsigned colonStart = pseudoElement[0] == ':' ? (pseudoElement[1] == ':' ? 2 : 1) : 0; CSSSelector::PseudoType pseudoType = CSSSelector::parsePseudoType(AtomicString(pseudoElement.substring(colonStart)), false); if (pseudoType == CSSSelector::PseudoUnknown && !pseudoElement.isEmpty()) return nullptr; unsigned rulesToInclude = StyleResolver::AuthorCSSRules; PseudoId pseudoId = CSSSelector::pseudoId(pseudoType); element->document().updateLayoutTree(); return frame()->document()->ensureStyleResolver().pseudoCSSRulesForElement(element, pseudoId, rulesToInclude); } double LocalDOMWindow::devicePixelRatio() const { if (!frame()) return 0.0; return frame()->devicePixelRatio(); } void LocalDOMWindow::scrollBy(double x, double y, ScrollBehavior scrollBehavior) const { if (!isCurrentlyDisplayedInFrame()) return; document()->updateLayoutIgnorePendingStylesheets(); FrameView* view = frame()->view(); if (!view) return; FrameHost* host = frame()->host(); if (!host) return; x = ScrollableArea::normalizeNonFiniteScroll(x); y = ScrollableArea::normalizeNonFiniteScroll(y); ScrollableArea* viewport = host->settings().inertVisualViewport() ? view->layoutViewportScrollableArea() : view->getScrollableArea(); DoublePoint currentOffset = viewport->scrollPositionDouble(); DoubleSize scaledDelta(x * frame()->pageZoomFactor(), y * frame()->pageZoomFactor()); viewport->setScrollPosition(currentOffset + scaledDelta, ProgrammaticScroll, scrollBehavior); } void LocalDOMWindow::scrollBy(const ScrollToOptions& scrollToOptions) const { double x = 0.0; double y = 0.0; if (scrollToOptions.hasLeft()) x = scrollToOptions.left(); if (scrollToOptions.hasTop()) y = scrollToOptions.top(); ScrollBehavior scrollBehavior = ScrollBehaviorAuto; ScrollableArea::scrollBehaviorFromString(scrollToOptions.behavior(), scrollBehavior); scrollBy(x, y, scrollBehavior); } void LocalDOMWindow::scrollTo(double x, double y) const { if (!isCurrentlyDisplayedInFrame()) return; FrameView* view = frame()->view(); if (!view) return; FrameHost* host = frame()->host(); if (!host) return; x = ScrollableArea::normalizeNonFiniteScroll(x); y = ScrollableArea::normalizeNonFiniteScroll(y); // It is only necessary to have an up-to-date layout if the position may be clamped, // which is never the case for (0, 0). if (x || y) document()->updateLayoutIgnorePendingStylesheets(); DoublePoint layoutPos(x * frame()->pageZoomFactor(), y * frame()->pageZoomFactor()); ScrollableArea* viewport = host->settings().inertVisualViewport() ? view->layoutViewportScrollableArea() : view->getScrollableArea(); viewport->setScrollPosition(layoutPos, ProgrammaticScroll, ScrollBehaviorAuto); } void LocalDOMWindow::scrollTo(const ScrollToOptions& scrollToOptions) const { if (!isCurrentlyDisplayedInFrame()) return; FrameView* view = frame()->view(); if (!view) return; FrameHost* host = frame()->host(); if (!host) return; // It is only necessary to have an up-to-date layout if the position may be clamped, // which is never the case for (0, 0). if (!scrollToOptions.hasLeft() || !scrollToOptions.hasTop() || scrollToOptions.left() || scrollToOptions.top()) { document()->updateLayoutIgnorePendingStylesheets(); } double scaledX = 0.0; double scaledY = 0.0; ScrollableArea* viewport = host->settings().inertVisualViewport() ? view->layoutViewportScrollableArea() : view->getScrollableArea(); DoublePoint currentOffset = viewport->scrollPositionDouble(); scaledX = currentOffset.x(); scaledY = currentOffset.y(); if (scrollToOptions.hasLeft()) scaledX = ScrollableArea::normalizeNonFiniteScroll(scrollToOptions.left()) * frame()->pageZoomFactor(); if (scrollToOptions.hasTop()) scaledY = ScrollableArea::normalizeNonFiniteScroll(scrollToOptions.top()) * frame()->pageZoomFactor(); ScrollBehavior scrollBehavior = ScrollBehaviorAuto; ScrollableArea::scrollBehaviorFromString(scrollToOptions.behavior(), scrollBehavior); viewport->setScrollPosition(DoublePoint(scaledX, scaledY), ProgrammaticScroll, scrollBehavior); } void LocalDOMWindow::moveBy(int x, int y) const { if (!frame() || !frame()->isMainFrame()) return; FrameHost* host = frame()->host(); if (!host) return; IntRect windowRect = host->chromeClient().windowRect(); windowRect.move(x, y); // Security check (the spec talks about UniversalBrowserWrite to disable this check...) host->chromeClient().setWindowRectWithAdjustment(windowRect); } void LocalDOMWindow::moveTo(int x, int y) const { if (!frame() || !frame()->isMainFrame()) return; FrameHost* host = frame()->host(); if (!host) return; IntRect windowRect = host->chromeClient().windowRect(); windowRect.setLocation(IntPoint(x, y)); // Security check (the spec talks about UniversalBrowserWrite to disable this check...) host->chromeClient().setWindowRectWithAdjustment(windowRect); } void LocalDOMWindow::resizeBy(int x, int y) const { if (!frame() || !frame()->isMainFrame()) return; FrameHost* host = frame()->host(); if (!host) return; IntRect fr = host->chromeClient().windowRect(); IntSize dest = fr.size() + IntSize(x, y); IntRect update(fr.location(), dest); host->chromeClient().setWindowRectWithAdjustment(update); } void LocalDOMWindow::resizeTo(int width, int height) const { if (!frame() || !frame()->isMainFrame()) return; FrameHost* host = frame()->host(); if (!host) return; IntRect fr = host->chromeClient().windowRect(); IntSize dest = IntSize(width, height); IntRect update(fr.location(), dest); host->chromeClient().setWindowRectWithAdjustment(update); } int LocalDOMWindow::requestAnimationFrame(FrameRequestCallback* callback) { callback->m_useLegacyTimeBase = false; if (Document* doc = document()) return doc->requestAnimationFrame(callback); return 0; } int LocalDOMWindow::webkitRequestAnimationFrame(FrameRequestCallback* callback) { callback->m_useLegacyTimeBase = true; if (Document* document = this->document()) return document->requestAnimationFrame(callback); return 0; } void LocalDOMWindow::cancelAnimationFrame(int id) { if (Document* document = this->document()) document->cancelAnimationFrame(id); } int LocalDOMWindow::requestIdleCallback(IdleRequestCallback* callback, const IdleRequestOptions& options) { if (Document* document = this->document()) return document->requestIdleCallback(callback, options); return 0; } void LocalDOMWindow::cancelIdleCallback(int id) { if (Document* document = this->document()) document->cancelIdleCallback(id); } bool LocalDOMWindow::addEventListenerInternal(const AtomicString& eventType, PassRefPtrWillBeRawPtr prpListener, const EventListenerOptions& options) { RefPtrWillBeRawPtr listener = prpListener; if (!EventTarget::addEventListenerInternal(eventType, listener, options)) return false; if (frame() && frame()->host()) frame()->host()->eventHandlerRegistry().didAddEventHandler(*this, eventType, options); if (Document* document = this->document()) { document->addListenerTypeIfNeeded(eventType); } notifyAddEventListener(this, eventType); if (eventType == EventTypeNames::unload) { UseCounter::count(document(), UseCounter::DocumentUnloadRegistered); addUnloadEventListener(this); } else if (eventType == EventTypeNames::beforeunload) { UseCounter::count(document(), UseCounter::DocumentBeforeUnloadRegistered); if (allowsBeforeUnloadListeners(this)) { // This is confusingly named. It doesn't actually add the listener. It just increments a count // so that we know we have listeners registered for the purposes of determining if we can // fast terminate the renderer process. addBeforeUnloadEventListener(this); } else { // Subframes return false from allowsBeforeUnloadListeners. UseCounter::count(document(), UseCounter::SubFrameBeforeUnloadRegistered); } } return true; } bool LocalDOMWindow::removeEventListenerInternal(const AtomicString& eventType, PassRefPtrWillBeRawPtr listener, const EventListenerOptions& options) { if (!EventTarget::removeEventListenerInternal(eventType, listener, options)) return false; if (frame() && frame()->host()) frame()->host()->eventHandlerRegistry().didRemoveEventHandler(*this, eventType, options); notifyRemoveEventListener(this, eventType); if (eventType == EventTypeNames::unload) { removeUnloadEventListener(this); } else if (eventType == EventTypeNames::beforeunload && allowsBeforeUnloadListeners(this)) { removeBeforeUnloadEventListener(this); } return true; } void LocalDOMWindow::dispatchLoadEvent() { RefPtrWillBeRawPtr loadEvent(Event::create(EventTypeNames::load)); if (frame() && frame()->loader().documentLoader() && !frame()->loader().documentLoader()->timing().loadEventStart()) { // The DocumentLoader (and thus its DocumentLoadTiming) might get destroyed while dispatching // the event, so protect it to prevent writing the end time into freed memory. RefPtrWillBeRawPtr documentLoader = frame()->loader().documentLoader(); DocumentLoadTiming& timing = documentLoader->timing(); timing.markLoadEventStart(); dispatchEvent(loadEvent, document()); timing.markLoadEventEnd(); } else { dispatchEvent(loadEvent, document()); } // For load events, send a separate load event to the enclosing frame only. // This is a DOM extension and is independent of bubbling/capturing rules of // the DOM. FrameOwner* owner = frame() ? frame()->owner() : nullptr; if (owner) owner->dispatchLoad(); TRACE_EVENT_INSTANT1("devtools.timeline", "MarkLoad", TRACE_EVENT_SCOPE_THREAD, "data", InspectorMarkLoadEvent::data(frame())); InspectorInstrumentation::loadEventFired(frame()); } DispatchEventResult LocalDOMWindow::dispatchEvent(PassRefPtrWillBeRawPtr prpEvent, PassRefPtrWillBeRawPtr prpTarget) { ASSERT(!EventDispatchForbiddenScope::isEventDispatchForbidden()); RefPtrWillBeRawPtr protect(this); RefPtrWillBeRawPtr event = prpEvent; event->setTrusted(true); event->setTarget(prpTarget ? prpTarget : this); event->setCurrentTarget(this); event->setEventPhase(Event::AT_TARGET); TRACE_EVENT1("devtools.timeline", "EventDispatch", "data", InspectorEventDispatchEvent::data(*event)); return fireEventListeners(event.get()); } void LocalDOMWindow::removeAllEventListeners() { EventTarget::removeAllEventListeners(); notifyRemoveAllEventListeners(this); if (frame() && frame()->host()) frame()->host()->eventHandlerRegistry().didRemoveAllEventHandlers(*this); removeAllUnloadEventListeners(this); removeAllBeforeUnloadEventListeners(this); } void LocalDOMWindow::finishedLoading() { if (m_shouldPrintWhenFinishedLoading) { m_shouldPrintWhenFinishedLoading = false; print(); } } void LocalDOMWindow::printErrorMessage(const String& message) const { if (!isCurrentlyDisplayedInFrame()) return; if (message.isEmpty()) return; frameConsole()->addMessage(ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, message)); } PassRefPtrWillBeRawPtr LocalDOMWindow::open(const String& urlString, const AtomicString& frameName, const String& windowFeaturesString, LocalDOMWindow* callingWindow, LocalDOMWindow* enteredWindow) { if (!isCurrentlyDisplayedInFrame()) return nullptr; if (!callingWindow->frame()) return nullptr; Document* activeDocument = callingWindow->document(); if (!activeDocument) return nullptr; LocalFrame* firstFrame = enteredWindow->frame(); if (!firstFrame) return nullptr; UseCounter::count(*activeDocument, UseCounter::DOMWindowOpen); if (!windowFeaturesString.isEmpty()) UseCounter::count(*activeDocument, UseCounter::DOMWindowOpenFeatures); if (!enteredWindow->allowPopUp()) { // Because FrameTree::find() returns true for empty strings, we must check for empty frame names. // Otherwise, illegitimate window.open() calls with no name will pass right through the popup blocker. if (frameName.isEmpty() || !frame()->tree().find(frameName)) return nullptr; } // Get the target frame for the special cases of _top and _parent. // In those cases, we schedule a location change right now and return early. Frame* targetFrame = nullptr; if (frameName == "_top") { targetFrame = frame()->tree().top(); } else if (frameName == "_parent") { if (Frame* parent = frame()->tree().parent()) targetFrame = parent; else targetFrame = frame(); } if (targetFrame) { if (!activeDocument->frame() || !activeDocument->frame()->canNavigate(*targetFrame)) return nullptr; KURL completedURL = firstFrame->document()->completeURL(urlString); if (targetFrame->domWindow()->isInsecureScriptAccess(*callingWindow, completedURL)) return targetFrame->domWindow(); if (urlString.isEmpty()) return targetFrame->domWindow(); targetFrame->navigate(*activeDocument, completedURL, false, UserGestureStatus::None); return targetFrame->domWindow(); } WindowFeatures features(windowFeaturesString); RefPtrWillBeRawPtr newWindow = createWindow(urlString, frameName, features, *callingWindow, *firstFrame, *frame()); return features.noopener ? nullptr : newWindow; } DEFINE_TRACE(LocalDOMWindow) { #if ENABLE(OILPAN) visitor->trace(m_frameObserver); visitor->trace(m_document); visitor->trace(m_properties); visitor->trace(m_screen); visitor->trace(m_history); visitor->trace(m_locationbar); visitor->trace(m_menubar); visitor->trace(m_personalbar); visitor->trace(m_scrollbars); visitor->trace(m_statusbar); visitor->trace(m_toolbar); visitor->trace(m_console); visitor->trace(m_navigator); visitor->trace(m_media); visitor->trace(m_applicationCache); visitor->trace(m_eventQueue); visitor->trace(m_postMessageTimers); HeapSupplementable::trace(visitor); #endif DOMWindow::trace(visitor); DOMWindowLifecycleNotifier::trace(visitor); } LocalFrame* LocalDOMWindow::frame() const { // If the LocalDOMWindow still has a frame reference, that frame must point // back to this LocalDOMWindow: otherwise, it's easy to get into a situation // where script execution leaks between different LocalDOMWindows. if (m_frameObserver->frame()) ASSERT_WITH_SECURITY_IMPLICATION(m_frameObserver->frame()->domWindow() == this); return m_frameObserver->frame(); } } // namespace blink