diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/dom/Document.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/dom/Document.cpp')
-rw-r--r-- | Source/WebCore/dom/Document.cpp | 4908 |
1 files changed, 2899 insertions, 2009 deletions
diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index 1e677dc90..f9e0bf272 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -3,7 +3,7 @@ * (C) 1999 Antti Koivisto (koivisto@kde.org) * (C) 2001 Dirk Mueller (mueller@kde.org) * (C) 2006 Alexey Proskuryakov (ap@webkit.org) - * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2011, 2012, 2013 Apple Inc. All rights reserved. + * Copyright (C) 2004-2017 Apple Inc. All rights reserved. * Copyright (C) 2008, 2009 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/) * Copyright (C) 2008, 2009, 2011, 2012 Google Inc. All rights reserved. * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies) @@ -29,117 +29,163 @@ #include "Document.h" #include "AXObjectCache.h" -#include "AnimationController.h" #include "Attr.h" #include "CDATASection.h" +#include "CSSAnimationController.h" +#include "CSSFontSelector.h" #include "CSSStyleDeclaration.h" #include "CSSStyleSheet.h" #include "CachedCSSStyleSheet.h" +#include "CachedFrame.h" #include "CachedResourceLoader.h" #include "Chrome.h" #include "ChromeClient.h" #include "Comment.h" +#include "CommonVM.h" +#include "CompositionEvent.h" #include "ContentSecurityPolicy.h" #include "CookieJar.h" +#include "CustomElementReactionQueue.h" +#include "CustomElementRegistry.h" +#include "CustomEvent.h" #include "DOMImplementation.h" #include "DOMNamedFlowCollection.h" #include "DOMWindow.h" #include "DateComponents.h" -#include "Dictionary.h" +#include "DebugPageOverlays.h" #include "DocumentLoader.h" #include "DocumentMarkerController.h" #include "DocumentSharedObjectPool.h" #include "DocumentType.h" #include "Editor.h" #include "ElementIterator.h" -#include "EntityReference.h" -#include "EventFactory.h" #include "EventHandler.h" -#include "FontLoader.h" +#include "ExtensionStyleSheets.h" +#include "FocusController.h" +#include "FontFaceSet.h" #include "FormController.h" #include "FrameLoader.h" #include "FrameLoaderClient.h" #include "FrameView.h" -#include "HashChangeEvent.h" -#include "HistogramSupport.h" -#include "History.h" +#include "GenericCachedHTMLCollection.h" #include "HTMLAllCollection.h" #include "HTMLAnchorElement.h" #include "HTMLBaseElement.h" #include "HTMLBodyElement.h" #include "HTMLCanvasElement.h" -#include "HTMLCollection.h" #include "HTMLDocument.h" #include "HTMLElementFactory.h" #include "HTMLFormControlElement.h" #include "HTMLFrameOwnerElement.h" #include "HTMLFrameSetElement.h" #include "HTMLHeadElement.h" -#include "HTMLIFrameElement.h" +#include "HTMLHtmlElement.h" #include "HTMLImageElement.h" +#include "HTMLInputElement.h" #include "HTMLLinkElement.h" #include "HTMLMediaElement.h" #include "HTMLNameCollection.h" #include "HTMLParserIdioms.h" +#include "HTMLPictureElement.h" #include "HTMLPlugInElement.h" #include "HTMLScriptElement.h" #include "HTMLStyleElement.h" #include "HTMLTitleElement.h" +#include "HTMLUnknownElement.h" +#include "HTTPHeaderNames.h" #include "HTTPParsers.h" +#include "HashChangeEvent.h" +#include "History.h" #include "HitTestResult.h" #include "IconController.h" #include "ImageLoader.h" #include "InspectorInstrumentation.h" +#include "JSCustomElementInterface.h" #include "JSLazyEventListener.h" +#include "KeyboardEvent.h" #include "Language.h" #include "LoaderStrategy.h" #include "Logging.h" #include "MainFrame.h" #include "MediaCanStartListener.h" +#include "MediaProducer.h" #include "MediaQueryList.h" #include "MediaQueryMatcher.h" +#include "MessageEvent.h" #include "MouseEventWithHitTestResults.h" +#include "MutationEvent.h" #include "NameNodeList.h" +#include "NamedFlowCollection.h" #include "NestingLevelIncrementer.h" +#include "NoEventDispatchAssertion.h" #include "NodeIterator.h" #include "NodeRareData.h" #include "NodeWithIndex.h" -#include "PageConsole.h" +#include "OriginAccessEntry.h" +#include "OverflowEvent.h" +#include "PageConsoleClient.h" #include "PageGroup.h" #include "PageTransitionEvent.h" #include "PlatformLocale.h" +#include "PlatformMediaSessionManager.h" +#include "PlatformScreen.h" #include "PlatformStrategies.h" #include "PlugInsResources.h" #include "PluginDocument.h" #include "PointerLockController.h" #include "PopStateEvent.h" #include "ProcessingInstruction.h" +#include "RenderChildIterator.h" +#include "RenderLayerCompositor.h" +#include "RenderTreeUpdater.h" #include "RenderView.h" #include "RenderWidget.h" -#include "ResourceLoadScheduler.h" -#include "ResourceLoader.h" +#include "RequestAnimationFrameCallback.h" +#include "ResourceLoadObserver.h" #include "RuntimeEnabledFeatures.h" +#include "SVGDocumentExtensions.h" +#include "SVGElement.h" +#include "SVGElementFactory.h" +#include "SVGNames.h" +#include "SVGSVGElement.h" +#include "SVGTitleElement.h" +#include "SVGZoomEvent.h" #include "SchemeRegistry.h" #include "ScopedEventQueue.h" -#include "ScriptCallStack.h" #include "ScriptController.h" +#include "ScriptModuleLoader.h" #include "ScriptRunner.h" #include "ScriptSourceCode.h" +#include "ScriptedAnimationController.h" #include "ScrollingCoordinator.h" #include "SecurityOrigin.h" +#include "SecurityOriginData.h" +#include "SecurityOriginPolicy.h" #include "SecurityPolicy.h" #include "SegmentedString.h" #include "SelectorQuery.h" #include "Settings.h" #include "ShadowRoot.h" +#include "SocketProvider.h" +#include "StorageEvent.h" #include "StyleProperties.h" +#include "StyleResolveForDocument.h" #include "StyleResolver.h" +#include "StyleScope.h" #include "StyleSheetContents.h" #include "StyleSheetList.h" -#include "TextResourceDecoder.h" +#include "StyleTreeResolver.h" +#include "SubresourceLoader.h" +#include "TextAutoSizing.h" +#include "TextEvent.h" +#include "TextNodeTraversal.h" #include "TransformSource.h" #include "TreeWalker.h" +#include "ValidationMessageClient.h" #include "VisitedLinkState.h" +#include "WheelEvent.h" +#include "WindowFeatures.h" +#include "XMLDocument.h" #include "XMLDocumentParser.h" #include "XMLNSNames.h" #include "XMLNames.h" @@ -148,31 +194,27 @@ #include "XPathNSResolver.h" #include "XPathResult.h" #include "htmlediting.h" +#include <ctime> +#include <inspector/ScriptCallStack.h> #include <wtf/CurrentTime.h> -#include <wtf/TemporaryChange.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/SetForScope.h> +#include <wtf/SystemTracing.h> #include <wtf/text/StringBuffer.h> +#include <yarr/RegularExpression.h> -#if USE(ACCELERATED_COMPOSITING) -#include "RenderLayerCompositor.h" -#endif - -#if ENABLE(SHARED_WORKERS) -#include "SharedWorkerRepository.h" -#endif - -#if ENABLE(XSLT) -#include "XSLTProcessor.h" +#if ENABLE(DEVICE_ORIENTATION) +#include "DeviceMotionEvent.h" +#include "DeviceOrientationEvent.h" #endif -#if ENABLE(SVG) -#include "SVGDocumentExtensions.h" -#include "SVGElementFactory.h" -#include "SVGNames.h" -#include "SVGSVGElement.h" +#if ENABLE(FULLSCREEN_API) +#include "RenderFullScreen.h" #endif -#if ENABLE(TOUCH_EVENTS) -#include "TouchList.h" +#if ENABLE(INDEXED_DATABASE) +#include "IDBConnectionProxy.h" +#include "IDBOpenDBRequest.h" #endif #if PLATFORM(IOS) @@ -198,29 +240,35 @@ #include "MathMLNames.h" #endif -#if ENABLE(FULLSCREEN_API) -#include "RenderFullScreen.h" +#if ENABLE(MEDIA_SESSION) +#include "MediaSession.h" #endif -#if ENABLE(REQUEST_ANIMATION_FRAME) -#include "RequestAnimationFrameCallback.h" -#include "ScriptedAnimationController.h" +#if USE(QUICK_LOOK) +#include "QuickLook.h" #endif -#if ENABLE(IOS_TEXT_AUTOSIZING) -#include "TextAutoSizing.h" +#if ENABLE(TOUCH_EVENTS) +#include "TouchEvent.h" +#include "TouchList.h" #endif -#if ENABLE(TEXT_AUTOSIZING) -#include "TextAutosizer.h" +#if ENABLE(VIDEO_TRACK) +#include "CaptionUserPreferences.h" #endif -#if ENABLE(CSP_NEXT) -#include "DOMSecurityPolicy.h" +#if ENABLE(WEB_REPLAY) +#include "WebReplayInputs.h" +#include <replay/EmptyInputCursor.h> +#include <replay/InputCursor.h> #endif -#if ENABLE(VIDEO_TRACK) -#include "CaptionUserPreferences.h" +#if ENABLE(WIRELESS_PLAYBACK_TARGET) +#include "MediaPlaybackTargetClient.h" +#endif + +#if ENABLE(XSLT) +#include "XSLTProcessor.h" #endif using namespace WTF; @@ -230,9 +278,8 @@ namespace WebCore { using namespace HTMLNames; -// #define INSTRUMENT_LAYOUT_SCHEDULING 1 - static const unsigned cMaxWriteRecursionDepth = 21; +bool Document::hasEverCreatedAnAXObjectCache = false; // DOM Level 2 says (letters added): // @@ -305,27 +352,14 @@ static inline bool isValidNamePart(UChar32 c) return true; } -static bool shouldInheritSecurityOriginFromOwner(const URL& url) -{ - // http://www.whatwg.org/specs/web-apps/current-work/#origin-0 - // - // If a Document has the address "about:blank" - // The origin of the Document is the origin it was assigned when its browsing context was created. - // - // Note: We generalize this to all "blank" URLs and invalid URLs because we - // treat all of these URLs as about:blank. - // - return url.isEmpty() || url.isBlankURL(); -} - static Widget* widgetForElement(Element* focusedElement) { if (!focusedElement) return nullptr; - auto renderer = focusedElement->renderer(); - if (!renderer || !renderer->isWidget()) + auto* renderer = focusedElement->renderer(); + if (!is<RenderWidget>(renderer)) return nullptr; - return toRenderWidget(renderer)->widget(); + return downcast<RenderWidget>(*renderer).widget(); } static bool acceptsEditingFocus(Node* node) @@ -338,31 +372,31 @@ static bool acceptsEditingFocus(Node* node) if (!frame || !root) return false; - return frame->editor().shouldBeginEditing(rangeOfContents(*root).get()); + return frame->editor().shouldBeginEditing(rangeOfContents(*root).ptr()); } -static bool canAccessAncestor(const SecurityOrigin* activeSecurityOrigin, Frame* targetFrame) +static bool canAccessAncestor(const SecurityOrigin& activeSecurityOrigin, Frame* targetFrame) { // targetFrame can be 0 when we're trying to navigate a top-level frame // that has a 0 opener. if (!targetFrame) return false; - const bool isLocalActiveOrigin = activeSecurityOrigin->isLocal(); + const bool isLocalActiveOrigin = activeSecurityOrigin.isLocal(); for (Frame* ancestorFrame = targetFrame; ancestorFrame; ancestorFrame = ancestorFrame->tree().parent()) { Document* ancestorDocument = ancestorFrame->document(); // FIXME: Should be an ASSERT? Frames should alway have documents. if (!ancestorDocument) return true; - const SecurityOrigin* ancestorSecurityOrigin = ancestorDocument->securityOrigin(); - if (activeSecurityOrigin->canAccess(ancestorSecurityOrigin)) + const SecurityOrigin& ancestorSecurityOrigin = ancestorDocument->securityOrigin(); + if (activeSecurityOrigin.canAccess(ancestorSecurityOrigin)) return true; // Allow file URL descendant navigation even when allowFileAccessFromFileURLs is false. // FIXME: It's a bit strange to special-case local origins here. Should we be doing // something more general instead? - if (isLocalActiveOrigin && ancestorSecurityOrigin->isLocal()) + if (isLocalActiveOrigin && ancestorSecurityOrigin.isLocal()) return true; } @@ -377,73 +411,73 @@ static void printNavigationErrorMessage(Frame* frame, const URL& activeURL, cons frame->document()->domWindow()->printErrorMessage(message); } -uint64_t Document::s_globalTreeVersion = 0; - -static const double timeBeforeThrowingAwayStyleResolverAfterLastUseInSeconds = 30; +#if ENABLE(TEXT_AUTOSIZING) -#if ENABLE(IOS_TEXT_AUTOSIZING) void TextAutoSizingTraits::constructDeletedValue(TextAutoSizingKey& slot) { - new (&slot) TextAutoSizingKey(TextAutoSizingKey::deletedKeyStyle(), TextAutoSizingKey::deletedKeyDoc()); + new (NotNull, &slot) TextAutoSizingKey(TextAutoSizingKey::Deleted); } bool TextAutoSizingTraits::isDeletedValue(const TextAutoSizingKey& value) { - return value.style() == TextAutoSizingKey::deletedKeyStyle() && value.doc() == TextAutoSizingKey::deletedKeyDoc(); + return value.isDeleted(); } + #endif +uint64_t Document::s_globalTreeVersion = 0; + +HashSet<Document*>& Document::allDocuments() +{ + static NeverDestroyed<HashSet<Document*>> documents; + return documents; +} + Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsigned constructionFlags) - : ContainerNode(nullptr, CreateDocument) - , TreeScope(this) -#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS) - , m_handlingTouchEvent(false) - , m_touchEventRegionsDirty(false) - , m_touchEventsChangedTimer(this, &Document::touchEventsChangedTimerFired) + : ContainerNode(*this, CreateDocument) + , TreeScope(*this) + , FrameDestructionObserver(frame) +#if ENABLE(IOS_TOUCH_EVENTS) + , m_touchEventsChangedTimer(*this, &Document::touchEventsChangedTimerFired) #endif - , m_styleResolverThrowawayTimer(this, &Document::styleResolverThrowawayTimerFired, timeBeforeThrowingAwayStyleResolverAfterLastUseInSeconds) - , m_didCalculateStyleResolver(false) + , m_referencingNodeCount(0) + , m_settings(frame ? Ref<Settings>(frame->settings()) : Settings::create(nullptr)) , m_hasNodesWithPlaceholderStyle(false) - , m_needsNotifyRemoveAllPendingStylesheet(false) , m_ignorePendingStylesheets(false) , m_pendingSheetLayout(NoLayoutWithPendingSheets) - , m_frame(frame) + , m_cachedResourceLoader(m_frame ? Ref<CachedResourceLoader>(m_frame->loader().activeDocumentLoader()->cachedResourceLoader()) : CachedResourceLoader::create(nullptr)) , m_activeParserCount(0) , m_wellFormed(false) , m_printing(false) , m_paginatedForScreen(false) - , m_ignoreAutofocus(false) - , m_compatibilityMode(NoQuirksMode) + , m_compatibilityMode(DocumentCompatibilityMode::NoQuirksMode) , m_compatibilityModeLocked(false) , m_textColor(Color::black) , m_domTreeVersion(++s_globalTreeVersion) , m_listenerTypes(0) , m_mutationObserverTypes(0) - , m_styleSheetCollection(*this) - , m_visitedLinkState(VisitedLinkState::create(*this)) + , m_styleScope(std::make_unique<Style::Scope>(*this)) + , m_extensionStyleSheets(std::make_unique<ExtensionStyleSheets>(*this)) + , m_visitedLinkState(std::make_unique<VisitedLinkState>(*this)) , m_visuallyOrdered(false) , m_readyState(Complete) , m_bParsing(false) - , m_optimizedStyleSheetUpdateTimer(this, &Document::optimizedStyleSheetUpdateTimerFired) - , m_styleRecalcTimer(this, &Document::styleRecalcTimerFired) + , m_styleRecalcTimer(*this, &Document::updateStyleIfNeeded) , m_pendingStyleRecalcShouldForce(false) , m_inStyleRecalc(false) , m_closeAfterStyleRecalc(false) , m_gotoAnchorNeededAfterStylesheetsLoad(false) , m_frameElementsShouldIgnoreScrolling(false) - , m_containsValidityStyleRules(false) - , m_updateFocusAppearanceRestoresSelection(false) - , m_ignoreDestructiveWriteCount(0) - , m_titleSetExplicitly(false) - , m_markers(adoptPtr(new DocumentMarkerController)) - , m_updateFocusAppearanceTimer(this, &Document::updateFocusAppearanceTimerFired) - , m_resetHiddenFocusElementTimer(this, &Document::resetHiddenFocusElementTimer) + , m_updateFocusAppearanceRestoresSelection(SelectionRestorationMode::SetDefault) + , m_markers(std::make_unique<DocumentMarkerController>(*this)) + , m_updateFocusAppearanceTimer(*this, &Document::updateFocusAppearanceTimerFired) , m_cssTarget(nullptr) , m_processingLoadEvent(false) , m_loadEventFinished(false) - , m_startTime(std::chrono::steady_clock::now()) + , m_documentCreationTime(MonotonicTime::now()) , m_overMinimumLayoutThreshold(false) , m_scriptRunner(std::make_unique<ScriptRunner>(*this)) + , m_moduleLoader(std::make_unique<ScriptModuleLoader>(*this)) , m_xmlVersion(ASCIILiteral("1.0")) , m_xmlStandalone(StandaloneUnspecified) , m_hasXMLDeclaration(false) @@ -453,56 +487,61 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig , m_annotatedRegionsDirty(false) #endif , m_createRenderers(true) - , m_inPageCache(false) , m_accessKeyMapValid(false) , m_documentClasses(documentClasses) , m_isSynthesized(constructionFlags & Synthesized) , m_isNonRenderedPlaceholder(constructionFlags & NonRenderedPlaceholder) - , m_isViewSource(false) , m_sawElementsInKnownNamespaces(false) , m_isSrcdocDocument(false) , m_eventQueue(*this) , m_weakFactory(this) - , m_idAttributeName(idAttr) #if ENABLE(FULLSCREEN_API) , m_areKeysEnabledInFullScreen(0) , m_fullScreenRenderer(nullptr) - , m_fullScreenChangeDelayTimer(this, &Document::fullScreenChangeDelayTimerFired) + , m_fullScreenChangeDelayTimer(*this, &Document::fullScreenChangeDelayTimerFired) , m_isAnimatingFullScreen(false) #endif , m_loadEventDelayCount(0) - , m_loadEventDelayTimer(this, &Document::loadEventDelayTimerFired) - , m_referrerPolicy(ReferrerPolicyDefault) - , m_directionSetOnDocumentElement(false) - , m_writingModeSetOnDocumentElement(false) + , m_loadEventDelayTimer(*this, &Document::loadEventDelayTimerFired) + , m_referrerPolicy(ReferrerPolicy::Default) , m_writeRecursionIsTooDeep(false) , m_writeRecursionDepth(0) - , m_wheelEventHandlerCount(0) , m_lastHandledUserGestureTimestamp(0) #if PLATFORM(IOS) #if ENABLE(DEVICE_ORIENTATION) - , m_deviceMotionClient(DeviceMotionClientIOS::create()) - , m_deviceMotionController(DeviceMotionController::create(m_deviceMotionClient.get())) - , m_deviceOrientationClient(DeviceOrientationClientIOS::create()) - , m_deviceOrientationController(DeviceOrientationController::create(m_deviceOrientationClient.get())) + , m_deviceMotionClient(std::make_unique<DeviceMotionClientIOS>()) + , m_deviceMotionController(std::make_unique<DeviceMotionController>(m_deviceMotionClient.get())) + , m_deviceOrientationClient(std::make_unique<DeviceOrientationClientIOS>()) + , m_deviceOrientationController(std::make_unique<DeviceOrientationController>(m_deviceOrientationClient.get())) +#endif #endif +#if ENABLE(TELEPHONE_NUMBER_DETECTION) , m_isTelephoneNumberParsingAllowed(true) #endif - , m_pendingTasksTimer(this, &Document::pendingTasksTimerFired) + , m_pendingTasksTimer(*this, &Document::pendingTasksTimerFired) , m_scheduledTasksAreSuspended(false) , m_visualUpdatesAllowed(true) - , m_visualUpdatesSuppressionTimer(this, &Document::visualUpdatesSuppressionTimerFired) - , m_sharedObjectPoolClearTimer(this, &Document::sharedObjectPoolClearTimerFired) + , m_visualUpdatesSuppressionTimer(*this, &Document::visualUpdatesSuppressionTimerFired) + , m_sharedObjectPoolClearTimer(*this, &Document::clearSharedObjectPool) #ifndef NDEBUG , m_didDispatchViewportPropertiesChanged(false) #endif -#if ENABLE(TEMPLATE_ELEMENT) , m_templateDocumentHost(nullptr) + , m_fontSelector(CSSFontSelector::create(*this)) +#if ENABLE(WEB_REPLAY) + , m_inputCursor(EmptyInputCursor::create()) #endif - , m_didAssociateFormControlsTimer(this, &Document::didAssociateFormControlsTimerFired) + , m_didAssociateFormControlsTimer(*this, &Document::didAssociateFormControlsTimerFired) + , m_cookieCacheExpiryTimer(*this, &Document::invalidateDOMCookieCache) + , m_disabledFieldsetElementsCount(0) , m_hasInjectedPlugInsScript(false) - , m_renderTreeBeingDestroyed(false) + , m_hasStyleWithViewportUnits(false) +#if ENABLE(WEB_SOCKETS) + , m_socketProvider(page() ? &page()->socketProvider() : nullptr) +#endif { + allDocuments().add(this); + // We depend on the url getting immediately set in subframes, but we // also depend on the url NOT getting immediately set in opened windows. // See fast/dom/early-frame-url.html @@ -511,16 +550,8 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig if ((frame && frame->ownerElement()) || !url.isEmpty()) setURL(url); - if (m_frame) - m_cachedResourceLoader = &m_frame->loader().activeDocumentLoader()->cachedResourceLoader(); - if (!m_cachedResourceLoader) - m_cachedResourceLoader = CachedResourceLoader::create(nullptr); m_cachedResourceLoader->setDocument(this); -#if ENABLE(TEXT_AUTOSIZING) - m_textAutosizer = TextAutosizer::create(this); -#endif - resetLinkColor(); resetVisitedLinkColor(); resetActiveLinkColor(); @@ -528,23 +559,14 @@ Document::Document(Frame* frame, const URL& url, unsigned documentClasses, unsig initSecurityContext(); initDNSPrefetch(); - for (unsigned i = 0; i < WTF_ARRAY_LENGTH(m_nodeListAndCollectionCounts); ++i) - m_nodeListAndCollectionCounts[i] = 0; + m_fontSelector->registerForInvalidationCallbacks(*this); - InspectorCounters::incrementCounter(InspectorCounters::DocumentCounter); -} - -static void histogramMutationEventUsage(const unsigned short& listenerTypes) -{ - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMSubtreeModified", static_cast<bool>(listenerTypes & Document::DOMSUBTREEMODIFIED_LISTENER), 2); - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMNodeInserted", static_cast<bool>(listenerTypes & Document::DOMNODEINSERTED_LISTENER), 2); - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMNodeRemoved", static_cast<bool>(listenerTypes & Document::DOMNODEREMOVED_LISTENER), 2); - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMNodeRemovedFromDocument", static_cast<bool>(listenerTypes & Document::DOMNODEREMOVEDFROMDOCUMENT_LISTENER), 2); - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMNodeInsertedIntoDocument", static_cast<bool>(listenerTypes & Document::DOMNODEINSERTEDINTODOCUMENT_LISTENER), 2); - HistogramSupport::histogramEnumeration("DOMAPI.PerDocumentMutationEventUsage.DOMCharacterDataModified", static_cast<bool>(listenerTypes & Document::DOMCHARACTERDATAMODIFIED_LISTENER), 2); + for (auto& nodeListAndCollectionCount : m_nodeListAndCollectionCounts) + nodeListAndCollectionCount = 0; } #if ENABLE(FULLSCREEN_API) + static bool isAttributeOnAllOwners(const WebCore::QualifiedName& attribute, const WebCore::QualifiedName& prefixedAttribute, const HTMLFrameOwnerElement* owner) { if (!owner) @@ -555,32 +577,42 @@ static bool isAttributeOnAllOwners(const WebCore::QualifiedName& attribute, cons } while ((owner = owner->document().ownerElement())); return true; } + #endif +Ref<Document> Document::create(Document& contextDocument) +{ + auto document = adoptRef(*new Document(nullptr, URL())); + document->setContextDocument(contextDocument); + document->setSecurityOriginPolicy(contextDocument.securityOriginPolicy()); + return document; +} + Document::~Document() { + allDocuments().remove(this); + ASSERT(!renderView()); - ASSERT(!m_inPageCache); + ASSERT(m_pageCacheState != InPageCache); ASSERT(m_ranges.isEmpty()); ASSERT(!m_parentTreeScope); + ASSERT(!m_disabledFieldsetElementsCount); + ASSERT(m_inDocumentShadowRoots.isEmpty()); #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS) m_deviceMotionClient->deviceMotionControllerDestroyed(); m_deviceOrientationClient->deviceOrientationControllerDestroyed(); #endif - -#if ENABLE(TEMPLATE_ELEMENT) + if (m_templateDocument) m_templateDocument->setTemplateDocumentHost(nullptr); // balanced in templateDocument(). -#endif // FIXME: Should we reset m_domWindow when we detach from the Frame? if (m_domWindow) - m_domWindow->resetUnlessSuspendedForPageCache(); + m_domWindow->resetUnlessSuspendedForDocumentSuspension(); m_scriptRunner = nullptr; - - histogramMutationEventUsage(m_listenerTypes); + m_moduleLoader = nullptr; removeAllEventListeners(); @@ -601,18 +633,22 @@ Document::~Document() if (m_styleSheetList) m_styleSheetList->detachFromDocument(); - if (m_elementSheet) - m_elementSheet->detachFromDocument(); - m_styleSheetCollection.detachFromDocument(); + extensionStyleSheets().detachFromDocument(); - clearStyleResolver(); // We need to destroy CSSFontSelector before destroying m_cachedResourceLoader. + styleScope().clearResolver(); // We need to destroy CSSFontSelector before destroying m_cachedResourceLoader. + m_fontSelector->clearDocument(); + m_fontSelector->unregisterForInvalidationCallbacks(*this); // It's possible for multiple Documents to end up referencing the same CachedResourceLoader (e.g., SVGImages // load the initial empty document and the SVGDocument with the same DocumentLoader). if (m_cachedResourceLoader->document() == this) m_cachedResourceLoader->setDocument(nullptr); - m_cachedResourceLoader.clear(); +#if ENABLE(VIDEO) + if (auto* platformMediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists()) + platformMediaSessionManager->stopAllMediaPlaybackForDocument(this); +#endif + // We must call clearRareData() here since a Document class inherits TreeScope // as well as Node. See a comment on TreeScope.h for the reason. if (hasRareData()) @@ -623,56 +659,68 @@ Document::~Document() for (unsigned i = 0; i < WTF_ARRAY_LENGTH(m_nodeListAndCollectionCounts); ++i) ASSERT(!m_nodeListAndCollectionCounts[i]); - - clearDocumentScope(); - - InspectorCounters::decrementCounter(InspectorCounters::DocumentCounter); } -void Document::dropChildren() +void Document::removedLastRef() { ASSERT(!m_deletionHasBegun); - - // We must make sure not to be retaining any of our children through - // these extra pointers or we will create a reference cycle. - m_focusedElement = nullptr; - m_hoveredElement = nullptr; - m_activeElement = nullptr; - m_titleElement = nullptr; - m_documentElement = nullptr; - m_userActionElements.documentDidRemoveLastRef(); + if (m_referencingNodeCount) { + // If removing a child removes the last node reference, we don't want the scope to be destroyed + // until after removeDetachedChildren returns, so we protect ourselves. + incrementReferencingNodeCount(); + + RELEASE_ASSERT(!hasLivingRenderTree()); + // We must make sure not to be retaining any of our children through + // these extra pointers or we will create a reference cycle. + m_focusedElement = nullptr; + m_hoveredElement = nullptr; + m_activeElement = nullptr; + m_titleElement = nullptr; + m_documentElement = nullptr; + m_focusNavigationStartingNode = nullptr; + m_userActionElements.documentDidRemoveLastRef(); #if ENABLE(FULLSCREEN_API) - m_fullScreenElement = nullptr; - m_fullScreenElementStack.clear(); + m_fullScreenElement = nullptr; + m_fullScreenElementStack.clear(); #endif + m_associatedFormControls.clear(); - detachParser(); - - // removeDetachedChildren() doesn't always unregister IDs, - // so tear down scope information up front to avoid having - // stale references in the map. - - destroyTreeScopeData(); - removeDetachedChildren(); - m_formController.clear(); + detachParser(); - m_markers->detach(); + // removeDetachedChildren() doesn't always unregister IDs, + // so tear down scope information up front to avoid having + // stale references in the map. - m_cssCanvasElements.clear(); + destroyTreeScopeData(); + removeDetachedChildren(); + m_formController = nullptr; + + m_markers->detach(); + + m_cssCanvasElements.clear(); + + commonTeardown(); - commonTeardown(); +#ifndef NDEBUG + // We need to do this right now since selfOnlyDeref() can delete this. + m_inRemovedLastRefFunction = false; +#endif + decrementReferencingNodeCount(); + } else { +#ifndef NDEBUG + m_inRemovedLastRefFunction = false; + m_deletionHasBegun = true; +#endif + delete this; + } } void Document::commonTeardown() { -#if ENABLE(SVG) if (svgExtensions()) - accessSVGExtensions()->pauseAnimations(); -#endif + accessSVGExtensions().pauseAnimations(); -#if ENABLE(REQUEST_ANIMATION_FRAME) clearScriptedAnimationController(); -#endif } Element* Document::getElementByAccessKey(const String& key) @@ -689,9 +737,8 @@ Element* Document::getElementByAccessKey(const String& key) void Document::buildAccessKeyMap(TreeScope* scope) { ASSERT(scope); - ContainerNode* rootNode = scope->rootNode(); - for (auto& element : descendantsOfType<Element>(*rootNode)) { - const AtomicString& accessKey = element.fastGetAttribute(accesskeyAttr); + for (auto& element : descendantsOfType<Element>(scope->rootNode())) { + const AtomicString& accessKey = element.attributeWithoutSynchronization(accesskeyAttr); if (!accessKey.isEmpty()) m_elementsByAccessKey.set(accessKey.impl(), &element); @@ -706,49 +753,55 @@ void Document::invalidateAccessKeyMap() m_elementsByAccessKey.clear(); } -void Document::addImageElementByLowercasedUsemap(const AtomicStringImpl& name, HTMLImageElement& element) +void Document::addImageElementByUsemap(const AtomicStringImpl& name, HTMLImageElement& element) { return m_imagesByUsemap.add(name, element, *this); } -void Document::removeImageElementByLowercasedUsemap(const AtomicStringImpl& name, HTMLImageElement& element) +void Document::removeImageElementByUsemap(const AtomicStringImpl& name, HTMLImageElement& element) { return m_imagesByUsemap.remove(name, element); } -HTMLImageElement* Document::imageElementByLowercasedUsemap(const AtomicStringImpl& name) const +HTMLImageElement* Document::imageElementByUsemap(const AtomicStringImpl& name) const { - return m_imagesByUsemap.getElementByLowercasedUsemap(name, *this); + return m_imagesByUsemap.getElementByUsemap(name, *this); } -SelectorQueryCache& Document::selectorQueryCache() +ExceptionOr<SelectorQuery&> Document::selectorQueryForString(const String& selectorString) { + if (selectorString.isEmpty()) + return Exception { SYNTAX_ERR }; if (!m_selectorQueryCache) - m_selectorQueryCache = adoptPtr(new SelectorQueryCache()); - return *m_selectorQueryCache; + m_selectorQueryCache = std::make_unique<SelectorQueryCache>(); + return m_selectorQueryCache->add(selectorString, *this); +} + +void Document::clearSelectorQueryCache() +{ + m_selectorQueryCache = nullptr; } MediaQueryMatcher& Document::mediaQueryMatcher() { if (!m_mediaQueryMatcher) - m_mediaQueryMatcher = MediaQueryMatcher::create(this); + m_mediaQueryMatcher = MediaQueryMatcher::create(*this); return *m_mediaQueryMatcher; } -void Document::setCompatibilityMode(CompatibilityMode mode) +void Document::setCompatibilityMode(DocumentCompatibilityMode mode) { if (m_compatibilityModeLocked || mode == m_compatibilityMode) return; bool wasInQuirksMode = inQuirksMode(); m_compatibilityMode = mode; - if (m_selectorQueryCache) - m_selectorQueryCache->invalidate(); + clearSelectorQueryCache(); if (inQuirksMode() != wasInQuirksMode) { // All user stylesheets have to reparse using the different mode. - m_styleSheetCollection.clearPageUserSheet(); - m_styleSheetCollection.invalidateInjectedStyleSheetCache(); + extensionStyleSheets().clearPageUserSheet(); + extensionStyleSheets().invalidateInjectedStyleSheetCache(); } } @@ -769,19 +822,19 @@ void Document::resetVisitedLinkColor() void Document::resetActiveLinkColor() { - m_activeLinkColor.setNamedColor("red"); + m_activeLinkColor = Color(255, 0, 0); } -DOMImplementation* Document::implementation() +DOMImplementation& Document::implementation() { if (!m_implementation) - m_implementation = DOMImplementation::create(*this); - return m_implementation.get(); + m_implementation = std::make_unique<DOMImplementation>(*this); + return *m_implementation; } bool Document::hasManifest() const { - return documentElement() && documentElement()->hasTagName(htmlTag) && documentElement()->hasAttribute(manifestAttr); + return documentElement() && documentElement()->hasTagName(htmlTag) && documentElement()->hasAttributeWithoutSynchronization(manifestAttr); } DocumentType* Document::doctype() const @@ -797,235 +850,188 @@ void Document::childrenChanged(const ChildChange& change) { ContainerNode::childrenChanged(change); - // NOTE: Per DOM, dynamically inserting/removing doctype nodes doesn't affect compatibility mode. - -#if ENABLE(LEGACY_VIEWPORT_ADAPTION) - // FIXME: It's a little strange to add these rules when a DocumentType with this prefix is added, - // but not remove these rules when a DocumentType with this prefix is removed. It seems this should - // be handled more the way the compatibility mode is, by fetching the doctype at the appropriate time, - // rather than by responding when a document type node is inserted. - if (DocumentType* documentType = doctype()) { - if (documentType->publicId().startsWith("-//wapforum//dtd xhtml mobile 1.", /* caseSensitive */ false)) - processViewport("width=device-width, height=device-height", ViewportArguments::XHTMLMobileProfile); - } -#endif + // FIXME: Chrome::didReceiveDocType() used to be called only when the doctype changed. We need to check the + // impact of calling this systematically. If the overhead is negligible, we need to rename didReceiveDocType, + // otherwise, we need to detect the doc type changes before updating the viewport. + if (Page* page = this->page()) + page->chrome().didReceiveDocType(*frame()); Element* newDocumentElement = childrenOfType<Element>(*this).first(); - if (newDocumentElement == m_documentElement) return; m_documentElement = newDocumentElement; // The root style used for media query matching depends on the document element. - clearStyleResolver(); + styleScope().clearResolver(); } -PassRefPtr<Element> Document::createElement(const AtomicString& name, ExceptionCode& ec) +static ALWAYS_INLINE Ref<HTMLElement> createUpgradeCandidateElement(Document& document, const QualifiedName& name) { - if (!isValidName(name)) { - ec = INVALID_CHARACTER_ERR; - return nullptr; + if (!RuntimeEnabledFeatures::sharedFeatures().customElementsEnabled() + || Document::validateCustomElementName(name.localName()) != CustomElementNameValidationStatus::Valid) + return HTMLUnknownElement::create(name, document); + + auto element = HTMLElement::create(name, document); + element->setIsCustomElementUpgradeCandidate(); + return element; +} + +static ALWAYS_INLINE Ref<HTMLElement> createUpgradeCandidateElement(Document& document, const AtomicString& localName) +{ + return createUpgradeCandidateElement(document, QualifiedName { nullAtom, localName, xhtmlNamespaceURI }); +} + +static inline bool isValidHTMLElementName(const AtomicString& localName) +{ + return Document::isValidName(localName); +} + +static inline bool isValidHTMLElementName(const QualifiedName& name) +{ + return Document::isValidName(name.localName()); +} + +template<typename NameType> +static ExceptionOr<Ref<Element>> createHTMLElementWithNameValidation(Document& document, const NameType& name) +{ + auto element = HTMLElementFactory::createKnownElement(name, document); + if (LIKELY(element)) + return Ref<Element> { element.releaseNonNull() }; + + if (auto* window = document.domWindow()) { + auto* registry = window->customElementRegistry(); + if (UNLIKELY(registry)) { + if (auto* elementInterface = registry->findInterface(name)) + return elementInterface->constructElementWithFallback(document, name); + } } + if (UNLIKELY(!isValidHTMLElementName(name))) + return Exception { INVALID_CHARACTER_ERR }; + + return Ref<Element> { createUpgradeCandidateElement(document, name) }; +} + +ExceptionOr<Ref<Element>> Document::createElementForBindings(const AtomicString& name) +{ + if (isHTMLDocument()) + return createHTMLElementWithNameValidation(*this, name.convertToASCIILowercase()); + if (isXHTMLDocument()) - return HTMLElementFactory::createElement(QualifiedName(nullAtom, name, xhtmlNamespaceURI), *this); + return createHTMLElementWithNameValidation(*this, name); + + if (!isValidName(name)) + return Exception { INVALID_CHARACTER_ERR }; return createElement(QualifiedName(nullAtom, name, nullAtom), false); } -PassRefPtr<DocumentFragment> Document::createDocumentFragment() +Ref<DocumentFragment> Document::createDocumentFragment() { return DocumentFragment::create(document()); } -PassRefPtr<Text> Document::createTextNode(const String& data) +Ref<Text> Document::createTextNode(const String& data) { return Text::create(*this, data); } -PassRefPtr<Comment> Document::createComment(const String& data) +Ref<Comment> Document::createComment(const String& data) { return Comment::create(*this, data); } -PassRefPtr<CDATASection> Document::createCDATASection(const String& data, ExceptionCode& ec) +ExceptionOr<Ref<CDATASection>> Document::createCDATASection(const String& data) { - if (isHTMLDocument()) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } + if (isHTMLDocument()) + return Exception { NOT_SUPPORTED_ERR }; return CDATASection::create(*this, data); } -PassRefPtr<ProcessingInstruction> Document::createProcessingInstruction(const String& target, const String& data, ExceptionCode& ec) +ExceptionOr<Ref<ProcessingInstruction>> Document::createProcessingInstruction(const String& target, const String& data) { - if (!isValidName(target)) { - ec = INVALID_CHARACTER_ERR; - return nullptr; - } - if (isHTMLDocument()) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - return ProcessingInstruction::create(*this, target, data); -} + if (!isValidName(target)) + return Exception { INVALID_CHARACTER_ERR }; -PassRefPtr<EntityReference> Document::createEntityReference(const String& name, ExceptionCode& ec) -{ - if (!isValidName(name)) { - ec = INVALID_CHARACTER_ERR; - return nullptr; - } - if (isHTMLDocument()) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - return EntityReference::create(*this, name); + if (data.contains("?>")) + return Exception { INVALID_CHARACTER_ERR }; + + return ProcessingInstruction::create(*this, target, data); } -PassRefPtr<Text> Document::createEditingTextNode(const String& text) +Ref<Text> Document::createEditingTextNode(const String& text) { return Text::createEditingText(*this, text); } -PassRefPtr<CSSStyleDeclaration> Document::createCSSStyleDeclaration() +Ref<CSSStyleDeclaration> Document::createCSSStyleDeclaration() { Ref<MutableStyleProperties> propertySet(MutableStyleProperties::create()); - return propertySet->ensureCSSStyleDeclaration(); + return *propertySet->ensureCSSStyleDeclaration(); } -PassRefPtr<Node> Document::importNode(Node* importedNode, bool deep, ExceptionCode& ec) +ExceptionOr<Ref<Node>> Document::importNode(Node& nodeToImport, bool deep) { - ec = 0; - - if (!importedNode) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - - switch (importedNode->nodeType()) { + switch (nodeToImport.nodeType()) { + case DOCUMENT_FRAGMENT_NODE: + if (nodeToImport.isShadowRoot()) + break; + FALLTHROUGH; + case ELEMENT_NODE: case TEXT_NODE: - return createTextNode(importedNode->nodeValue()); case CDATA_SECTION_NODE: - return createCDATASection(importedNode->nodeValue(), ec); - case ENTITY_REFERENCE_NODE: - return createEntityReference(importedNode->nodeName(), ec); case PROCESSING_INSTRUCTION_NODE: - return createProcessingInstruction(importedNode->nodeName(), importedNode->nodeValue(), ec); case COMMENT_NODE: - return createComment(importedNode->nodeValue()); - case ELEMENT_NODE: { - Element& oldElement = toElement(*importedNode); - // FIXME: The following check might be unnecessary. Is it possible that - // oldElement has mismatched prefix/namespace? - if (!hasValidNamespaceForElements(oldElement.tagQName())) { - ec = NAMESPACE_ERR; - return nullptr; - } - - RefPtr<Element> newElement = createElement(oldElement.tagQName(), false); - newElement->cloneDataFromElement(oldElement); - - if (deep) { - for (Node* oldChild = oldElement.firstChild(); oldChild; oldChild = oldChild->nextSibling()) { - RefPtr<Node> newChild = importNode(oldChild, true, ec); - if (ec) - return nullptr; - newElement->appendChild(newChild.release(), ec); - if (ec) - return nullptr; - } - } + return nodeToImport.cloneNodeInternal(document(), deep ? CloningOperation::Everything : CloningOperation::OnlySelf); - return newElement.release(); - } case ATTRIBUTE_NODE: - return Attr::create(*this, QualifiedName(nullAtom, toAttr(*importedNode).name(), nullAtom), toAttr(*importedNode).value()); - case DOCUMENT_FRAGMENT_NODE: { - if (importedNode->isShadowRoot()) { - // ShadowRoot nodes should not be explicitly importable. - // Either they are imported along with their host node, or created implicitly. - break; - } - DocumentFragment& oldFragment = toDocumentFragment(*importedNode); - RefPtr<DocumentFragment> newFragment = createDocumentFragment(); - if (deep) { - for (Node* oldChild = oldFragment.firstChild(); oldChild; oldChild = oldChild->nextSibling()) { - RefPtr<Node> newChild = importNode(oldChild, true, ec); - if (ec) - return nullptr; - newFragment->appendChild(newChild.release(), ec); - if (ec) - return nullptr; - } - } - - return newFragment.release(); - } - case ENTITY_NODE: - case NOTATION_NODE: - // FIXME: It should be possible to import these node types, however in DOM3 the DocumentType is readonly, so there isn't much sense in doing that. - // Ability to add these imported nodes to a DocumentType will be considered for addition to a future release of the DOM. - case DOCUMENT_NODE: - case DOCUMENT_TYPE_NODE: - case XPATH_NAMESPACE_NODE: + // FIXME: This will "Attr::normalize" child nodes of Attr. + return Ref<Node> { Attr::create(*this, QualifiedName(nullAtom, downcast<Attr>(nodeToImport).name(), nullAtom), downcast<Attr>(nodeToImport).value()) }; + + case DOCUMENT_NODE: // Can't import a document into another document. + case DOCUMENT_TYPE_NODE: // FIXME: Support cloning a DocumentType node per DOM4. break; } - ec = NOT_SUPPORTED_ERR; - return nullptr; + + return Exception { NOT_SUPPORTED_ERR }; } -PassRefPtr<Node> Document::adoptNode(PassRefPtr<Node> source, ExceptionCode& ec) +ExceptionOr<Ref<Node>> Document::adoptNode(Node& source) { - if (!source) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - - if (source->isReadOnlyNode()) { - ec = NO_MODIFICATION_ALLOWED_ERR; - return nullptr; - } - EventQueueScope scope; - switch (source->nodeType()) { - case ENTITY_NODE: - case NOTATION_NODE: + switch (source.nodeType()) { case DOCUMENT_NODE: - case DOCUMENT_TYPE_NODE: - case XPATH_NAMESPACE_NODE: - ec = NOT_SUPPORTED_ERR; - return nullptr; - case ATTRIBUTE_NODE: { - Attr& attr = toAttr(*source); - if (attr.ownerElement()) - attr.ownerElement()->removeAttributeNode(&attr, ec); + return Exception { NOT_SUPPORTED_ERR }; + case ATTRIBUTE_NODE: { + auto& attr = downcast<Attr>(source); + if (auto* element = attr.ownerElement()) { + auto result = element->removeAttributeNode(attr); + if (result.hasException()) + return result.releaseException(); + } break; } default: - if (source->isShadowRoot()) { + if (source.isShadowRoot()) { // ShadowRoot cannot disconnect itself from the host node. - ec = HIERARCHY_REQUEST_ERR; - return nullptr; - } - if (source->isFrameOwnerElement()) { - HTMLFrameOwnerElement& frameOwnerElement = toHTMLFrameOwnerElement(*source); - if (frame() && frame()->tree().isDescendantOf(frameOwnerElement.contentFrame())) { - ec = HIERARCHY_REQUEST_ERR; - return nullptr; - } + return Exception { HIERARCHY_REQUEST_ERR }; } - if (source->parentNode()) { - source->parentNode()->removeChild(source.get(), ec); - if (ec) - return nullptr; + if (is<HTMLFrameOwnerElement>(source)) { + auto& frameOwnerElement = downcast<HTMLFrameOwnerElement>(source); + if (frame() && frame()->tree().isDescendantOf(frameOwnerElement.contentFrame())) + return Exception { HIERARCHY_REQUEST_ERR }; } + auto result = source.remove(); + if (result.hasException()) + return result.releaseException(); + ASSERT_WITH_SECURITY_IMPLICATION(!source.isConnected()); + ASSERT_WITH_SECURITY_IMPLICATION(!source.parentNode()); } - adoptIfNeeded(source.get()); + adoptIfNeeded(source); - return source; + return Ref<Node> { source }; } bool Document::hasValidNamespaceForElements(const QualifiedName& qName) @@ -1050,18 +1056,34 @@ bool Document::hasValidNamespaceForAttributes(const QualifiedName& qName) return hasValidNamespaceForElements(qName); } +static Ref<HTMLElement> createFallbackHTMLElement(Document& document, const QualifiedName& name) +{ + if (auto* window = document.domWindow()) { + auto* registry = window->customElementRegistry(); + if (UNLIKELY(registry)) { + if (auto* elementInterface = registry->findInterface(name)) { + auto element = HTMLElement::create(name, document); + element->enqueueToUpgrade(*elementInterface); + return element; + } + } + } + // FIXME: Should we also check the equality of prefix between the custom element and name? + return createUpgradeCandidateElement(document, name); +} + // FIXME: This should really be in a possible ElementFactory class. -PassRefPtr<Element> Document::createElement(const QualifiedName& name, bool createdByParser) +Ref<Element> Document::createElement(const QualifiedName& name, bool createdByParser) { RefPtr<Element> element; // FIXME: Use registered namespaces and look up in a hash to find the right factory. - if (name.namespaceURI() == xhtmlNamespaceURI) - element = HTMLElementFactory::createElement(name, *this, nullptr, createdByParser); -#if ENABLE(SVG) - else if (name.namespaceURI() == SVGNames::svgNamespaceURI) + if (name.namespaceURI() == xhtmlNamespaceURI) { + element = HTMLElementFactory::createKnownElement(name, *this, nullptr, createdByParser); + if (UNLIKELY(!element)) + element = createFallbackHTMLElement(*this, name); + } else if (name.namespaceURI() == SVGNames::svgNamespaceURI) element = SVGElementFactory::createElement(name, *this, createdByParser); -#endif #if ENABLE(MATHML) else if (name.namespaceURI() == MathMLNames::mathmlNamespaceURI) element = MathMLElementFactory::createElement(name, *this, createdByParser); @@ -1075,76 +1097,88 @@ PassRefPtr<Element> Document::createElement(const QualifiedName& name, bool crea // <image> uses imgTag so we need a special rule. ASSERT((name.matches(imageTag) && element->tagQName().matches(imgTag) && element->tagQName().prefix() == name.prefix()) || name == element->tagQName()); - return element.release(); + return element.releaseNonNull(); } -bool Document::regionBasedColumnsEnabled() const +CustomElementNameValidationStatus Document::validateCustomElementName(const AtomicString& localName) { - return settings() && settings()->regionBasedColumnsEnabled(); -} + bool containsHyphen = false; + for (auto character : StringView(localName).codeUnits()) { + if (isASCIIUpper(character)) + return CustomElementNameValidationStatus::ContainsUpperCase; + if (character == '-') + containsHyphen = true; + } -bool Document::cssStickyPositionEnabled() const -{ - return settings() && settings()->cssStickyPositionEnabled(); -} + if (!containsHyphen) + return CustomElementNameValidationStatus::NoHyphen; -bool Document::cssRegionsEnabled() const -{ - return RuntimeEnabledFeatures::sharedFeatures().cssRegionsEnabled(); -} +#if ENABLE(MATHML) + const auto& annotationXmlLocalName = MathMLNames::annotation_xmlTag.localName(); +#else + static NeverDestroyed<const AtomicString> annotationXmlLocalName("annotation-xml", AtomicString::ConstructFromLiteral); +#endif -bool Document::cssCompositingEnabled() const -{ - return RuntimeEnabledFeatures::sharedFeatures().cssCompositingEnabled(); + if (localName == SVGNames::color_profileTag.localName() + || localName == SVGNames::font_faceTag.localName() + || localName == SVGNames::font_face_formatTag.localName() + || localName == SVGNames::font_face_nameTag.localName() + || localName == SVGNames::font_face_srcTag.localName() + || localName == SVGNames::font_face_uriTag.localName() + || localName == SVGNames::missing_glyphTag.localName() + || localName == annotationXmlLocalName) + return CustomElementNameValidationStatus::ConflictsWithBuiltinNames; + + return CustomElementNameValidationStatus::Valid; } -bool Document::cssGridLayoutEnabled() const +bool Document::isCSSGridLayoutEnabled() const { - return settings() && settings()->cssGridLayoutEnabled(); + return RuntimeEnabledFeatures::sharedFeatures().isCSSGridLayoutEnabled(); } #if ENABLE(CSS_REGIONS) -PassRefPtr<DOMNamedFlowCollection> Document::webkitGetNamedFlows() +RefPtr<DOMNamedFlowCollection> Document::webkitGetNamedFlows() { - if (!cssRegionsEnabled() || !renderView()) + if (!renderView()) return nullptr; updateStyleIfNeeded(); - return namedFlows()->createCSSOMSnapshot(); + return namedFlows().createCSSOMSnapshot(); } #endif -NamedFlowCollection* Document::namedFlows() +NamedFlowCollection& Document::namedFlows() { if (!m_namedFlows) m_namedFlows = NamedFlowCollection::create(this); - return m_namedFlows.get(); + return *m_namedFlows; } -PassRefPtr<Element> Document::createElementNS(const String& namespaceURI, const String& qualifiedName, ExceptionCode& ec) +ExceptionOr<Ref<Element>> Document::createElementNS(const AtomicString& namespaceURI, const String& qualifiedName) { - String prefix, localName; - if (!parseQualifiedName(qualifiedName, prefix, localName, ec)) - return nullptr; + auto parseResult = parseQualifiedName(namespaceURI, qualifiedName); + if (parseResult.hasException()) + return parseResult.releaseException(); + QualifiedName parsedName { parseResult.releaseReturnValue() }; + if (!hasValidNamespaceForElements(parsedName)) + return Exception { NAMESPACE_ERR }; - QualifiedName qName(prefix, localName, namespaceURI); - if (!hasValidNamespaceForElements(qName)) { - ec = NAMESPACE_ERR; - return nullptr; - } + if (parsedName.namespaceURI() == xhtmlNamespaceURI) + return createHTMLElementWithNameValidation(*this, parsedName); - return createElement(qName, false); + return createElement(parsedName, false); } String Document::readyState() const { - DEFINE_STATIC_LOCAL(const String, loading, (ASCIILiteral("loading"))); - DEFINE_STATIC_LOCAL(const String, interactive, (ASCIILiteral("interactive"))); - DEFINE_STATIC_LOCAL(const String, complete, (ASCIILiteral("complete"))); + static NeverDestroyed<const String> loading(ASCIILiteral("loading")); + static NeverDestroyed<const String> interactive(ASCIILiteral("interactive")); + static NeverDestroyed<const String> complete(ASCIILiteral("complete")); switch (m_readyState) { case Loading: @@ -1168,15 +1202,15 @@ void Document::setReadyState(ReadyState readyState) switch (readyState) { case Loading: if (!m_documentTiming.domLoading) - m_documentTiming.domLoading = monotonicallyIncreasingTime(); + m_documentTiming.domLoading = MonotonicTime::now(); break; case Interactive: if (!m_documentTiming.domInteractive) - m_documentTiming.domInteractive = monotonicallyIncreasingTime(); + m_documentTiming.domInteractive = MonotonicTime::now(); break; case Complete: if (!m_documentTiming.domComplete) - m_documentTiming.domComplete = monotonicallyIncreasingTime(); + m_documentTiming.domComplete = MonotonicTime::now(); break; } #endif @@ -1184,13 +1218,13 @@ void Document::setReadyState(ReadyState readyState) m_readyState = readyState; dispatchEvent(Event::create(eventNames().readystatechangeEvent, false, false)); - if (settings() && settings()->suppressesIncrementalRendering()) + if (settings().suppressesIncrementalRendering()) setVisualUpdatesAllowed(readyState); } void Document::setVisualUpdatesAllowed(ReadyState readyState) { - ASSERT(settings() && settings()->suppressesIncrementalRendering()); + ASSERT(settings().suppressesIncrementalRendering()); switch (readyState) { case Loading: ASSERT(!m_visualUpdatesSuppressionTimer.isActive()); @@ -1224,7 +1258,7 @@ void Document::setVisualUpdatesAllowed(bool visualUpdatesAllowed) if (visualUpdatesAllowed) m_visualUpdatesSuppressionTimer.stop(); else - m_visualUpdatesSuppressionTimer.startOneShot(settings()->incrementalRenderingSuppressionTimeoutInSeconds()); + m_visualUpdatesSuppressionTimer.startOneShot(settings().incrementalRenderingSuppressionTimeoutInSeconds()); if (!visualUpdatesAllowed) return; @@ -1238,14 +1272,12 @@ void Document::setVisualUpdatesAllowed(bool visualUpdatesAllowed) if (frame()->isMainFrame()) { frameView->addPaintPendingMilestones(DidFirstPaintAfterSuppressedIncrementalRendering); if (page->requestedLayoutMilestones() & DidFirstLayoutAfterSuppressedIncrementalRendering) - frame()->loader().didLayout(DidFirstLayoutAfterSuppressedIncrementalRendering); + frame()->loader().didReachLayoutMilestone(DidFirstLayoutAfterSuppressedIncrementalRendering); } } -#if USE(ACCELERATED_COMPOSITING) if (view()) view()->updateCompositingLayersAfterLayout(); -#endif if (RenderView* renderView = this->renderView()) renderView->repaintViewAndCompositedLayers(); @@ -1254,7 +1286,7 @@ void Document::setVisualUpdatesAllowed(bool visualUpdatesAllowed) frame->loader().forcePageTransitionIfNeeded(); } -void Document::visualUpdatesSuppressionTimerFired(Timer<Document>&) +void Document::visualUpdatesSuppressionTimerFired() { ASSERT(!m_visualUpdatesAllowed); @@ -1275,18 +1307,19 @@ void Document::setVisualUpdatesAllowedByClient(bool visualUpdatesAllowedByClient setVisualUpdatesAllowed(true); } -String Document::encoding() const +String Document::characterSetWithUTF8Fallback() const { - if (TextResourceDecoder* d = decoder()) - return d->encoding().domName(); - return String(); + AtomicString name = encoding(); + if (!name.isNull()) + return name; + return UTF8Encoding().domName(); } -String Document::defaultCharset() const +String Document::defaultCharsetForLegacyBindings() const { - if (Settings* settings = this->settings()) - return settings->defaultTextEncodingName(); - return String(); + if (!frame()) + UTF8Encoding().domName(); + return settings().defaultTextEncodingName(); } void Document::setCharset(const String& charset) @@ -1303,31 +1336,20 @@ void Document::setContentLanguage(const String& language) m_contentLanguage = language; // Recalculate style so language is used when selecting the initial font. - styleResolverChanged(DeferRecalcStyle); + m_styleScope->didChangeStyleSheetEnvironment(); } -void Document::setXMLVersion(const String& version, ExceptionCode& ec) +ExceptionOr<void> Document::setXMLVersion(const String& version) { - if (!implementation()->hasFeature("XML", String())) { - ec = NOT_SUPPORTED_ERR; - return; - } - - if (!XMLDocumentParser::supportsXMLVersion(version)) { - ec = NOT_SUPPORTED_ERR; - return; - } + if (!XMLDocumentParser::supportsXMLVersion(version)) + return Exception { NOT_SUPPORTED_ERR }; m_xmlVersion = version; + return { }; } -void Document::setXMLStandalone(bool standalone, ExceptionCode& ec) +void Document::setXMLStandalone(bool standalone) { - if (!implementation()->hasFeature("XML", String())) { - ec = NOT_SUPPORTED_ERR; - return; - } - m_xmlStandalone = standalone ? Standalone : NotStandalone; } @@ -1338,11 +1360,6 @@ void Document::setDocumentURI(const String& uri) updateBaseURL(); } -URL Document::baseURI() const -{ - return m_baseURL; -} - void Document::setContent(const String& content) { open(); @@ -1369,99 +1386,102 @@ String Document::suggestedMIMEType() const return String(); } -Element* Document::elementFromPoint(int x, int y) const +void Document::overrideMIMEType(const String& mimeType) { - if (!hasLivingRenderTree()) - return nullptr; + m_overriddenMIMEType = mimeType; +} + +String Document::contentType() const +{ + if (!m_overriddenMIMEType.isNull()) + return m_overriddenMIMEType; + + if (DocumentLoader* documentLoader = loader()) + return documentLoader->currentContentType(); + + String mimeType = suggestedMIMEType(); + if (!mimeType.isNull()) + return mimeType; - return TreeScope::elementFromPoint(x, y); + return ASCIILiteral("application/xml"); } -PassRefPtr<Range> Document::caretRangeFromPoint(int x, int y) +RefPtr<Range> Document::caretRangeFromPoint(int x, int y) +{ + return caretRangeFromPoint(LayoutPoint(x, y)); +} + +RefPtr<Range> Document::caretRangeFromPoint(const LayoutPoint& clientPoint) { if (!hasLivingRenderTree()) return nullptr; + LayoutPoint localPoint; - Node* node = nodeFromPoint(this, x, y, &localPoint); + Node* node = nodeFromPoint(clientPoint, &localPoint); if (!node) return nullptr; - Node* shadowAncestorNode = ancestorInThisScope(node); - if (shadowAncestorNode != node) { - unsigned offset = shadowAncestorNode->nodeIndex(); - ContainerNode* container = shadowAncestorNode->parentNode(); - return Range::create(*this, container, offset, container, offset); - } - RenderObject* renderer = node->renderer(); if (!renderer) return nullptr; - VisiblePosition visiblePosition = renderer->positionForPoint(localPoint); - if (visiblePosition.isNull()) + Position rangeCompliantPosition = renderer->positionForPoint(localPoint).parentAnchoredEquivalent(); + if (rangeCompliantPosition.isNull()) return nullptr; - Position rangeCompliantPosition = visiblePosition.deepEquivalent().parentAnchoredEquivalent(); - return Range::create(*this, rangeCompliantPosition, rangeCompliantPosition); + unsigned offset = rangeCompliantPosition.offsetInContainerNode(); + node = &retargetToScope(*rangeCompliantPosition.containerNode()); + if (node != rangeCompliantPosition.containerNode()) + offset = 0; + + return Range::create(*this, node, offset, node, offset); } -/* - * Performs three operations: - * 1. Convert control characters to spaces - * 2. Trim leading and trailing spaces - * 3. Collapse internal whitespace. - */ -template <typename CharacterType> -static inline StringWithDirection canonicalizedTitle(Document* document, const StringWithDirection& titleWithDirection) +Element* Document::scrollingElement() { - const String& title = titleWithDirection.string(); - const CharacterType* characters = title.getCharacters<CharacterType>(); - unsigned length = title.length(); - unsigned i; - - StringBuffer<CharacterType> buffer(length); - unsigned builderIndex = 0; + // FIXME: When we fix https://bugs.webkit.org/show_bug.cgi?id=106133, this should be replaced with the full implementation + // of Document.scrollingElement() as specified at http://dev.w3.org/csswg/cssom-view/#dom-document-scrollingelement. - // Skip leading spaces and leading characters that would convert to spaces - for (i = 0; i < length; ++i) { - CharacterType c = characters[i]; - if (!(c <= 0x20 || c == 0x7F)) - break; - } + return body(); +} - if (i == length) - return StringWithDirection(); +template<typename CharacterType> static inline String canonicalizedTitle(Document& document, const String& title) +{ + // FIXME: Compiling a separate copy of this for LChar and UChar is likely unnecessary. + // FIXME: Missing an optimized case for when title is fine as-is. This unnecessarily allocates + // and keeps around a new copy, and it's even the less optimal type of StringImpl with a separate buffer. + // Could probably just use StringBuilder instead. - // Replace control characters with spaces, and backslashes with currency symbols, and collapse whitespace. - bool previousCharWasWS = false; - for (; i < length; ++i) { - CharacterType c = characters[i]; - if (c <= 0x20 || c == 0x7F || (U_GET_GC_MASK(c) & (U_GC_ZL_MASK | U_GC_ZP_MASK))) { - if (previousCharWasWS) - continue; - buffer[builderIndex++] = ' '; - previousCharWasWS = true; - } else { - buffer[builderIndex++] = c; - previousCharWasWS = false; - } - } + auto* characters = title.characters<CharacterType>(); + unsigned length = title.length(); - // Strip trailing spaces - while (builderIndex > 0) { - --builderIndex; - if (buffer[builderIndex] != ' ') - break; - } + StringBuffer<CharacterType> buffer { length }; + unsigned bufferLength = 0; - if (!builderIndex && buffer[builderIndex] == ' ') - return StringWithDirection(); + auto* decoder = document.decoder(); + auto backslashAsCurrencySymbol = decoder ? decoder->encoding().backslashAsCurrencySymbol() : '\\'; - buffer.shrink(builderIndex + 1); + // Collapse runs of HTML spaces into single space characters. + // Strip leading and trailing spaces. + // Replace backslashes with currency symbols. + bool previousCharacterWasHTMLSpace = false; + for (unsigned i = 0; i < length; ++i) { + auto character = characters[i]; + if (isHTMLSpace(character)) + previousCharacterWasHTMLSpace = true; + else { + if (character == '\\') + character = backslashAsCurrencySymbol; + if (previousCharacterWasHTMLSpace && bufferLength) + buffer[bufferLength++] = ' '; + buffer[bufferLength++] = character; + previousCharacterWasHTMLSpace = false; + } + } + if (!bufferLength) + return { }; - // Replace the backslashes with currency symbols if the encoding requires it. - document->displayBufferModifiedByEncoding(buffer.characters(), buffer.length()); - - return StringWithDirection(String::adopt(buffer), titleWithDirection.direction()); + buffer.shrink(bufferLength); + return String::adopt(WTFMove(buffer)); } void Document::updateTitle(const StringWithDirection& title) @@ -1470,71 +1490,104 @@ void Document::updateTitle(const StringWithDirection& title) return; m_rawTitle = title; + m_title = title; - if (m_rawTitle.string().isEmpty()) - m_title = StringWithDirection(); - else { - if (m_rawTitle.string().is8Bit()) - m_title = canonicalizedTitle<LChar>(this, m_rawTitle); + if (!m_title.string.isEmpty()) { + if (m_title.string.is8Bit()) + m_title.string = canonicalizedTitle<LChar>(*this, m_title.string); else - m_title = canonicalizedTitle<UChar>(this, m_rawTitle); + m_title.string = canonicalizedTitle<UChar>(*this, m_title.string); } - if (DocumentLoader* loader = this->loader()) + + if (auto* loader = this->loader()) loader->setTitle(m_title); } +void Document::updateTitleFromTitleElement() +{ + if (!m_titleElement) { + updateTitle({ }); + return; + } + + if (is<HTMLTitleElement>(*m_titleElement)) + updateTitle(downcast<HTMLTitleElement>(*m_titleElement).textWithDirection()); + else if (is<SVGTitleElement>(*m_titleElement)) { + // FIXME: Does the SVG title element have a text direction? + updateTitle({ downcast<SVGTitleElement>(*m_titleElement).textContent(), LTR }); + } +} + void Document::setTitle(const String& title) { - // Title set by JavaScript -- overrides any title elements. - m_titleSetExplicitly = true; - if (!isHTMLDocument() && !isXHTMLDocument()) - m_titleElement = nullptr; - else if (!m_titleElement) { - if (HTMLElement* headElement = head()) { - m_titleElement = createElement(titleTag, false); - headElement->appendChild(m_titleElement, ASSERT_NO_EXCEPTION); + if (!m_titleElement) { + if (isHTMLDocument() || isXHTMLDocument()) { + auto* headElement = head(); + if (!headElement) + return; + m_titleElement = HTMLTitleElement::create(HTMLNames::titleTag, *this); + headElement->appendChild(*m_titleElement); + } else if (isSVGDocument()) { + auto* element = documentElement(); + if (!is<SVGSVGElement>(element)) + return; + m_titleElement = SVGTitleElement::create(SVGNames::titleTag, *this); + element->insertBefore(*m_titleElement, element->firstChild()); } + } else if (!isHTMLDocument() && !isXHTMLDocument() && !isSVGDocument()) { + // FIXME: What exactly is the point of this? This seems like a strange moment + // in time to demote something from being m_titleElement, when setting the + // value of the title attribute. Do we have test coverage for this? + m_titleElement = nullptr; } - // The DOM API has no method of specifying direction, so assume LTR. - updateTitle(StringWithDirection(title, LTR)); - - if (m_titleElement && isHTMLTitleElement(m_titleElement.get())) - toHTMLTitleElement(m_titleElement.get())->setText(title); + if (is<HTMLTitleElement>(m_titleElement.get())) + downcast<HTMLTitleElement>(*m_titleElement).setTextContent(title); + else if (is<SVGTitleElement>(m_titleElement.get())) + downcast<SVGTitleElement>(*m_titleElement).setTextContent(title); + else + updateTitle({ title, LTR }); } -void Document::setTitleElement(const StringWithDirection& title, Element* titleElement) +void Document::updateTitleElement(Element* newTitleElement) { - if (titleElement != m_titleElement) { - if (m_titleElement || m_titleSetExplicitly) { - // Only allow the first title element to change the title -- others have no effect. - return; - } - m_titleElement = titleElement; + if (is<SVGSVGElement>(documentElement())) + m_titleElement = childrenOfType<SVGTitleElement>(*documentElement()).first(); + else { + if (m_titleElement) { + if (isHTMLDocument() || isXHTMLDocument()) + m_titleElement = descendantsOfType<HTMLTitleElement>(*this).first(); + } else + m_titleElement = newTitleElement; } - updateTitle(title); + updateTitleFromTitleElement(); } -void Document::removeTitle(Element* titleElement) +void Document::titleElementAdded(Element& titleElement) { - if (m_titleElement != titleElement) + if (m_titleElement == &titleElement) return; - m_titleElement = nullptr; - m_titleSetExplicitly = false; + updateTitleElement(&titleElement); +} - // Update title based on first title element in the head, if one exists. - if (HTMLElement* headElement = head()) { - if (auto firstTitle = childrenOfType<HTMLTitleElement>(*headElement).first()) - setTitleElement(firstTitle->textWithDirection(), firstTitle); - } +void Document::titleElementRemoved(Element& titleElement) +{ + if (m_titleElement != &titleElement) + return; + + updateTitleElement(nullptr); +} + +void Document::titleElementTextChanged(Element& titleElement) +{ + if (m_titleElement != &titleElement) + return; - if (!m_titleElement) - updateTitle(StringWithDirection()); + updateTitleFromTitleElement(); } -#if ENABLE(PAGE_VISIBILITY_API) void Document::registerForVisibilityStateChangedCallbacks(Element* element) { m_visibilityStateCallbackElements.add(element); @@ -1548,44 +1601,49 @@ void Document::unregisterForVisibilityStateChangedCallbacks(Element* element) void Document::visibilityStateChanged() { dispatchEvent(Event::create(eventNames().visibilitychangeEvent, false, false)); - for (auto it = m_visibilityStateCallbackElements.begin(); it != m_visibilityStateCallbackElements.end(); ++it) - (*it)->visibilityStateChanged(); + for (auto* element : m_visibilityStateCallbackElements) + element->visibilityStateChanged(); } -PageVisibilityState Document::pageVisibilityState() const +auto Document::visibilityState() const -> VisibilityState { // The visibility of the document is inherited from the visibility of the // page. If there is no page associated with the document, we will assume // that the page is hidden, as specified by the spec: // http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html#dom-document-hidden if (!m_frame || !m_frame->page()) - return PageVisibilityStateHidden; + return VisibilityState::Hidden; return m_frame->page()->visibilityState(); } -String Document::visibilityState() const +bool Document::hidden() const { - return pageVisibilityStateString(pageVisibilityState()); + return visibilityState() != VisibilityState::Visible; } -bool Document::hidden() const +#if ENABLE(VIDEO) + +void Document::registerForAllowsMediaDocumentInlinePlaybackChangedCallbacks(HTMLMediaElement& element) { - return pageVisibilityState() != PageVisibilityStateVisible; + m_allowsMediaDocumentInlinePlaybackElements.add(&element); } -#endif -#if ENABLE(CSP_NEXT) -DOMSecurityPolicy* Document::securityPolicy() +void Document::unregisterForAllowsMediaDocumentInlinePlaybackChangedCallbacks(HTMLMediaElement& element) { - if (!m_domSecurityPolicy) - m_domSecurityPolicy = DOMSecurityPolicy::create(this); - return m_domSecurityPolicy.get(); + m_allowsMediaDocumentInlinePlaybackElements.remove(&element); } + +void Document::allowsMediaDocumentInlinePlaybackChanged() +{ + for (auto* element : m_allowsMediaDocumentInlinePlaybackElements) + element->allowsMediaDocumentInlinePlaybackChanged(); +} + #endif String Document::nodeName() const { - return "#document"; + return ASCIILiteral("#document"); } Node::NodeType Document::nodeType() const @@ -1596,7 +1654,7 @@ Node::NodeType Document::nodeType() const FormController& Document::formController() { if (!m_formController) - m_formController = FormController::create(); + m_formController = std::make_unique<FormController>(); return *m_formController; } @@ -1624,34 +1682,19 @@ Page* Document::page() const return m_frame ? m_frame->page() : nullptr; } -Settings* Document::settings() const -{ - return m_frame ? &m_frame->settings() : nullptr; -} - -PassRefPtr<Range> Document::createRange() +Ref<Range> Document::createRange() { return Range::create(*this); } -PassRefPtr<NodeIterator> Document::createNodeIterator(Node* root, unsigned whatToShow, - PassRefPtr<NodeFilter> filter, bool expandEntityReferences, ExceptionCode& ec) +Ref<NodeIterator> Document::createNodeIterator(Node& root, unsigned long whatToShow, RefPtr<NodeFilter>&& filter, bool) { - if (!root) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - return NodeIterator::create(root, whatToShow, filter, expandEntityReferences); + return NodeIterator::create(root, whatToShow, WTFMove(filter)); } -PassRefPtr<TreeWalker> Document::createTreeWalker(Node* root, unsigned whatToShow, - PassRefPtr<NodeFilter> filter, bool expandEntityReferences, ExceptionCode& ec) +Ref<TreeWalker> Document::createTreeWalker(Node& root, unsigned long whatToShow, RefPtr<NodeFilter>&& filter, bool) { - if (!root) { - ec = NOT_SUPPORTED_ERR; - return nullptr; - } - return TreeWalker::create(root, whatToShow, filter, expandEntityReferences); + return TreeWalker::create(root, whatToShow, WTFMove(filter)); } void Document::scheduleForcedStyleRecalc() @@ -1662,14 +1705,9 @@ void Document::scheduleForcedStyleRecalc() void Document::scheduleStyleRecalc() { - if (shouldDisplaySeamlesslyWithParent()) { - // When we're seamless, our parent document manages our style recalcs. - ownerElement()->setNeedsStyleRecalc(); - ownerElement()->document().scheduleStyleRecalc(); - return; - } + ASSERT(!m_renderView || !m_renderView->inHitTesting()); - if (m_styleRecalcTimer.isActive() || inPageCache()) + if (m_styleRecalcTimer.isActive() || pageCacheState() != NotInPageCache) return; ASSERT(childNeedsStyleRecalc() || m_pendingStyleRecalcShouldForce); @@ -1679,7 +1717,7 @@ void Document::scheduleStyleRecalc() m_styleRecalcTimer.startOneShot(0); - InspectorInstrumentation::didScheduleStyleRecalculation(this); + InspectorInstrumentation::didScheduleStyleRecalculation(*this); } void Document::unscheduleStyleRecalc() @@ -1700,11 +1738,6 @@ bool Document::hasPendingForcedStyleRecalc() const return m_styleRecalcTimer.isActive() && m_pendingStyleRecalcShouldForce; } -void Document::styleRecalcTimerFired(Timer<Document>&) -{ - updateStyleIfNeeded(); -} - void Document::recalcStyle(Style::Change change) { ASSERT(!view() || !view()->isPainting()); @@ -1714,13 +1747,17 @@ void Document::recalcStyle(Style::Change change) return; FrameView& frameView = m_renderView->frameView(); + Ref<FrameView> protect(frameView); if (frameView.isPainting()) return; if (m_inStyleRecalc) return; // Guard against re-entrancy. -dwh + TraceScope tracingScope(StyleRecalcStart, StyleRecalcEnd); + RenderView::RepaintRegionAccumulator repaintRegionAccumulator(renderView()); + AnimationUpdateBlock animationUpdateBlock(&m_frame->animation()); // FIXME: We should update style on our ancestor chain before proceeding (especially for seamless), // however doing so currently causes several tests to crash, as Frame::setDocument calls Document::attach @@ -1729,16 +1766,16 @@ void Document::recalcStyle(Style::Change change) // re-attaching our containing iframe, which when asked HTMLFrameElementBase::isURLAllowed // hits a null-dereference due to security code always assuming the document has a SecurityOrigin. - m_styleSheetCollection.flushPendingUpdates(); + styleScope().flushPendingUpdate(); - InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRecalculateStyle(this); + frameView.willRecalcStyle(); - if (m_elementSheet && m_elementSheet->contents().usesRemUnits()) - m_styleSheetCollection.setUsesRemUnit(true); + InspectorInstrumentationCookie cookie = InspectorInstrumentation::willRecalculateStyle(*this); m_inStyleRecalc = true; + bool updatedCompositingLayers = false; { - PostAttachCallbackDisabler disabler(*this); + Style::PostResolutionCallbackDisabler disabler(*this); WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; if (m_pendingStyleRecalcShouldForce) @@ -1747,23 +1784,38 @@ void Document::recalcStyle(Style::Change change) if (change == Style::Force) { // This may get set again during style resolve. m_hasNodesWithPlaceholderStyle = false; + + auto documentStyle = Style::resolveForDocument(*this); + + // Inserting the pictograph font at the end of the font fallback list is done by the + // font selector, so set a font selector if needed. + if (settings().fontFallbackPrefersPictographs()) + documentStyle.fontCascade().update(&fontSelector()); + + auto documentChange = Style::determineChange(documentStyle, m_renderView->style()); + if (documentChange != Style::NoChange) + renderView()->setStyle(WTFMove(documentStyle)); } - Style::resolveTree(*this, change); + Style::TreeResolver resolver(*this); + auto styleUpdate = resolver.resolve(change); -#if USE(ACCELERATED_COMPOSITING) - frameView.updateCompositingLayersAfterStyleChange(); -#endif + m_lastStyleUpdateSizeForTesting = styleUpdate ? styleUpdate->size() : 0; - clearNeedsStyleRecalc(); + setHasValidStyle(); clearChildNeedsStyleRecalc(); unscheduleStyleRecalc(); m_inStyleRecalc = false; - // Pseudo element removal and similar may only work with these flags still set. Reset them after the style recalc. - if (m_styleResolver) - m_styleSheetCollection.resetCSSFeatureFlags(); + if (styleUpdate) { + SetForScope<bool> inRenderTreeUpdate(m_inRenderTreeUpdate, true); + + RenderTreeUpdater updater(*this); + updater.commit(WTFMove(styleUpdate)); + } + + updatedCompositingLayers = frameView.updateCompositingLayersAfterStyleChange(); } // If we wanted to call implicitClose() during recalcStyle, do so now that we're finished. @@ -1771,17 +1823,39 @@ void Document::recalcStyle(Style::Change change) m_closeAfterStyleRecalc = false; implicitClose(); } + + ++m_styleRecalcCount; InspectorInstrumentation::didRecalculateStyle(cookie); + // Some animated images may now be inside the viewport due to style recalc, + // resume them if necessary if there is no layout pending. Otherwise, we'll + // check if they need to be resumed after layout. + if (updatedCompositingLayers && !frameView.needsLayout()) + frameView.viewportContentsChanged(); + + // Usually this is handled by post-layout. + if (!frameView.needsLayout()) + frameView.frame().selection().scheduleAppearanceUpdateAfterStyleChange(); + // As a result of the style recalculation, the currently hovered element might have been // detached (for example, by setting display:none in the :hover style), schedule another mouseMove event // to check if any other elements ended up under the mouse pointer due to re-layout. if (m_hoveredElement && !m_hoveredElement->renderer()) - frameView.frame().eventHandler().dispatchFakeMouseMoveEventSoon(); + frameView.frame().mainFrame().eventHandler().dispatchFakeMouseMoveEventSoon(); + + if (m_gotoAnchorNeededAfterStylesheetsLoad && !styleScope().hasPendingSheets()) + frameView.scrollToFragment(m_url); - // Style change may reset the focus, e.g. display: none, visibility: hidden. - resetHiddenFocusElementSoon(); + // FIXME: Ideally we would ASSERT(!needsStyleRecalc()) here but we have some cases where it is not true. +} + +bool Document::needsStyleRecalc() const +{ + if (pageCacheState() != NotInPageCache) + return false; + + return m_pendingStyleRecalcShouldForce || childNeedsStyleRecalc() || styleScope().hasPendingUpdate(); } void Document::updateStyleIfNeeded() @@ -1789,17 +1863,15 @@ void Document::updateStyleIfNeeded() ASSERT(isMainThread()); ASSERT(!view() || !view()->isPainting()); - if (!view() || view()->isInLayout()) + if (!view() || view()->isInRenderTreeLayout()) return; - if (m_optimizedStyleSheetUpdateTimer.isActive()) - styleResolverChanged(RecalcStyleIfNeeded); + styleScope().flushPendingUpdate(); - if ((!m_pendingStyleRecalcShouldForce && !childNeedsStyleRecalc()) || inPageCache()) + if (!needsStyleRecalc()) return; - AnimationUpdateBlock animationUpdateBlock(m_frame ? &m_frame->animation() : nullptr); - recalcStyle(Style::NoChange); + recalcStyle(); } void Document::updateLayout() @@ -1807,7 +1879,7 @@ void Document::updateLayout() ASSERT(isMainThread()); FrameView* frameView = view(); - if (frameView && frameView->isInLayout()) { + if (frameView && frameView->isInRenderTreeLayout()) { // View layout should not be re-entrant. ASSERT_NOT_REACHED(); return; @@ -1815,8 +1887,8 @@ void Document::updateLayout() RenderView::RepaintRegionAccumulator repaintRegionAccumulator(renderView()); - if (Element* oe = ownerElement()) - oe->document().updateLayout(); + if (HTMLFrameOwnerElement* owner = ownerElement()) + owner->document().updateLayout(); updateStyleIfNeeded(); @@ -1825,9 +1897,6 @@ void Document::updateLayout() // Only do a layout if changes have occurred that make it necessary. if (frameView && renderView() && (frameView->layoutPending() || renderView()->needsLayout())) frameView->layout(); - - // Active focus element's isFocusable() state may change after Layout. e.g. width: 0px or height: 0px. - resetHiddenFocusElementSoon(); } // FIXME: This is a bad idea and needs to be removed eventually. @@ -1848,10 +1917,11 @@ void Document::updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks // moment. If it were more refined, we might be able to do something better.) // It's worth noting though that this entire method is a hack, since what we really want to do is // suspend JS instead of doing a layout with inaccurate information. - HTMLElement* bodyElement = body(); + HTMLElement* bodyElement = bodyOrFrameset(); if (bodyElement && !bodyElement->renderer() && m_pendingSheetLayout == NoLayoutWithPendingSheets) { m_pendingSheetLayout = DidLayoutWithPendingSheets; - styleResolverChanged(RecalcStyleImmediately); + styleScope().didChangeActiveStyleSheetCandidates(); + recalcStyle(Style::Force); } else if (m_hasNodesWithPlaceholderStyle) // If new nodes have been added or style recalc has been done with style sheets still pending, some nodes // may not have had their real style calculated yet. Normally this gets cleaned when style sheets arrive @@ -1861,33 +1931,141 @@ void Document::updateLayoutIgnorePendingStylesheets(Document::RunPostLayoutTasks updateLayout(); - if (runPostLayoutTasks == RunPostLayoutTasksSynchronously && view()) + if (runPostLayoutTasks == RunPostLayoutTasks::Synchronously && view()) view()->flushAnyPendingPostLayoutTasks(); m_ignorePendingStylesheets = oldIgnore; } -PassRef<RenderStyle> Document::styleForElementIgnoringPendingStylesheets(Element* element) +std::unique_ptr<RenderStyle> Document::styleForElementIgnoringPendingStylesheets(Element& element, const RenderStyle* parentStyle) { - ASSERT_ARG(element, &element->document() == this); + ASSERT(&element.document() == this); // On iOS request delegates called during styleForElement may result in re-entering WebKit and killing the style resolver. - ResourceLoadScheduler::Suspender suspender(*platformStrategies()->loaderStrategy()->resourceLoadScheduler()); + Style::PostResolutionCallbackDisabler disabler(*this); - TemporaryChange<bool> change(m_ignorePendingStylesheets, true); - return ensureStyleResolver().styleForElement(element, element->parentNode() ? element->parentNode()->computedStyle() : nullptr); + SetForScope<bool> change(m_ignorePendingStylesheets, true); + auto elementStyle = element.resolveStyle(parentStyle); + + if (elementStyle.relations) { + Style::Update emptyUpdate(*this); + Style::commitRelations(WTFMove(elementStyle.relations), emptyUpdate); + } + + return WTFMove(elementStyle.renderStyle); +} + +bool Document::updateLayoutIfDimensionsOutOfDate(Element& element, DimensionsCheck dimensionsCheck) +{ + ASSERT(isMainThread()); + + // If the stylesheets haven't loaded, just give up and do a full layout ignoring pending stylesheets. + if (!haveStylesheetsLoaded()) { + updateLayoutIgnorePendingStylesheets(); + return true; + } + + // Check for re-entrancy and assert (same code that is in updateLayout()). + FrameView* frameView = view(); + if (frameView && frameView->isInRenderTreeLayout()) { + // View layout should not be re-entrant. + ASSERT_NOT_REACHED(); + return true; + } + + RenderView::RepaintRegionAccumulator repaintRegionAccumulator(renderView()); + + // Mimic the structure of updateLayout(), but at each step, see if we have been forced into doing a full + // layout. + bool requireFullLayout = false; + if (HTMLFrameOwnerElement* owner = ownerElement()) { + if (owner->document().updateLayoutIfDimensionsOutOfDate(*owner)) + requireFullLayout = true; + } + + updateStyleIfNeeded(); + + RenderObject* renderer = element.renderer(); + if (!renderer || renderer->needsLayout() || element.renderNamedFlowFragment()) { + // If we don't have a renderer or if the renderer needs layout for any reason, give up. + // Named flows can have auto height, so don't try to enforce the optimization in this case. + // The 2-pass nature of auto height named flow layout means the region may not be dirty yet. + requireFullLayout = true; + } + + bool isVertical = renderer && !renderer->isHorizontalWritingMode(); + bool checkingLogicalWidth = ((dimensionsCheck & WidthDimensionsCheck) && !isVertical) || ((dimensionsCheck & HeightDimensionsCheck) && isVertical); + bool checkingLogicalHeight = ((dimensionsCheck & HeightDimensionsCheck) && !isVertical) || ((dimensionsCheck & WidthDimensionsCheck) && isVertical); + bool hasSpecifiedLogicalHeight = renderer && renderer->style().logicalMinHeight() == Length(0, Fixed) && renderer->style().logicalHeight().isFixed() && renderer->style().logicalMaxHeight().isAuto(); + + if (!requireFullLayout) { + RenderBox* previousBox = nullptr; + RenderBox* currentBox = nullptr; + + // Check our containing block chain. If anything in the chain needs a layout, then require a full layout. + for (RenderObject* currRenderer = element.renderer(); currRenderer && !currRenderer->isRenderView(); currRenderer = currRenderer->container()) { + + // Require the entire container chain to be boxes. + if (!is<RenderBox>(currRenderer)) { + requireFullLayout = true; + break; + } + + previousBox = currentBox; + currentBox = downcast<RenderBox>(currRenderer); + + // If a box needs layout for itself or if a box has changed children and sizes its width to + // its content, then require a full layout. + if (currentBox->selfNeedsLayout() || + (checkingLogicalWidth && currRenderer->needsLayout() && currentBox->sizesLogicalWidthToFitContent(MainOrPreferredSize))) { + requireFullLayout = true; + break; + } + + // If a block contains floats and the child's height isn't specified, then + // give up also, since our height could end up being influenced by the floats. + if (checkingLogicalHeight && !hasSpecifiedLogicalHeight && currentBox->isRenderBlockFlow()) { + RenderBlockFlow* currentBlockFlow = downcast<RenderBlockFlow>(currentBox); + if (currentBlockFlow->containsFloats() && previousBox && !previousBox->isFloatingOrOutOfFlowPositioned()) { + requireFullLayout = true; + break; + } + } + + if (!currentBox->isRenderBlockFlow() || currentBox->flowThreadContainingBlock() || currentBox->isWritingModeRoot()) { + // FIXME: For now require only block flows all the way back to the root. This limits the optimization + // for now, and we'll expand it in future patches to apply to more and more scenarios. + // Disallow regions/columns from having the optimization. + // Give up if the writing mode changes at all in the containing block chain. + requireFullLayout = true; + break; + } + + if (currRenderer == frameView->layoutRoot()) + break; + } + } + + StackStats::LayoutCheckPoint layoutCheckPoint; + + // Only do a layout if changes have occurred that make it necessary. + if (requireFullLayout && frameView && renderView() && (frameView->layoutPending() || renderView()->needsLayout())) + frameView->layout(); + + return requireFullLayout; } bool Document::isPageBoxVisible(int pageIndex) { - Ref<RenderStyle> pageStyle(ensureStyleResolver().styleForPage(pageIndex)); + updateStyleIfNeeded(); + std::unique_ptr<RenderStyle> pageStyle(styleScope().resolver().styleForPage(pageIndex)); return pageStyle->visibility() != HIDDEN; // display property doesn't apply to @page. } void Document::pageSizeAndMarginsInPixels(int pageIndex, IntSize& pageSize, int& marginTop, int& marginRight, int& marginBottom, int& marginLeft) { - RefPtr<RenderStyle> style = ensureStyleResolver().styleForPage(pageIndex); - RenderView* view = renderView(); + updateStyleIfNeeded(); + auto style = styleScope().resolver().styleForPage(pageIndex); int width = pageSize.width(); int height = pageSize.height(); @@ -1903,11 +2081,11 @@ void Document::pageSizeAndMarginsInPixels(int pageIndex, IntSize& pageSize, int& std::swap(width, height); break; case PAGE_SIZE_RESOLVED: { - LengthSize size = style->pageSize(); - ASSERT(size.width().isFixed()); - ASSERT(size.height().isFixed()); - width = valueForLength(size.width(), 0, view); - height = valueForLength(size.height(), 0, view); + auto& size = style->pageSize(); + ASSERT(size.width.isFixed()); + ASSERT(size.height.isFixed()); + width = valueForLength(size.width, 0); + height = valueForLength(size.height, 0); break; } default: @@ -1917,39 +2095,39 @@ void Document::pageSizeAndMarginsInPixels(int pageIndex, IntSize& pageSize, int& // The percentage is calculated with respect to the width even for margin top and bottom. // http://www.w3.org/TR/CSS2/box.html#margin-properties - marginTop = style->marginTop().isAuto() ? marginTop : intValueForLength(style->marginTop(), width, view); - marginRight = style->marginRight().isAuto() ? marginRight : intValueForLength(style->marginRight(), width, view); - marginBottom = style->marginBottom().isAuto() ? marginBottom : intValueForLength(style->marginBottom(), width, view); - marginLeft = style->marginLeft().isAuto() ? marginLeft : intValueForLength(style->marginLeft(), width, view); + marginTop = style->marginTop().isAuto() ? marginTop : intValueForLength(style->marginTop(), width); + marginRight = style->marginRight().isAuto() ? marginRight : intValueForLength(style->marginRight(), width); + marginBottom = style->marginBottom().isAuto() ? marginBottom : intValueForLength(style->marginBottom(), width); + marginLeft = style->marginLeft().isAuto() ? marginLeft : intValueForLength(style->marginLeft(), width); } -void Document::setIsViewSource(bool isViewSource) +StyleResolver& Document::userAgentShadowTreeStyleResolver() { - m_isViewSource = isViewSource; - if (!m_isViewSource) - return; - - setSecurityOrigin(SecurityOrigin::createUnique()); + if (!m_userAgentShadowTreeStyleResolver) + m_userAgentShadowTreeStyleResolver = std::make_unique<StyleResolver>(*this); + return *m_userAgentShadowTreeStyleResolver; } -void Document::createStyleResolver() +void Document::fontsNeedUpdate(FontSelector&) { - bool matchAuthorAndUserStyles = true; - if (Settings* settings = this->settings()) - matchAuthorAndUserStyles = settings->authorAndUserStylesEnabled(); - m_styleResolver = adoptPtr(new StyleResolver(*this, matchAuthorAndUserStyles)); - m_styleSheetCollection.combineCSSFeatureFlags(); + if (auto* resolver = styleScope().resolverIfExists()) + resolver->invalidateMatchedPropertiesCache(); + if (pageCacheState() != NotInPageCache || !renderView()) + return; + scheduleForcedStyleRecalc(); } -void Document::clearStyleResolver() +void Document::didClearStyleResolver() { - m_styleResolver.clear(); + m_userAgentShadowTreeStyleResolver = nullptr; + + m_fontSelector->buildStarted(); } void Document::createRenderTree() { ASSERT(!renderView()); - ASSERT(!m_inPageCache); + ASSERT(m_pageCacheState != InPageCache); ASSERT(!m_axObjectCache || this != &topDocument()); if (m_isNonRenderedPlaceholder) @@ -1959,23 +2137,11 @@ void Document::createRenderTree() m_renderView = createRenderer<RenderView>(*this, RenderStyle::create()); Node::setRenderer(m_renderView.get()); -#if USE(ACCELERATED_COMPOSITING) renderView()->setIsInWindow(true); -#endif recalcStyle(Style::Force); } -static void pageWheelEventHandlerCountChanged(Page& page) -{ - unsigned count = 0; - for (const Frame* frame = &page.mainFrame(); frame; frame = frame->tree().traverseNext()) { - if (Document* document = frame->document()) - count += document->wheelEventHandlerCount(); - } - page.chrome().client().numWheelEventHandlersChanged(count); -} - void Document::didBecomeCurrentDocumentInFrame() { // FIXME: Are there cases where the document can be dislodged from the frame during the event handling below? @@ -1996,52 +2162,71 @@ void Document::didBecomeCurrentDocumentInFrame() // subframes' documents have no wheel event handlers, then the count did not change, // unless the documents they are replacing had wheel event handlers. if (page() && m_frame->isMainFrame()) - pageWheelEventHandlerCountChanged(*page()); - -#if ENABLE(TOUCH_EVENTS) - // FIXME: Doing this only for the main frame is insufficient. - // A subframe could have touch event handlers. - if (hasTouchEventHandlers() && page() && m_frame->isMainFrame()) - page()->chrome().client().needTouchEvents(true); -#endif - -#if PLATFORM(IOS) - // Ensure that document scheduled task state matches frame timer state. It can be out of sync - // if timers state changed while the document was not in the frame (possibly in page cache, - // or simply newly created). - // FIXME: How does this interact with cross-platform code below? - if (m_frame->timersPaused()) - suspendScheduledTasks(ActiveDOMObject::DocumentWillBePaused); - else - resumeScheduledTasks(ActiveDOMObject::DocumentWillBePaused); -#endif + wheelEventHandlersChanged(); + // Ensure that the scheduled task state of the document matches the DOM suspension state of the frame. It can + // be out of sync if the DOM suspension state changed while the document was not in the frame (possibly in the + // page cache, or simply newly created). if (m_frame->activeDOMObjectsAndAnimationsSuspended()) { - suspendScriptedAnimationControllerCallbacks(); m_frame->animation().suspendAnimationsForDocument(this); - suspendActiveDOMObjects(ActiveDOMObject::PageWillBeSuspended); + suspendScheduledTasks(ActiveDOMObject::PageWillBeSuspended); + } else { + resumeScheduledTasks(ActiveDOMObject::PageWillBeSuspended); + m_frame->animation().resumeAnimationsForDocument(this); } } -void Document::disconnectFromFrame() +void Document::frameDestroyed() { - m_frame = nullptr; + // detachFromFrame() must be called before destroying the Frame. + ASSERT_WITH_SECURITY_IMPLICATION(!m_frame); + FrameDestructionObserver::frameDestroyed(); +} + +void Document::didBecomeCurrentDocumentInView() +{ + ASSERT(view()); + if (!hasLivingRenderTree()) + createRenderTree(); +} + +void Document::attachToCachedFrame(CachedFrameBase& cachedFrame) +{ + ASSERT_WITH_SECURITY_IMPLICATION(cachedFrame.document() == this); + ASSERT(cachedFrame.view()); + ASSERT(m_pageCacheState == Document::InPageCache); + observeFrame(&cachedFrame.view()->frame()); +} + +void Document::detachFromCachedFrame(CachedFrameBase& cachedFrame) +{ + ASSERT_UNUSED(cachedFrame, cachedFrame.view()); + ASSERT_WITH_SECURITY_IMPLICATION(cachedFrame.document() == this); + ASSERT(m_frame == &cachedFrame.view()->frame()); + ASSERT(m_pageCacheState == Document::InPageCache); + detachFromFrame(); } void Document::destroyRenderTree() { ASSERT(hasLivingRenderTree()); - ASSERT(!m_inPageCache); + ASSERT(frame()); + ASSERT(page()); + + FrameView* frameView = frame()->document() == this ? frame()->view() : nullptr; + + // Prevent Widget tree changes from committing until the RenderView is dead and gone. + WidgetHierarchyUpdatesSuspensionScope suspendWidgetHierarchyUpdates; - TemporaryChange<bool> change(m_renderTreeBeingDestroyed, true); + SetForScope<bool> change(m_renderTreeBeingDestroyed, true); if (this == &topDocument()) clearAXObjectCache(); documentWillBecomeInactive(); - if (FrameView* frameView = view()) - frameView->detachCustomScrollbars(); + if (frameView) + frameView->willDestroyRenderTree(); #if ENABLE(FULLSCREEN_API) if (m_fullScreenRenderer) @@ -2051,9 +2236,10 @@ void Document::destroyRenderTree() m_hoveredElement = nullptr; m_focusedElement = nullptr; m_activeElement = nullptr; + m_focusNavigationStartingNode = nullptr; if (m_documentElement) - Style::detachRenderTree(*m_documentElement); + RenderTreeUpdater::tearDownRenderers(*m_documentElement); clearChildNeedsStyleRecalc(); @@ -2062,32 +2248,61 @@ void Document::destroyRenderTree() m_renderView = nullptr; Node::setRenderer(nullptr); -#if ENABLE(IOS_TEXT_AUTOSIZING) +#if ENABLE(TEXT_AUTOSIZING) // Do this before the arena is cleared, which is needed to deref the RenderStyle on TextAutoSizingKey. m_textAutoSizedNodes.clear(); #endif + + if (frameView) + frameView->didDestroyRenderTree(); } void Document::prepareForDestruction() { -#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS) - clearTouchEventListeners(); + if (m_hasPreparedForDestruction) + return; + + if (m_frame) + m_frame->animation().detachFromDocument(this); + +#if ENABLE(IOS_TOUCH_EVENTS) + clearTouchEventHandlersAndListeners(); +#endif + +#if HAVE(ACCESSIBILITY) + // Sub-frames need to cleanup Nodes in the text marker cache when the Document disappears. + if (this != &topDocument()) { + if (AXObjectCache* cache = existingAXObjectCache()) + cache->clearTextMarkerNodesInUse(this); + } #endif - disconnectDescendantFrames(); + { + NavigationDisabler navigationDisabler; + disconnectDescendantFrames(); + } + if (m_domWindow && m_frame) m_domWindow->willDetachDocumentFromFrame(); - destroyRenderTree(); + if (hasLivingRenderTree()) + destroyRenderTree(); - if (isPluginDocument()) - toPluginDocument(this)->detachFromPluginElement(); + if (is<PluginDocument>(*this)) + downcast<PluginDocument>(*this).detachFromPluginElement(); #if ENABLE(POINTER_LOCK) if (page()) - page()->pointerLockController()->documentDetached(this); + page()->pointerLockController().documentDetached(*this); #endif + if (auto* page = this->page()) { + if (auto* validationMessageClient = page->validationMessageClient()) + validationMessageClient->documentDetached(*this); + } + + InspectorInstrumentation::documentDetached(*this); + stopActiveDOMObjects(); m_eventQueue.close(); #if ENABLE(FULLSCREEN_API) @@ -2097,19 +2312,34 @@ void Document::prepareForDestruction() commonTeardown(); -#if ENABLE(SHARED_WORKERS) - SharedWorkerRepository::documentDetached(this); -#endif - #if ENABLE(TOUCH_EVENTS) if (m_touchEventTargets && m_touchEventTargets->size() && parentDocument()) - parentDocument()->didRemoveEventTargetNode(this); + parentDocument()->didRemoveEventTargetNode(*this); #endif + if (m_wheelEventTargets && m_wheelEventTargets->size() && parentDocument()) + parentDocument()->didRemoveEventTargetNode(*this); + if (m_mediaQueryMatcher) m_mediaQueryMatcher->documentDestroyed(); - disconnectFromFrame(); +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + if (!m_clientToIDMap.isEmpty() && page()) { + Vector<WebCore::MediaPlaybackTargetClient*> clients; + copyKeysToVector(m_clientToIDMap, clients); + for (auto* client : clients) + removePlaybackTargetPickerClient(*client); + } +#endif + + detachFromFrame(); + + m_hasPreparedForDestruction = true; + + // Note that m_pageCacheState can be Document::AboutToEnterPageCache if our frame + // was removed in an onpagehide event handler fired when the top-level frame is + // about to enter the page cache. + ASSERT_WITH_SECURITY_IMPLICATION(m_pageCacheState != Document::InPageCache); } void Document::removeAllEventListeners() @@ -2118,27 +2348,63 @@ void Document::removeAllEventListeners() if (m_domWindow) m_domWindow->removeAllEventListeners(); -#if ENABLE(TOUCH_EVENTS) && PLATFORM(IOS) - clearTouchEventListeners(); +#if ENABLE(IOS_TOUCH_EVENTS) + clearTouchEventHandlersAndListeners(); #endif - for (Node* node = firstChild(); node; node = NodeTraversal::next(node)) + for (Node* node = firstChild(); node; node = NodeTraversal::next(*node)) node->removeAllEventListeners(); + +#if ENABLE(TOUCH_EVENTS) + m_touchEventTargets = nullptr; +#endif + m_wheelEventTargets = nullptr; } -void Document::platformSuspendOrStopActiveDOMObjects() +void Document::suspendDeviceMotionAndOrientationUpdates() { -#if PLATFORM(IOS) -#if ENABLE(DEVICE_ORIENTATION) + if (m_areDeviceMotionAndOrientationUpdatesSuspended) + return; + m_areDeviceMotionAndOrientationUpdatesSuspended = true; +#if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS) if (m_deviceMotionController) m_deviceMotionController->suspendUpdates(); if (m_deviceOrientationController) m_deviceOrientationController->suspendUpdates(); #endif +} + +void Document::resumeDeviceMotionAndOrientationUpdates() +{ + if (!m_areDeviceMotionAndOrientationUpdatesSuspended) + return; + m_areDeviceMotionAndOrientationUpdatesSuspended = false; +#if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS) + if (m_deviceMotionController) + m_deviceMotionController->resumeUpdates(); + if (m_deviceOrientationController) + m_deviceOrientationController->resumeUpdates(); +#endif +} + +bool Document::shouldBypassMainWorldContentSecurityPolicy() const +{ + JSC::CallFrame* callFrame = commonVM().topCallFrame; + if (callFrame == JSC::CallFrame::noCaller()) + return false; + DOMWrapperWorld& domWrapperWorld = currentWorld(callFrame); + if (domWrapperWorld.isNormal()) + return false; + return true; +} +void Document::platformSuspendOrStopActiveDOMObjects() +{ +#if PLATFORM(IOS) if (WebThreadCountOfObservedContentModifiers() > 0) { - Frame* frame = this->frame(); - if (Page* page = frame ? frame->page() : nullptr) - page->chrome().client().clearContentChangeObservers(frame); + if (auto* frame = this->frame()) { + if (auto* page = frame->page()) + page->chrome().client().clearContentChangeObservers(*frame); + } } #endif } @@ -2146,19 +2412,14 @@ void Document::platformSuspendOrStopActiveDOMObjects() void Document::suspendActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) { ScriptExecutionContext::suspendActiveDOMObjects(why); + suspendDeviceMotionAndOrientationUpdates(); platformSuspendOrStopActiveDOMObjects(); } void Document::resumeActiveDOMObjects(ActiveDOMObject::ReasonForSuspension why) { ScriptExecutionContext::resumeActiveDOMObjects(why); - -#if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS) - if (m_deviceMotionController) - m_deviceMotionController->resumeUpdates(); - if (m_deviceOrientationController) - m_deviceOrientationController->resumeUpdates(); -#endif + resumeDeviceMotionAndOrientationUpdates(); // FIXME: For iOS, do we need to add content change observers that were removed in Document::suspendActiveDOMObjects()? } @@ -2173,11 +2434,12 @@ void Document::clearAXObjectCache() ASSERT(&topDocument() == this); // Clear the cache member variable before calling delete because attempts // are made to access it during destruction. - m_axObjectCache.clear(); + m_axObjectCache = nullptr; } -AXObjectCache* Document::existingAXObjectCache() const +AXObjectCache* Document::existingAXObjectCacheSlow() const { + ASSERT(hasEverCreatedAnAXObjectCache); Document& topDocument = this->topDocument(); if (!topDocument.hasLivingRenderTree()) return nullptr; @@ -2200,8 +2462,10 @@ AXObjectCache* Document::axObjectCache() const return nullptr; ASSERT(&topDocument == this || !m_axObjectCache); - if (!topDocument.m_axObjectCache) - topDocument.m_axObjectCache = adoptPtr(new AXObjectCache(topDocument)); + if (!topDocument.m_axObjectCache) { + topDocument.m_axObjectCache = std::make_unique<AXObjectCache>(topDocument); + hasEverCreatedAnAXObjectCache = true; + } return topDocument.m_axObjectCache.get(); } @@ -2209,10 +2473,10 @@ void Document::setVisuallyOrdered() { m_visuallyOrdered = true; if (renderView()) - renderView()->style().setRTLOrdering(VisualOrder); + renderView()->mutableStyle().setRTLOrdering(VisualOrder); } -PassRefPtr<DocumentParser> Document::createParser() +Ref<DocumentParser> Document::createParser() { // FIXME: this should probably pass the frame instead return XMLDocumentParser::create(*this, view()); @@ -2225,10 +2489,13 @@ ScriptableDocumentParser* Document::scriptableDocumentParser() const void Document::open(Document* ownerDocument) { + if (m_ignoreOpensDuringUnloadCount) + return; + if (ownerDocument) { setURL(ownerDocument->url()); - m_cookieURL = ownerDocument->cookieURL(); - setSecurityOrigin(ownerDocument->securityOrigin()); + setCookieURL(ownerDocument->cookieURL()); + setSecurityOriginPolicy(ownerDocument->securityOriginPolicy()); } if (m_frame) { @@ -2261,7 +2528,7 @@ void Document::detachParser() if (!m_parser) return; m_parser->detach(); - m_parser.clear(); + m_parser = nullptr; } void Document::cancelParsing() @@ -2279,57 +2546,61 @@ void Document::cancelParsing() void Document::implicitOpen() { - cancelParsing(); - removeChildren(); - setCompatibilityMode(NoQuirksMode); - - // Documents rendered seamlessly should start out requiring a stylesheet - // collection update in order to ensure they inherit all the relevant data - // from their parent. - if (shouldDisplaySeamlesslyWithParent()) - styleResolverChanged(DeferRecalcStyle); + setCompatibilityMode(DocumentCompatibilityMode::NoQuirksMode); + cancelParsing(); m_parser = createParser(); setParsing(true); setReadyState(Loading); } -HTMLElement* Document::body() const +HTMLBodyElement* Document::body() const { - // If the document element contains both a frameset and a body, the frameset wins. - auto element = documentElement(); + auto* element = documentElement(); if (!element) return nullptr; - if (auto frameset = childrenOfType<HTMLFrameSetElement>(*element).first()) - return frameset; return childrenOfType<HTMLBodyElement>(*element).first(); } -void Document::setBody(PassRefPtr<HTMLElement> prpNewBody, ExceptionCode& ec) +HTMLElement* Document::bodyOrFrameset() const { - RefPtr<HTMLElement> newBody = prpNewBody; - - if (!newBody || !documentElement() || !newBody->hasTagName(bodyTag)) { - ec = HIERARCHY_REQUEST_ERR; - return; + // Return the first body or frameset child of the html element. + auto* element = documentElement(); + if (!is<HTMLHtmlElement>(element)) + return nullptr; + for (auto& child : childrenOfType<HTMLElement>(*element)) { + if (is<HTMLBodyElement>(child) || is<HTMLFrameSetElement>(child)) + return &child; } + return nullptr; +} - if (&newBody->document() != this) { - ec = 0; - RefPtr<Node> node = importNode(newBody.get(), true, ec); - if (ec) - return; - - newBody = toHTMLElement(node.get()); - } +ExceptionOr<void> Document::setBodyOrFrameset(RefPtr<HTMLElement>&& newBody) +{ + if (!is<HTMLBodyElement>(newBody.get()) && !is<HTMLFrameSetElement>(newBody.get())) + return Exception { HIERARCHY_REQUEST_ERR }; - HTMLElement* b = body(); - if (!b) - documentElement()->appendChild(newBody.release(), ec); - else - documentElement()->replaceChild(newBody.release(), b, ec); + auto* currentBody = bodyOrFrameset(); + if (newBody == currentBody) + return { }; + + if (!m_documentElement) + return Exception { HIERARCHY_REQUEST_ERR }; + + if (currentBody) + return m_documentElement->replaceChild(*newBody, *currentBody); + return m_documentElement->appendChild(*newBody); +} + +Location* Document::location() const +{ + auto* window = domWindow(); + if (!window) + return nullptr; + + return window->location(); } HTMLHeadElement* Document::head() @@ -2363,7 +2634,7 @@ void Document::explicitClose() return; } - m_frame->loader().checkCompleted(); + checkCompleted(); } void Document::implicitClose() @@ -2381,7 +2652,7 @@ void Document::implicitClose() return; // Call to dispatchWindowLoadEvent can blow us from underneath. - Ref<Document> protect(*this); + Ref<Document> protectedThis(*this); m_processingLoadEvent = true; @@ -2400,35 +2671,37 @@ void Document::implicitClose() // ramifications, and we need to decide what is the Right Thing To Do(tm) Frame* f = frame(); if (f) { - f->loader().icon().startLoader(); - f->animation().startAnimationsIfNotSuspended(this); - } + if (f->loader().client().useIconLoadingClient()) { + if (auto* documentLoader = loader()) + documentLoader->startIconLoading(); + } else + f->loader().icon().startLoader(); - ImageLoader::dispatchPendingBeforeLoadEvents(); - ImageLoader::dispatchPendingLoadEvents(); - ImageLoader::dispatchPendingErrorEvents(); + f->animation().startAnimationsIfNotSuspended(this); - HTMLLinkElement::dispatchPendingLoadEvents(); - HTMLStyleElement::dispatchPendingLoadEvents(); + // FIXME: We shouldn't be dispatching pending events globally on all Documents here. + // For now, only do this when there is a Frame, otherwise this could cause JS reentrancy + // below SVG font parsing, for example. <https://webkit.org/b/136269> + ImageLoader::dispatchPendingBeforeLoadEvents(); + ImageLoader::dispatchPendingLoadEvents(); + ImageLoader::dispatchPendingErrorEvents(); + HTMLLinkElement::dispatchPendingLoadEvents(); + HTMLStyleElement::dispatchPendingLoadEvents(); -#if ENABLE(SVG) - // To align the HTML load event and the SVGLoad event for the outermost <svg> element, fire it from - // here, instead of doing it from SVGElement::finishedParsingChildren (if externalResourcesRequired="false", - // which is the default, for ='true' its fired at a later time, once all external resources finished loading). - if (svgExtensions()) - accessSVGExtensions()->dispatchSVGLoadEventToOutermostSVGElements(); -#endif + // To align the HTML load event and the SVGLoad event for the outermost <svg> element, fire it from + // here, instead of doing it from SVGElement::finishedParsingChildren (if externalResourcesRequired="false", + // which is the default, for ='true' its fired at a later time, once all external resources finished loading). + if (svgExtensions()) + accessSVGExtensions().dispatchSVGLoadEventToOutermostSVGElements(); + } dispatchWindowLoadEvent(); - enqueuePageshowEvent(PageshowEventNotPersisted); - enqueuePopstateEvent(m_pendingStateObject ? m_pendingStateObject.release() : SerializedScriptValue::nullValue()); - + dispatchPageshowEvent(PageshowEventNotPersisted); + if (m_pendingStateObject) + dispatchPopstateEvent(WTFMove(m_pendingStateObject)); + if (f) - f->loader().handledOnloadEvents(); -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement()) - printf("onload fired at %lld\n", elapsedTime().count()); -#endif + f->loader().dispatchOnloadEvents(); // An event handler may have removed the frame if (!frame()) { @@ -2440,7 +2713,7 @@ void Document::implicitClose() // fires. This will improve onload scores, and other browsers do it. // If they wanna cheat, we can too. -dwh - if (frame()->navigationScheduler().locationChangePending() && elapsedTime() < settings()->layoutInterval()) { + if (frame()->navigationScheduler().locationChangePending() && timeSinceDocumentCreation() < settings().layoutInterval()) { // Just bail out. Before or during the onload we were shifted to another page. // The old i-Bench suite does this. When this happens don't bother painting or laying out. m_processingLoadEvent = false; @@ -2464,15 +2737,19 @@ void Document::implicitClose() m_processingLoadEvent = false; -#if PLATFORM(MAC) || PLATFORM(WIN) || PLATFORM(GTK) || PLATFORM(EFL) +#if PLATFORM(COCOA) || PLATFORM(WIN) || PLATFORM(GTK) if (f && hasLivingRenderTree() && AXObjectCache::accessibilityEnabled()) { // The AX cache may have been cleared at this point, but we need to make sure it contains an // AX object to send the notification to. getOrCreate will make sure that an valid AX object // exists in the cache (we ignore the return value because we don't need it here). This is - // only safe to call when a layout is not in progress, so it can not be used in postNotification. + // only safe to call when a layout is not in progress, so it can not be used in postNotification. + // + // This notification is now called AXNewDocumentLoadComplete because there are other handlers that will + // catch new AND page history loads, and that uses AXLoadComplete + axObjectCache()->getOrCreate(renderView()); if (this == &topDocument()) - axObjectCache()->postNotification(renderView(), AXObjectCache::AXLoadComplete); + axObjectCache()->postNotification(renderView(), AXObjectCache::AXNewDocumentLoadComplete); else { // AXLoadComplete can only be posted on the top document, so if it's a document // in an iframe that just finished loading, post AXLayoutComplete instead. @@ -2481,10 +2758,8 @@ void Document::implicitClose() } #endif -#if ENABLE(SVG) if (svgExtensions()) - accessSVGExtensions()->startAnimations(); -#endif + accessSVGExtensions().startAnimations(); } void Document::setParsing(bool b) @@ -2492,15 +2767,10 @@ void Document::setParsing(bool b) m_bParsing = b; if (m_bParsing && !m_sharedObjectPool) - m_sharedObjectPool = DocumentSharedObjectPool::create(); - - if (!m_bParsing && view()) - view()->scheduleRelayout(); + m_sharedObjectPool = std::make_unique<DocumentSharedObjectPool>(); -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement() && !m_bParsing) - printf("Parsing finished at %lld\n", elapsedTime().count()); -#endif + if (!m_bParsing && view() && !view()->needsLayout()) + view()->fireLayoutRelatedMilestonesIfNeeded(); } bool Document::shouldScheduleLayout() @@ -2511,35 +2781,33 @@ bool Document::shouldScheduleLayout() // (a) Only schedule a layout once the stylesheets are loaded. // (b) Only schedule layout once we have a body element. - return (haveStylesheetsLoaded() && body()) - || (documentElement() && !documentElement()->hasTagName(htmlTag)); + return (haveStylesheetsLoaded() && bodyOrFrameset()) + || (documentElement() && !is<HTMLHtmlElement>(*documentElement())); } bool Document::isLayoutTimerActive() { - return view() && view()->layoutPending() && !minimumLayoutDelay().count(); + return view() && view()->layoutPending() && !minimumLayoutDelay(); } -std::chrono::milliseconds Document::minimumLayoutDelay() +Seconds Document::minimumLayoutDelay() { if (m_overMinimumLayoutThreshold) - return std::chrono::milliseconds(0); + return 0_s; - std::chrono::milliseconds elapsed = elapsedTime(); - m_overMinimumLayoutThreshold = elapsed > settings()->layoutInterval(); + auto elapsed = timeSinceDocumentCreation(); + m_overMinimumLayoutThreshold = elapsed > settings().layoutInterval(); // We'll want to schedule the timer to fire at the minimum layout threshold. - return std::max(std::chrono::milliseconds(0), settings()->layoutInterval() - elapsed); + return std::max(0_s, settings().layoutInterval() - elapsed); } -std::chrono::milliseconds Document::elapsedTime() const +Seconds Document::timeSinceDocumentCreation() const { - auto elapsedTime = std::chrono::steady_clock::now() - m_startTime; - - return std::chrono::duration_cast<std::chrono::milliseconds>(elapsedTime); + return MonotonicTime::now() - m_documentCreationTime; } -void Document::write(const SegmentedString& text, Document* ownerDocument) +void Document::write(SegmentedString&& text, Document* ownerDocument) { NestingLevelIncrementer nestingLevelIncrementer(m_writeRecursionDepth); @@ -2547,54 +2815,60 @@ void Document::write(const SegmentedString& text, Document* ownerDocument) m_writeRecursionIsTooDeep = (m_writeRecursionDepth > cMaxWriteRecursionDepth) || m_writeRecursionIsTooDeep; if (m_writeRecursionIsTooDeep) - return; - -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement()) - printf("Beginning a document.write at %lld\n", elapsedTime().count()); -#endif + return; bool hasInsertionPoint = m_parser && m_parser->hasInsertionPoint(); - if (!hasInsertionPoint && m_ignoreDestructiveWriteCount) + if (!hasInsertionPoint && (m_ignoreOpensDuringUnloadCount || m_ignoreDestructiveWriteCount)) return; if (!hasInsertionPoint) open(ownerDocument); ASSERT(m_parser); - m_parser->insert(text); - -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement()) - printf("Ending a document.write at %lld\n", elapsedTime().count()); -#endif + m_parser->insert(WTFMove(text)); } void Document::write(const String& text, Document* ownerDocument) { - write(SegmentedString(text), ownerDocument); + write(SegmentedString { text }, ownerDocument); } void Document::writeln(const String& text, Document* ownerDocument) { - write(text, ownerDocument); - write("\n", ownerDocument); + SegmentedString textWithNewline { text }; + textWithNewline.append(String { "\n" }); + write(WTFMove(textWithNewline), ownerDocument); } -double Document::minimumTimerInterval() const +std::chrono::milliseconds Document::minimumTimerInterval() const { - Page* p = page(); - if (!p) + auto* page = this->page(); + if (!page) return ScriptExecutionContext::minimumTimerInterval(); - return p->settings().minDOMTimerInterval(); + return page->settings().minimumDOMTimerInterval(); } -double Document::timerAlignmentInterval() const +void Document::setTimerThrottlingEnabled(bool shouldThrottle) { - Page* p = page(); - if (!p) - return ScriptExecutionContext::timerAlignmentInterval(); - return p->settings().domTimerAlignmentInterval(); + if (m_isTimerThrottlingEnabled == shouldThrottle) + return; + + m_isTimerThrottlingEnabled = shouldThrottle; + didChangeTimerAlignmentInterval(); +} + +std::chrono::milliseconds Document::timerAlignmentInterval(bool hasReachedMaxNestingLevel) const +{ + auto alignmentInterval = ScriptExecutionContext::timerAlignmentInterval(hasReachedMaxNestingLevel); + + // Apply Document-level DOMTimer throttling only if timers have reached their maximum nesting level as the Page may still be visible. + if (m_isTimerThrottlingEnabled && hasReachedMaxNestingLevel) + alignmentInterval = std::max(alignmentInterval, DOMTimer::hiddenPageAlignmentInterval()); + + if (Page* page = this->page()) + alignmentInterval = std::max(alignmentInterval, page->domTimerAlignmentInterval()); + + return alignmentInterval; } EventTarget* Document::errorEventTarget() @@ -2602,9 +2876,9 @@ EventTarget* Document::errorEventTarget() return m_domWindow.get(); } -void Document::logExceptionToConsole(const String& errorMessage, const String& sourceURL, int lineNumber, int columnNumber, PassRefPtr<ScriptCallStack> callStack) +void Document::logExceptionToConsole(const String& errorMessage, const String& sourceURL, int lineNumber, int columnNumber, RefPtr<Inspector::ScriptCallStack>&& callStack) { - addMessage(JSMessageSource, ErrorMessageLevel, errorMessage, sourceURL, lineNumber, columnNumber, callStack); + addMessage(MessageSource::JS, MessageLevel::Error, errorMessage, sourceURL, lineNumber, columnNumber, WTFMove(callStack)); } void Document::setURL(const URL& url) @@ -2635,21 +2909,11 @@ void Document::updateBaseURL() m_baseURL = URL(ParsedURLString, documentURI()); } - if (m_selectorQueryCache) - m_selectorQueryCache->invalidate(); + clearSelectorQueryCache(); if (!m_baseURL.isValid()) m_baseURL = URL(); - if (m_elementSheet) { - // Element sheet is silly. It never contains anything. - ASSERT(!m_elementSheet->contents().ruleCount()); - bool usesRemUnits = m_elementSheet->contents().usesRemUnits(); - m_elementSheet = CSSStyleSheet::createInline(*this, m_baseURL); - // FIXME: So we are not really the parser. The right fix is to eliminate the element sheet completely. - m_elementSheet->contents().parserSetUsesRemUnits(usesRemUnits); - } - if (!equalIgnoringFragmentIdentifier(oldBaseURL, m_baseURL)) { // Base URL change changes any relative visited links. // FIXME: There are other URLs in the tree that would need to be re-evaluated on dynamic base URL change. Style should be invalidated too. @@ -2670,16 +2934,22 @@ void Document::processBaseElement() const AtomicString* href = nullptr; const AtomicString* target = nullptr; auto baseDescendants = descendantsOfType<HTMLBaseElement>(*this); - for (auto base = baseDescendants.begin(), end = baseDescendants.end(); base != end && (!href || !target); ++base) { + for (auto& base : baseDescendants) { if (!href) { - const AtomicString& value = base->fastGetAttribute(hrefAttr); - if (!value.isNull()) + const AtomicString& value = base.attributeWithoutSynchronization(hrefAttr); + if (!value.isNull()) { href = &value; + if (target) + break; + } } if (!target) { - const AtomicString& value = base->fastGetAttribute(targetAttr); - if (!value.isNull()) + const AtomicString& value = base.attributeWithoutSynchronization(targetAttr); + if (!value.isNull()) { target = &value; + if (href) + break; + } } } @@ -2711,6 +2981,30 @@ void Document::disableEval(const String& errorMessage) frame()->script().disableEval(errorMessage); } +#if ENABLE(INDEXED_DATABASE) + +IDBClient::IDBConnectionProxy* Document::idbConnectionProxy() +{ + if (!m_idbConnectionProxy) { + Page* currentPage = page(); + if (!currentPage) + return nullptr; + m_idbConnectionProxy = ¤tPage->idbConnection().proxy(); + } + return m_idbConnectionProxy.get(); +} + +#endif + +#if ENABLE(WEB_SOCKETS) + +SocketProvider* Document::socketProvider() +{ + return m_socketProvider.get(); +} + +#endif + bool Document::canNavigate(Frame* targetFrame) { if (!m_frame) @@ -2780,7 +3074,7 @@ Frame* Document::findUnsafeParentScrollPropagationBoundary() Frame* ancestorFrame = currentFrame->tree().parent(); while (ancestorFrame) { - if (!ancestorFrame->document()->securityOrigin()->canAccess(securityOrigin())) + if (!ancestorFrame->document()->securityOrigin().canAccess(securityOrigin())) return currentFrame; currentFrame = ancestorFrame; ancestorFrame = ancestorFrame->tree().parent(); @@ -2788,159 +3082,141 @@ Frame* Document::findUnsafeParentScrollPropagationBoundary() return nullptr; } - -void Document::seamlessParentUpdatedStylesheets() -{ - styleResolverChanged(RecalcStyleImmediately); -} - void Document::didRemoveAllPendingStylesheet() { - m_needsNotifyRemoveAllPendingStylesheet = false; - - styleResolverChanged(DeferRecalcStyleIfNeeded); - if (m_pendingSheetLayout == DidLayoutWithPendingSheets) { + // Painting is disabled when doing layouts with pending sheets to avoid FOUC. + // We need to force paint when coming out from this state. + // FIXME: This is not very elegant. m_pendingSheetLayout = IgnoreLayoutWithPendingSheets; if (renderView()) renderView()->repaintViewAndCompositedLayers(); } - if (ScriptableDocumentParser* parser = scriptableDocumentParser()) - parser->executeScriptsWaitingForStylesheets(); + if (auto* parser = scriptableDocumentParser()) + parser->executeScriptsWaitingForStylesheetsSoon(); +} + +bool Document::usesStyleBasedEditability() const +{ + if (m_hasElementUsingStyleBasedEditability) + return true; - if (m_gotoAnchorNeededAfterStylesheetsLoad && view()) - view()->scrollToFragment(m_url); + ASSERT(!m_renderView || !m_renderView->frameView().isPainting()); + ASSERT(!m_inStyleRecalc); + + auto& styleScope = const_cast<Style::Scope&>(this->styleScope()); + styleScope.flushPendingUpdate(); + return styleScope.usesStyleBasedEditability(); } -CSSStyleSheet& Document::elementSheet() +void Document::setHasElementUsingStyleBasedEditability() { - if (!m_elementSheet) - m_elementSheet = CSSStyleSheet::createInline(*this, m_baseURL); - return *m_elementSheet; + m_hasElementUsingStyleBasedEditability = true; } -void Document::processHttpEquiv(const String& equiv, const String& content) +void Document::processHttpEquiv(const String& equiv, const String& content, bool isInDocumentHead) { - ASSERT(!equiv.isNull() && !content.isNull()); + ASSERT(!equiv.isNull()); + ASSERT(!content.isNull()); + + HttpEquivPolicy policy = httpEquivPolicy(); + if (policy != HttpEquivPolicy::Enabled) { + String reason; + switch (policy) { + case HttpEquivPolicy::Enabled: + ASSERT_NOT_REACHED(); + break; + case HttpEquivPolicy::DisabledBySettings: + reason = "by the embedder."; + break; + case HttpEquivPolicy::DisabledByContentDispositionAttachmentSandbox: + reason = "for documents with Content-Disposition: attachment."; + break; + } + String message = "http-equiv '" + equiv + "' is disabled " + reason; + addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); + return; + } Frame* frame = this->frame(); - if (equalIgnoringCase(equiv, "default-style")) { - // The preferred style set has been overridden as per section + HTTPHeaderName headerName; + if (!findHTTPHeaderName(equiv, headerName)) + return; + + switch (headerName) { + case HTTPHeaderName::DefaultStyle: + // The preferred style set has been overridden as per section // 14.3.2 of the HTML4.0 specification. We need to update the - // sheet used variable and then update our style selector. + // sheet used variable and then update our style selector. // For more info, see the test at: // http://www.hixie.ch/tests/evil/css/import/main/preferred.html // -dwh - m_styleSheetCollection.setSelectedStylesheetSetName(content); - m_styleSheetCollection.setPreferredStylesheetSetName(content); - styleResolverChanged(DeferRecalcStyle); - } else if (equalIgnoringCase(equiv, "refresh")) { + styleScope().setSelectedStylesheetSetName(content); + styleScope().setPreferredStylesheetSetName(content); + break; + + case HTTPHeaderName::Refresh: { double delay; - String url; - if (frame && parseHTTPRefresh(content, true, delay, url)) { - if (url.isEmpty()) - url = m_url.string(); + String urlString; + if (frame && parseMetaHTTPEquivRefresh(content, delay, urlString)) { + URL completedURL; + if (urlString.isEmpty()) + completedURL = m_url; else - url = completeURL(url).string(); - if (!protocolIsJavaScript(url)) - frame->navigationScheduler().scheduleRedirect(delay, url); + completedURL = completeURL(urlString); + if (!protocolIsJavaScript(completedURL)) + frame->navigationScheduler().scheduleRedirect(*this, delay, completedURL); else { String message = "Refused to refresh " + m_url.stringCenterEllipsizedToLength() + " to a javascript: URL"; - addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); + addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); } } - } else if (equalIgnoringCase(equiv, "set-cookie")) { + + break; + } + + case HTTPHeaderName::SetCookie: // FIXME: make setCookie work on XML documents too; e.g. in case of <html:meta .....> - if (isHTMLDocument()) { + if (is<HTMLDocument>(*this)) { // Exception (for sandboxed documents) ignored. - toHTMLDocument(this)->setCookie(content, IGNORE_EXCEPTION); + downcast<HTMLDocument>(*this).setCookie(content); } - } else if (equalIgnoringCase(equiv, "content-language")) + break; + + case HTTPHeaderName::ContentLanguage: setContentLanguage(content); - else if (equalIgnoringCase(equiv, "x-dns-prefetch-control")) + break; + + case HTTPHeaderName::XDNSPrefetchControl: parseDNSPrefetchControlHeader(content); - else if (equalIgnoringCase(equiv, "x-frame-options")) { + break; + + case HTTPHeaderName::XFrameOptions: if (frame) { FrameLoader& frameLoader = frame->loader(); unsigned long requestIdentifier = 0; if (frameLoader.activeDocumentLoader() && frameLoader.activeDocumentLoader()->mainResourceLoader()) requestIdentifier = frameLoader.activeDocumentLoader()->mainResourceLoader()->identifier(); - if (frameLoader.shouldInterruptLoadForXFrameOptions(content, url(), requestIdentifier)) { - String message = "Refused to display '" + url().stringCenterEllipsizedToLength() + "' in a frame because it set 'X-Frame-Options' to '" + content + "'."; - frameLoader.stopAllLoaders(); - // Stopping the loader isn't enough, as we're already parsing the document; to honor the header's - // intent, we must navigate away from the possibly partially-rendered document to a location that - // doesn't inherit the parent's SecurityOrigin. - frame->navigationScheduler().scheduleLocationChange(securityOrigin(), SecurityOrigin::urlWithUniqueSecurityOrigin(), String()); - addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message, requestIdentifier); - } - } - } else if (equalIgnoringCase(equiv, "content-security-policy")) - contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicy::Enforce); - else if (equalIgnoringCase(equiv, "content-security-policy-report-only")) - contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicy::Report); - else if (equalIgnoringCase(equiv, "x-webkit-csp")) - contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicy::PrefixedEnforce); - else if (equalIgnoringCase(equiv, "x-webkit-csp-report-only")) - contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicy::PrefixedReport); -} -// Though isspace() considers \t and \v to be whitespace, Win IE doesn't. -static bool isSeparator(UChar c) -{ - return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '=' || c == ',' || c == '\0'; -} - -void Document::processArguments(const String& features, void* data, ArgumentsCallback callback) -{ - // Tread lightly in this code -- it was specifically designed to mimic Win IE's parsing behavior. - int keyBegin, keyEnd; - int valueBegin, valueEnd; - - int i = 0; - int length = features.length(); - String buffer = features.lower(); - while (i < length) { - // skip to first non-separator, but don't skip past the end of the string - while (isSeparator(buffer[i])) { - if (i >= length) - break; - i++; + String message = "The X-Frame-Option '" + content + "' supplied in a <meta> element was ignored. X-Frame-Options may only be provided by an HTTP header sent with the document."; + addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, requestIdentifier); } - keyBegin = i; - - // skip to first separator - while (!isSeparator(buffer[i])) - i++; - keyEnd = i; - - // skip to first '=', but don't skip past a ',' or the end of the string - while (buffer[i] != '=') { - if (buffer[i] == ',' || i >= length) - break; - i++; - } - - // skip to first non-separator, but don't skip past a ',' or the end of the string - while (isSeparator(buffer[i])) { - if (buffer[i] == ',' || i >= length) - break; - i++; - } - valueBegin = i; + break; - // skip to first separator - while (!isSeparator(buffer[i])) - i++; - valueEnd = i; + case HTTPHeaderName::ContentSecurityPolicy: + if (isInDocumentHead) + contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicyHeaderType::Enforce, ContentSecurityPolicy::PolicyFrom::HTTPEquivMeta); + break; - ASSERT_WITH_SECURITY_IMPLICATION(i <= length); + case HTTPHeaderName::XWebKitCSP: + if (isInDocumentHead) + contentSecurityPolicy()->didReceiveHeader(content, ContentSecurityPolicyHeaderType::PrefixedEnforce, ContentSecurityPolicy::PolicyFrom::HTTPEquivMeta); + break; - String keyString = buffer.substring(keyBegin, keyEnd - keyBegin); - String valueString = buffer.substring(valueBegin, valueEnd - valueBegin); - callback(keyString, valueString, this, data); + default: + break; } } @@ -2952,15 +3228,10 @@ void Document::processViewport(const String& features, ViewportArguments::Type o return; m_viewportArguments = ViewportArguments(origin); - processArguments(features, (void*)&m_viewportArguments, &setViewportFeature); -#if PLATFORM(IOS) - // FIXME: <rdar://problem/8955959> Investigate moving to ToT WebKit's extended Viewport Implementation - // Moving to ToT's implementation would mean calling findConfigurationForViewportData, which does - // bounds checking and determining concrete values for ValueAuto which we already do in UIKit. - // To maintain old behavior, we just need to update a few values, leaving Auto's for UIKit. - finalizeViewportArguments(m_viewportArguments); -#endif + processFeaturesString(features, [this](StringView key, StringView value) { + setViewportFeature(m_viewportArguments, *this, key, value); + }); updateViewportArguments(); } @@ -2972,24 +3243,19 @@ void Document::updateViewportArguments() m_didDispatchViewportPropertiesChanged = true; #endif page()->chrome().dispatchViewportPropertiesDidChange(m_viewportArguments); -#if PLATFORM(IOS) - page()->chrome().didReceiveDocType(frame()); -#endif + page()->chrome().didReceiveDocType(*frame()); } } #if PLATFORM(IOS) -// FIXME: Find a better place for this functionality. -void setParserFeature(const String& key, const String& value, Document* document, void*) -{ - if (key == "telephone" && equalIgnoringCase(value, "no")) - document->setIsTelephoneNumberParsingAllowed(false); -} void Document::processFormatDetection(const String& features) { - ASSERT(!features.isNull()); - processArguments(features, nullptr, &setParserFeature); + // FIXME: Find a better place for this function. + processFeaturesString(features, [this](StringView key, StringView value) { + if (equalLettersIgnoringASCIICase(key, "telephone") && equalLettersIgnoringASCIICase(value, "no")) + setIsTelephoneNumberParsingAllowed(false); + }); } void Document::processWebAppOrientations() @@ -2997,20 +3263,37 @@ void Document::processWebAppOrientations() if (Page* page = this->page()) page->chrome().client().webAppOrientationsUpdated(); } + #endif void Document::processReferrerPolicy(const String& policy) { ASSERT(!policy.isNull()); - m_referrerPolicy = ReferrerPolicyDefault; + // Documents in a Content-Disposition: attachment sandbox should never send a Referer header, + // even if the document has a meta tag saying otherwise. + if (shouldEnforceContentDispositionAttachmentSandbox()) + return; + +#if USE(QUICK_LOOK) + if (shouldEnforceQuickLookSandbox()) + return; +#endif - if (equalIgnoringCase(policy, "never")) - m_referrerPolicy = ReferrerPolicyNever; - else if (equalIgnoringCase(policy, "always")) - m_referrerPolicy = ReferrerPolicyAlways; - else if (equalIgnoringCase(policy, "origin")) - m_referrerPolicy = ReferrerPolicyOrigin; + // Note that we're supporting both the standard and legacy keywords for referrer + // policies, as defined by http://www.w3.org/TR/referrer-policy/#referrer-policy-delivery-meta + if (equalLettersIgnoringASCIICase(policy, "no-referrer") || equalLettersIgnoringASCIICase(policy, "never")) + setReferrerPolicy(ReferrerPolicy::Never); + else if (equalLettersIgnoringASCIICase(policy, "unsafe-url") || equalLettersIgnoringASCIICase(policy, "always")) + setReferrerPolicy(ReferrerPolicy::Always); + else if (equalLettersIgnoringASCIICase(policy, "origin")) + setReferrerPolicy(ReferrerPolicy::Origin); + else if (equalLettersIgnoringASCIICase(policy, "no-referrer-when-downgrade") || equalLettersIgnoringASCIICase(policy, "default")) + setReferrerPolicy(ReferrerPolicy::Default); + else { + addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, "Failed to set referrer policy: The value '" + policy + "' is not one of 'no-referrer', 'origin', 'no-referrer-when-downgrade', or 'unsafe-url'. Defaulting to 'no-referrer'."); + setReferrerPolicy(ReferrerPolicy::Never); + } } MouseEventWithHitTestResults Document::prepareMouseEvent(const HitTestRequest& request, const LayoutPoint& documentPoint, const PlatformMouseEvent& event) @@ -3022,7 +3305,7 @@ MouseEventWithHitTestResults Document::prepareMouseEvent(const HitTestRequest& r renderView()->hitTest(request, result); if (!request.readOnly()) - updateHoverActiveState(request, result.innerElement(), &event); + updateHoverActiveState(request, result.targetElement()); return MouseEventWithHitTestResults(event, result); } @@ -3035,11 +3318,7 @@ bool Document::childTypeAllowed(NodeType type) const case CDATA_SECTION_NODE: case DOCUMENT_FRAGMENT_NODE: case DOCUMENT_NODE: - case ENTITY_NODE: - case ENTITY_REFERENCE_NODE: - case NOTATION_NODE: case TEXT_NODE: - case XPATH_NAMESPACE_NODE: return false; case COMMENT_NODE: case PROCESSING_INSTRUCTION_NODE: @@ -3056,103 +3335,97 @@ bool Document::childTypeAllowed(NodeType type) const return false; } -bool Document::canReplaceChild(Node* newChild, Node* oldChild) +bool Document::canAcceptChild(const Node& newChild, const Node* refChild, AcceptChildOperation operation) const { - if (!oldChild) - // ContainerNode::replaceChild will raise a NOT_FOUND_ERR. + if (operation == AcceptChildOperation::Replace && refChild->nodeType() == newChild.nodeType()) return true; - if (oldChild->nodeType() == newChild->nodeType()) + switch (newChild.nodeType()) { + case ATTRIBUTE_NODE: + case CDATA_SECTION_NODE: + case DOCUMENT_NODE: + case TEXT_NODE: + return false; + case COMMENT_NODE: + case PROCESSING_INSTRUCTION_NODE: return true; - - int numDoctypes = 0; - int numElements = 0; - - // First, check how many doctypes and elements we have, not counting - // the child we're about to remove. - for (Node* c = firstChild(); c; c = c->nextSibling()) { - if (c == oldChild) - continue; - - switch (c->nodeType()) { - case DOCUMENT_TYPE_NODE: - numDoctypes++; - break; - case ELEMENT_NODE: - numElements++; - break; - default: - break; + case DOCUMENT_FRAGMENT_NODE: { + bool hasSeenElementChild = false; + for (auto* node = downcast<DocumentFragment>(newChild).firstChild(); node; node = node->nextSibling()) { + if (is<Element>(*node)) { + if (hasSeenElementChild) + return false; + hasSeenElementChild = true; + } + if (!canAcceptChild(*node, refChild, operation)) + return false; } + break; } - - // Then, see how many doctypes and elements might be added by the new child. - if (newChild->isDocumentFragment()) { - for (Node* c = newChild->firstChild(); c; c = c->nextSibling()) { - switch (c->nodeType()) { - case ATTRIBUTE_NODE: - case CDATA_SECTION_NODE: - case DOCUMENT_FRAGMENT_NODE: - case DOCUMENT_NODE: - case ENTITY_NODE: - case ENTITY_REFERENCE_NODE: - case NOTATION_NODE: - case TEXT_NODE: - case XPATH_NAMESPACE_NODE: + case DOCUMENT_TYPE_NODE: { + auto* existingDocType = childrenOfType<DocumentType>(*this).first(); + if (operation == AcceptChildOperation::Replace) { + // parent has a doctype child that is not child, or an element is preceding child. + if (existingDocType && existingDocType != refChild) return false; - case COMMENT_NODE: - case PROCESSING_INSTRUCTION_NODE: - break; - case DOCUMENT_TYPE_NODE: - numDoctypes++; - break; - case ELEMENT_NODE: - numElements++; - break; + if (refChild->previousElementSibling()) + return false; + } else { + ASSERT(operation == AcceptChildOperation::InsertOrAdd); + if (existingDocType) + return false; + if ((refChild && refChild->previousElementSibling()) || (!refChild && firstElementChild())) + return false; + } + break; + } + case ELEMENT_NODE: { + auto* existingElementChild = firstElementChild(); + if (operation == AcceptChildOperation::Replace) { + if (existingElementChild && existingElementChild != refChild) + return false; + for (auto* child = refChild->nextSibling(); child; child = child->nextSibling()) { + if (is<DocumentType>(*child)) + return false; + } + } else { + ASSERT(operation == AcceptChildOperation::InsertOrAdd); + if (existingElementChild) + return false; + for (auto* child = refChild; child; child = child->nextSibling()) { + if (is<DocumentType>(*child)) + return false; } } - } else { - switch (newChild->nodeType()) { - case ATTRIBUTE_NODE: - case CDATA_SECTION_NODE: - case DOCUMENT_FRAGMENT_NODE: - case DOCUMENT_NODE: - case ENTITY_NODE: - case ENTITY_REFERENCE_NODE: - case NOTATION_NODE: - case TEXT_NODE: - case XPATH_NAMESPACE_NODE: - return false; - case COMMENT_NODE: - case PROCESSING_INSTRUCTION_NODE: - return true; - case DOCUMENT_TYPE_NODE: - numDoctypes++; - break; - case ELEMENT_NODE: - numElements++; - break; - } + break; + } } - - if (numElements > 1 || numDoctypes > 1) - return false; - return true; } -PassRefPtr<Node> Document::cloneNode(bool deep) +Ref<Node> Document::cloneNodeInternal(Document&, CloningOperation type) { - RefPtr<Document> clone = cloneDocumentWithoutChildren(); + Ref<Document> clone = cloneDocumentWithoutChildren(); clone->cloneDataFromDocument(*this); - if (deep) - cloneChildNodes(clone.get()); - return clone.release(); + switch (type) { + case CloningOperation::OnlySelf: + case CloningOperation::SelfWithTemplateContent: + break; + case CloningOperation::Everything: + cloneChildNodes(clone); + break; + } + return WTFMove(clone); } -PassRefPtr<Document> Document::cloneDocumentWithoutChildren() const +Ref<Document> Document::cloneDocumentWithoutChildren() const { - return isXHTMLDocument() ? createXHTML(nullptr, url()) : create(nullptr, url()); + if (isXMLDocument()) { + if (isXHTMLDocument()) + return XMLDocument::createXHTML(nullptr, url()); + return XMLDocument::create(nullptr, url()); + } + return create(nullptr, url()); } void Document::cloneDataFromDocument(const Document& other) @@ -3162,144 +3435,147 @@ void Document::cloneDataFromDocument(const Document& other) m_baseURLOverride = other.baseURLOverride(); m_documentURI = other.documentURI(); - setCompatibilityMode(other.compatibilityMode()); - setSecurityOrigin(other.securityOrigin()); + setCompatibilityMode(other.m_compatibilityMode); + setContextDocument(other.contextDocument()); + setSecurityOriginPolicy(other.securityOriginPolicy()); + overrideMIMEType(other.contentType()); setDecoder(other.decoder()); } -StyleSheetList* Document::styleSheets() +StyleSheetList& Document::styleSheets() { if (!m_styleSheetList) m_styleSheetList = StyleSheetList::create(this); - return m_styleSheetList.get(); + return *m_styleSheetList; } String Document::preferredStylesheetSet() const { - return m_styleSheetCollection.preferredStylesheetSetName(); + return styleScope().preferredStylesheetSetName(); } String Document::selectedStylesheetSet() const { - return m_styleSheetCollection.selectedStylesheetSetName(); + return styleScope().selectedStylesheetSetName(); } void Document::setSelectedStylesheetSet(const String& aString) { - m_styleSheetCollection.setSelectedStylesheetSetName(aString); - styleResolverChanged(DeferRecalcStyle); + styleScope().setSelectedStylesheetSetName(aString); } void Document::evaluateMediaQueryList() { if (m_mediaQueryMatcher) m_mediaQueryMatcher->styleResolverChanged(); + + checkViewportDependentPictures(); } -void Document::optimizedStyleSheetUpdateTimerFired(Timer<Document>&) +void Document::checkViewportDependentPictures() { - styleResolverChanged(RecalcStyleIfNeeded); + Vector<HTMLPictureElement*, 16> changedPictures; + HashSet<HTMLPictureElement*>::iterator end = m_viewportDependentPictures.end(); + for (HashSet<HTMLPictureElement*>::iterator it = m_viewportDependentPictures.begin(); it != end; ++it) { + if ((*it)->viewportChangeAffectedPicture()) + changedPictures.append(*it); + } + for (auto* picture : changedPictures) + picture->sourcesChanged(); } -void Document::scheduleOptimizedStyleSheetUpdate() +void Document::updateViewportUnitsOnResize() { - if (m_optimizedStyleSheetUpdateTimer.isActive()) + if (!hasStyleWithViewportUnits()) return; - m_styleSheetCollection.setPendingUpdateType(DocumentStyleSheetCollection::OptimizedUpdate); - m_optimizedStyleSheetUpdateTimer.startOneShot(0); -} -void Document::styleResolverChanged(StyleResolverUpdateFlag updateFlag) -{ - if (m_optimizedStyleSheetUpdateTimer.isActive()) - m_optimizedStyleSheetUpdateTimer.stop(); + styleScope().resolver().clearCachedPropertiesAffectedByViewportUnits(); - // Don't bother updating, since we haven't loaded all our style info yet - // and haven't calculated the style selector for the first time. - if (!hasLivingRenderTree() || (!m_didCalculateStyleResolver && !haveStylesheetsLoaded())) { - m_styleResolver.clear(); - return; + // FIXME: Ideally, we should save the list of elements that have viewport units and only iterate over those. + for (Element* element = ElementTraversal::firstWithin(rootNode()); element; element = ElementTraversal::nextIncludingPseudo(*element)) { + auto* renderer = element->renderer(); + if (renderer && renderer->style().hasViewportUnits()) + element->invalidateStyle(); } - m_didCalculateStyleResolver = true; - -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement()) - printf("Beginning update of style selector at time %lld.\n", elapsedTime().count()); -#endif +} - DocumentStyleSheetCollection::UpdateFlag styleSheetUpdate = (updateFlag == RecalcStyleIfNeeded || updateFlag == DeferRecalcStyleIfNeeded) - ? DocumentStyleSheetCollection::OptimizedUpdate - : DocumentStyleSheetCollection::FullUpdate; - bool stylesheetChangeRequiresStyleRecalc = m_styleSheetCollection.updateActiveStyleSheets(styleSheetUpdate); +void Document::addAudioProducer(MediaProducer* audioProducer) +{ + m_audioProducers.add(audioProducer); + updateIsPlayingMedia(); +} - if (updateFlag == DeferRecalcStyle) { - scheduleForcedStyleRecalc(); - return; - } +void Document::removeAudioProducer(MediaProducer* audioProducer) +{ + m_audioProducers.remove(audioProducer); + updateIsPlayingMedia(); +} - if (updateFlag == DeferRecalcStyleIfNeeded) { - if (stylesheetChangeRequiresStyleRecalc) - scheduleForcedStyleRecalc(); - return; - } +void Document::updateIsPlayingMedia(uint64_t sourceElementID) +{ + MediaProducer::MediaStateFlags state = MediaProducer::IsNotPlaying; + for (auto* audioProducer : m_audioProducers) + state |= audioProducer->mediaState(); - if (!stylesheetChangeRequiresStyleRecalc) - return; +#if ENABLE(MEDIA_SESSION) + if (HTMLMediaElement* sourceElement = HTMLMediaElement::elementWithID(sourceElementID)) { + if (sourceElement->isPlaying()) + state |= MediaProducer::IsSourceElementPlaying; - // This recalcStyle initiates a new recalc cycle. We need to bracket it to - // make sure animations get the correct update time - { - AnimationUpdateBlock animationUpdateBlock(m_frame ? &m_frame->animation() : nullptr); - recalcStyle(Style::Force); + if (auto* session = sourceElement->session()) { + if (auto* controls = session->controls()) { + if (controls->previousTrackEnabled()) + state |= MediaProducer::IsPreviousTrackControlEnabled; + if (controls->nextTrackEnabled()) + state |= MediaProducer::IsNextTrackControlEnabled; + } + } } - -#ifdef INSTRUMENT_LAYOUT_SCHEDULING - if (!ownerElement()) - printf("Finished update of style selector at time %lld\n", elapsedTime().count()); #endif - if (renderView()) { - renderView()->setNeedsLayoutAndPrefWidthsRecalc(); - if (view()) - view()->scheduleRelayout(); - } + if (state == m_mediaState) + return; - evaluateMediaQueryList(); + m_mediaState = state; + + if (page()) + page()->updateIsPlayingMedia(sourceElementID); } -void Document::notifySeamlessChildDocumentsOfStylesheetUpdate() const +void Document::pageMutedStateDidChange() { - // If we're not in a frame yet any potential child documents won't have a StyleResolver to update. - if (!frame()) - return; + for (auto* audioProducer : m_audioProducers) + audioProducer->pageMutedStateDidChange(); +} - // Seamless child frames are expected to notify their seamless children recursively, so we only do direct children. - for (Frame* child = frame()->tree().firstChild(); child; child = child->tree().nextSibling()) { - Document* childDocument = child->document(); - if (childDocument->shouldDisplaySeamlesslyWithParent()) { - ASSERT(&childDocument->seamlessParentIFrame()->document() == this); - childDocument->seamlessParentUpdatedStylesheets(); - } - } +static bool isNodeInSubtree(Node& node, Node& container, bool amongChildrenOnly) +{ + if (amongChildrenOnly) + return node.isDescendantOf(container); + else + return &node == &container || node.isDescendantOf(container); } -void Document::removeFocusedNodeOfSubtree(Node* node, bool amongChildrenOnly) +void Document::removeFocusedNodeOfSubtree(Node& node, bool amongChildrenOnly) { - if (!m_focusedElement || this->inPageCache()) // If the document is in the page cache, then we don't need to clear out the focused node. + if (!m_focusedElement || pageCacheState() != NotInPageCache) // If the document is in the page cache, then we don't need to clear out the focused node. return; - Element* focusedElement = node->treeScope().focusedElement(); + Element* focusedElement = node.treeScope().focusedElementInScope(); if (!focusedElement) return; - - bool nodeInSubtree = false; - if (amongChildrenOnly) - nodeInSubtree = focusedElement->isDescendantOf(node); - else - nodeInSubtree = (focusedElement == node) || focusedElement->isDescendantOf(node); - if (nodeInSubtree) - setFocusedElement(nullptr); + if (isNodeInSubtree(*focusedElement, node, amongChildrenOnly)) { + // FIXME: We should avoid synchronously updating the style inside setFocusedElement. + // FIXME: Object elements should avoid loading a frame synchronously in a post style recalc callback. + SubframeLoadingDisabler disabler(is<ContainerNode>(node) ? &downcast<ContainerNode>(node) : nullptr); + setFocusedElement(nullptr, FocusDirectionNone, FocusRemovalEventsMode::DoNotDispatch); + // Set the focus navigation starting node to the previous focused element so that + // we can fallback to the siblings or parent node for the next search. + // Also we need to call removeFocusNavigationNodeOfSubtree after this function because + // setFocusedElement(nullptr) will reset m_focusNavigationStartingNode. + setFocusNavigationStartingNode(focusedElement); + } } void Document::hoveredElementDidDetach(Element* element) @@ -3325,6 +3601,7 @@ void Document::elementInActiveChainDidDetach(Element* element) } #if ENABLE(DASHBOARD_SUPPORT) + const Vector<AnnotatedRegionValue>& Document::annotatedRegions() const { return m_annotatedRegions; @@ -3335,12 +3612,12 @@ void Document::setAnnotatedRegions(const Vector<AnnotatedRegionValue>& regions) m_annotatedRegions = regions; setAnnotatedRegionsDirty(false); } + #endif -bool Document::setFocusedElement(PassRefPtr<Element> prpNewFocusedElement, FocusDirection direction) +bool Document::setFocusedElement(Element* element, FocusDirection direction, FocusRemovalEventsMode eventsMode) { - RefPtr<Element> newFocusedElement = prpNewFocusedElement; - + RefPtr<Element> newFocusedElement = element; // Make sure newFocusedElement is actually in this document if (newFocusedElement && (&newFocusedElement->document() != this)) return true; @@ -3348,46 +3625,53 @@ bool Document::setFocusedElement(PassRefPtr<Element> prpNewFocusedElement, Focus if (m_focusedElement == newFocusedElement) return true; - if (m_inPageCache) + if (pageCacheState() != NotInPageCache) return false; bool focusChangeBlocked = false; - RefPtr<Element> oldFocusedElement = m_focusedElement.release(); + RefPtr<Element> oldFocusedElement = WTFMove(m_focusedElement); // Remove focus from the existing focus node (if any) if (oldFocusedElement) { - if (oldFocusedElement->active()) - oldFocusedElement->setActive(false); - oldFocusedElement->setFocus(false); + setFocusNavigationStartingNode(nullptr); + + if (eventsMode == FocusRemovalEventsMode::Dispatch) { + // Dispatch a change event for form control elements that have been edited. + if (is<HTMLFormControlElement>(*oldFocusedElement)) { + HTMLFormControlElement& formControlElement = downcast<HTMLFormControlElement>(*oldFocusedElement); + if (formControlElement.wasChangedSinceLastFormControlChangeEvent()) + formControlElement.dispatchFormControlChangeEvent(); + } - // Dispatch a change event for form control elements that have been edited. - if (oldFocusedElement->isFormControlElement()) { - HTMLFormControlElement* formControlElement = toHTMLFormControlElement(oldFocusedElement.get()); - if (formControlElement->wasChangedSinceLastFormControlChangeEvent()) - formControlElement->dispatchFormControlChangeEvent(); - } + // Dispatch the blur event and let the node do any other blur related activities (important for text fields) + oldFocusedElement->dispatchBlurEvent(newFocusedElement.copyRef()); - // Dispatch the blur event and let the node do any other blur related activities (important for text fields) - oldFocusedElement->dispatchBlurEvent(newFocusedElement); + if (m_focusedElement) { + // handler shifted focus + focusChangeBlocked = true; + newFocusedElement = nullptr; + } - if (m_focusedElement) { - // handler shifted focus - focusChangeBlocked = true; - newFocusedElement = nullptr; - } - - oldFocusedElement->dispatchFocusOutEvent(eventNames().focusoutEvent, newFocusedElement); // DOM level 3 name for the bubbling blur event. - // FIXME: We should remove firing DOMFocusOutEvent event when we are sure no content depends - // on it, probably when <rdar://problem/8503958> is resolved. - oldFocusedElement->dispatchFocusOutEvent(eventNames().DOMFocusOutEvent, newFocusedElement); // DOM level 2 name for compatibility. + oldFocusedElement->dispatchFocusOutEvent(eventNames().focusoutEvent, newFocusedElement.copyRef()); // DOM level 3 name for the bubbling blur event. + // FIXME: We should remove firing DOMFocusOutEvent event when we are sure no content depends + // on it, probably when <rdar://problem/8503958> is resolved. + oldFocusedElement->dispatchFocusOutEvent(eventNames().DOMFocusOutEvent, newFocusedElement.copyRef()); // DOM level 2 name for compatibility. - if (m_focusedElement) { - // handler shifted focus - focusChangeBlocked = true; - newFocusedElement = nullptr; + if (m_focusedElement) { + // handler shifted focus + focusChangeBlocked = true; + newFocusedElement = nullptr; + } + } else { + // Match the order in HTMLTextFormControlElement::dispatchBlurEvent. + if (is<HTMLInputElement>(*oldFocusedElement)) + downcast<HTMLInputElement>(*oldFocusedElement).endEditing(); + if (page()) + page()->chrome().client().elementDidBlur(*oldFocusedElement); + ASSERT(!m_focusedElement); } - + if (oldFocusedElement->isRootEditableElement()) frame()->editor().didEndEditing(); @@ -3407,9 +3691,10 @@ bool Document::setFocusedElement(PassRefPtr<Element> prpNewFocusedElement, Focus } // Set focus on the new node m_focusedElement = newFocusedElement; + setFocusNavigationStartingNode(m_focusedElement.get()); // Dispatch the focus event and let the node do any other focus related activities (important for text fields) - m_focusedElement->dispatchFocusEvent(oldFocusedElement, direction); + m_focusedElement->dispatchFocusEvent(oldFocusedElement.copyRef(), direction); if (m_focusedElement != newFocusedElement) { // handler shifted focus @@ -3417,7 +3702,7 @@ bool Document::setFocusedElement(PassRefPtr<Element> prpNewFocusedElement, Focus goto SetFocusedNodeDone; } - m_focusedElement->dispatchFocusInEvent(eventNames().focusinEvent, oldFocusedElement); // DOM level 3 bubbling focus event. + m_focusedElement->dispatchFocusInEvent(eventNames().focusinEvent, oldFocusedElement.copyRef()); // DOM level 3 bubbling focus event. if (m_focusedElement != newFocusedElement) { // handler shifted focus @@ -3427,7 +3712,7 @@ bool Document::setFocusedElement(PassRefPtr<Element> prpNewFocusedElement, Focus // FIXME: We should remove firing DOMFocusInEvent event when we are sure no content depends // on it, probably when <rdar://problem/8503958> is m. - m_focusedElement->dispatchFocusInEvent(eventNames().DOMFocusInEvent, oldFocusedElement); // DOM level 2 for compatibility. + m_focusedElement->dispatchFocusInEvent(eventNames().DOMFocusInEvent, oldFocusedElement.copyRef()); // DOM level 2 for compatibility. if (m_focusedElement != newFocusedElement) { // handler shifted focus @@ -3473,34 +3758,93 @@ SetFocusedNodeDone: return !focusChangeBlocked; } -void Document::setCSSTarget(Element* n) +static bool shouldResetFocusNavigationStartingNode(Node& node) +{ + // Setting focus navigation starting node to the following nodes means that we should start + // the search from the beginning of the document. + return is<HTMLHtmlElement>(node) || is<HTMLDocument>(node); +} + +void Document::setFocusNavigationStartingNode(Node* node) +{ + if (!m_frame) + return; + + m_focusNavigationStartingNodeIsRemoved = false; + if (!node || shouldResetFocusNavigationStartingNode(*node)) { + m_focusNavigationStartingNode = nullptr; + return; + } + + m_focusNavigationStartingNode = node; +} + +Element* Document::focusNavigationStartingNode(FocusDirection direction) const +{ + if (m_focusedElement) { + if (!m_focusNavigationStartingNode || !m_focusNavigationStartingNode->isDescendantOf(m_focusedElement.get())) + return m_focusedElement.get(); + } + + if (!m_focusNavigationStartingNode) + return nullptr; + + Node* node = m_focusNavigationStartingNode.get(); + + // When the node was removed from the document tree. This case is not specified in the spec: + // https://html.spec.whatwg.org/multipage/interaction.html#sequential-focus-navigation-starting-point + // Current behaivor is to move the sequential navigation node to / after (based on the focus direction) + // the previous sibling of the removed node. + if (m_focusNavigationStartingNodeIsRemoved) { + Node* nextNode = NodeTraversal::next(*node); + if (!nextNode) + nextNode = node; + if (direction == FocusDirectionForward) + return ElementTraversal::previous(*nextNode); + if (is<Element>(*nextNode)) + return downcast<Element>(nextNode); + return ElementTraversal::next(*nextNode); + } + + if (is<Element>(*node)) + return downcast<Element>(node); + if (Element* elementBeforeNextFocusableElement = direction == FocusDirectionForward ? ElementTraversal::previous(*node) : ElementTraversal::next(*node)) + return elementBeforeNextFocusableElement; + return node->parentOrShadowHostElement(); +} + +void Document::setCSSTarget(Element* targetNode) { if (m_cssTarget) - m_cssTarget->didAffectSelector(AffectedSelectorTarget); - m_cssTarget = n; - if (n) - n->didAffectSelector(AffectedSelectorTarget); + m_cssTarget->invalidateStyleForSubtree(); + m_cssTarget = targetNode; + if (targetNode) + targetNode->invalidateStyleForSubtree(); } -void Document::registerNodeList(LiveNodeList& list) +void Document::registerNodeListForInvalidation(LiveNodeList& list) { m_nodeListAndCollectionCounts[list.invalidationType()]++; - if (list.isRootedAtDocument()) - m_listsInvalidatedAtDocument.add(&list); + if (!list.isRootedAtDocument()) + return; + ASSERT(!list.isRegisteredForInvalidationAtDocument()); + list.setRegisteredForInvalidationAtDocument(true); + m_listsInvalidatedAtDocument.add(&list); } -void Document::unregisterNodeList(LiveNodeList& list) +void Document::unregisterNodeListForInvalidation(LiveNodeList& list) { m_nodeListAndCollectionCounts[list.invalidationType()]--; - if (list.isRootedAtDocument()) { - ASSERT(m_listsInvalidatedAtDocument.contains(&list)); - m_listsInvalidatedAtDocument.remove(&list); - } + if (!list.isRegisteredForInvalidationAtDocument()) + return; + + list.setRegisteredForInvalidationAtDocument(false); + ASSERT(m_listsInvalidatedAtDocument.contains(&list)); + m_listsInvalidatedAtDocument.remove(&list); } void Document::registerCollection(HTMLCollection& collection) { - m_nodeListAndCollectionCounts[InvalidateOnIdNameAttrChange]++; m_nodeListAndCollectionCounts[collection.invalidationType()]++; if (collection.isRootedAtDocument()) m_collectionsInvalidatedAtDocument.add(&collection); @@ -3508,12 +3852,25 @@ void Document::registerCollection(HTMLCollection& collection) void Document::unregisterCollection(HTMLCollection& collection) { - m_nodeListAndCollectionCounts[InvalidateOnIdNameAttrChange]--; + ASSERT(m_nodeListAndCollectionCounts[collection.invalidationType()]); m_nodeListAndCollectionCounts[collection.invalidationType()]--; - if (collection.isRootedAtDocument()) { - ASSERT(m_collectionsInvalidatedAtDocument.contains(&collection)); - m_collectionsInvalidatedAtDocument.remove(&collection); - } + if (!collection.isRootedAtDocument()) + return; + + m_collectionsInvalidatedAtDocument.remove(&collection); +} + +void Document::collectionCachedIdNameMap(const HTMLCollection& collection) +{ + ASSERT_UNUSED(collection, collection.hasNamedElementCache()); + m_nodeListAndCollectionCounts[InvalidateOnIdNameAttrChange]++; +} + +void Document::collectionWillClearIdNameMap(const HTMLCollection& collection) +{ + ASSERT_UNUSED(collection, collection.hasNamedElementCache()); + ASSERT(m_nodeListAndCollectionCounts[InvalidateOnIdNameAttrChange]); + m_nodeListAndCollectionCounts[InvalidateOnIdNameAttrChange]--; } void Document::attachNodeIterator(NodeIterator* ni) @@ -3528,72 +3885,105 @@ void Document::detachNodeIterator(NodeIterator* ni) m_nodeIterators.remove(ni); } -void Document::moveNodeIteratorsToNewDocument(Node* node, Document* newDocument) +void Document::moveNodeIteratorsToNewDocument(Node& node, Document& newDocument) { - HashSet<NodeIterator*> nodeIteratorsList = m_nodeIterators; - HashSet<NodeIterator*>::const_iterator nodeIteratorsEnd = nodeIteratorsList.end(); - for (HashSet<NodeIterator*>::const_iterator it = nodeIteratorsList.begin(); it != nodeIteratorsEnd; ++it) { - if ((*it)->root() == node) { - detachNodeIterator(*it); - newDocument->attachNodeIterator(*it); + Vector<NodeIterator*> nodeIterators; + copyToVector(m_nodeIterators, nodeIterators); + for (auto* it : nodeIterators) { + if (&it->root() == &node) { + detachNodeIterator(it); + newDocument.attachNodeIterator(it); } } } void Document::updateRangesAfterChildrenChanged(ContainerNode& container) { - if (!m_ranges.isEmpty()) { - for (auto it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) - (*it)->nodeChildrenChanged(container); - } + for (auto* range : m_ranges) + range->nodeChildrenChanged(container); } void Document::nodeChildrenWillBeRemoved(ContainerNode& container) { - if (!m_ranges.isEmpty()) { - for (auto it = m_ranges.begin(), end = m_ranges.end(); it != end; ++it) - (*it)->nodeChildrenWillBeRemoved(container); - } + NoEventDispatchAssertion assertNoEventDispatch; + + removeFocusedNodeOfSubtree(container, true /* amongChildrenOnly */); + removeFocusNavigationNodeOfSubtree(container, true /* amongChildrenOnly */); + +#if ENABLE(FULLSCREEN_API) + removeFullScreenElementOfSubtree(container, true /* amongChildrenOnly */); +#endif - for (auto it = m_nodeIterators.begin(), end = m_nodeIterators.end(); it != end; ++it) { + for (auto* range : m_ranges) + range->nodeChildrenWillBeRemoved(container); + + for (auto* it : m_nodeIterators) { for (Node* n = container.firstChild(); n; n = n->nextSibling()) - (*it)->nodeWillBeRemoved(n); + it->nodeWillBeRemoved(*n); } if (Frame* frame = this->frame()) { for (Node* n = container.firstChild(); n; n = n->nextSibling()) { - frame->eventHandler().nodeWillBeRemoved(n); - frame->selection().nodeWillBeRemoved(n); - frame->page()->dragCaretController().nodeWillBeRemoved(n); + frame->eventHandler().nodeWillBeRemoved(*n); + frame->selection().nodeWillBeRemoved(*n); + frame->page()->dragCaretController().nodeWillBeRemoved(*n); } } + + if (m_markers->hasMarkers()) { + for (Text* textNode = TextNodeTraversal::firstChild(container); textNode; textNode = TextNodeTraversal::nextSibling(*textNode)) + m_markers->removeMarkers(textNode); + } } -void Document::nodeWillBeRemoved(Node* n) +void Document::nodeWillBeRemoved(Node& node) { - HashSet<NodeIterator*>::const_iterator nodeIteratorsEnd = m_nodeIterators.end(); - for (HashSet<NodeIterator*>::const_iterator it = m_nodeIterators.begin(); it != nodeIteratorsEnd; ++it) - (*it)->nodeWillBeRemoved(n); + NoEventDispatchAssertion assertNoEventDispatch; - if (!m_ranges.isEmpty()) { - HashSet<Range*>::const_iterator rangesEnd = m_ranges.end(); - for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != rangesEnd; ++it) - (*it)->nodeWillBeRemoved(n); - } + removeFocusedNodeOfSubtree(node); + removeFocusNavigationNodeOfSubtree(node); + +#if ENABLE(FULLSCREEN_API) + removeFullScreenElementOfSubtree(node); +#endif + + for (auto* it : m_nodeIterators) + it->nodeWillBeRemoved(node); + + for (auto* range : m_ranges) + range->nodeWillBeRemoved(node); if (Frame* frame = this->frame()) { - frame->eventHandler().nodeWillBeRemoved(n); - frame->selection().nodeWillBeRemoved(n); - frame->page()->dragCaretController().nodeWillBeRemoved(n); + frame->eventHandler().nodeWillBeRemoved(node); + frame->selection().nodeWillBeRemoved(node); + frame->page()->dragCaretController().nodeWillBeRemoved(node); + } + + if (is<Text>(node)) + m_markers->removeMarkers(&node); +} + +static Node* fallbackFocusNavigationStartingNodeAfterRemoval(Node& node) +{ + return node.previousSibling() ? node.previousSibling() : node.parentNode(); +} + +void Document::removeFocusNavigationNodeOfSubtree(Node& node, bool amongChildrenOnly) +{ + if (!m_focusNavigationStartingNode) + return; + + if (isNodeInSubtree(*m_focusNavigationStartingNode, node, amongChildrenOnly)) { + m_focusNavigationStartingNode = amongChildrenOnly ? &node : fallbackFocusNavigationStartingNodeAfterRemoval(node); + m_focusNavigationStartingNodeIsRemoved = true; } } void Document::textInserted(Node* text, unsigned offset, unsigned length) { if (!m_ranges.isEmpty()) { - HashSet<Range*>::const_iterator end = m_ranges.end(); - for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it) - (*it)->textInserted(text, offset, length); + for (auto* range : m_ranges) + range->textInserted(text, offset, length); } // Update the markers for spelling and grammar checking. @@ -3603,9 +3993,8 @@ void Document::textInserted(Node* text, unsigned offset, unsigned length) void Document::textRemoved(Node* text, unsigned offset, unsigned length) { if (!m_ranges.isEmpty()) { - HashSet<Range*>::const_iterator end = m_ranges.end(); - for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it) - (*it)->textRemoved(text, offset, length); + for (auto* range : m_ranges) + range->textRemoved(text, offset, length); } // Update the markers for spelling and grammar checking. @@ -3617,9 +4006,8 @@ void Document::textNodesMerged(Text* oldNode, unsigned offset) { if (!m_ranges.isEmpty()) { NodeWithIndex oldNodeWithIndex(oldNode); - HashSet<Range*>::const_iterator end = m_ranges.end(); - for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it) - (*it)->textNodesMerged(oldNodeWithIndex, offset); + for (auto* range : m_ranges) + range->textNodesMerged(oldNodeWithIndex, offset); } // FIXME: This should update markers for spelling and grammar checking. @@ -3627,11 +4015,8 @@ void Document::textNodesMerged(Text* oldNode, unsigned offset) void Document::textNodeSplit(Text* oldNode) { - if (!m_ranges.isEmpty()) { - HashSet<Range*>::const_iterator end = m_ranges.end(); - for (HashSet<Range*>::const_iterator it = m_ranges.begin(); it != end; ++it) - (*it)->textNodeSplit(oldNode); - } + for (auto* range : m_ranges) + range->textNodeSplit(oldNode); // FIXME: This should update markers for spelling and grammar checking. } @@ -3641,7 +4026,7 @@ void Document::createDOMWindow() ASSERT(m_frame); ASSERT(!m_domWindow); - m_domWindow = DOMWindow::create(this); + m_domWindow = DOMWindow::create(*this); ASSERT(m_domWindow->document() == this); ASSERT(m_domWindow->frame() == m_frame); @@ -3653,39 +4038,51 @@ void Document::takeDOMWindowFrom(Document* document) ASSERT(!m_domWindow); ASSERT(document->m_domWindow); // A valid DOMWindow is needed by CachedFrame for its documents. - ASSERT(!document->inPageCache()); + ASSERT(pageCacheState() == NotInPageCache); - m_domWindow = document->m_domWindow.release(); - m_domWindow->didSecureTransitionTo(this); + m_domWindow = WTFMove(document->m_domWindow); + m_domWindow->didSecureTransitionTo(*this); ASSERT(m_domWindow->document() == this); ASSERT(m_domWindow->frame() == m_frame); } -void Document::setWindowAttributeEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener) +Document& Document::contextDocument() const +{ + if (m_contextDocument) + return *m_contextDocument.get(); + return const_cast<Document&>(*this); +} + +void Document::setAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& attributeValue, DOMWrapperWorld& isolatedWorld) +{ + setAttributeEventListener(eventType, JSLazyEventListener::create(*this, attributeName, attributeValue), isolatedWorld); +} + +void Document::setWindowAttributeEventListener(const AtomicString& eventType, RefPtr<EventListener>&& listener, DOMWrapperWorld& isolatedWorld) { if (!m_domWindow) return; - m_domWindow->setAttributeEventListener(eventType, listener); + m_domWindow->setAttributeEventListener(eventType, WTFMove(listener), isolatedWorld); } -void Document::setWindowAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& attributeValue) +void Document::setWindowAttributeEventListener(const AtomicString& eventType, const QualifiedName& attributeName, const AtomicString& attributeValue, DOMWrapperWorld& isolatedWorld) { - if (!m_frame) + if (!m_domWindow) return; - setWindowAttributeEventListener(eventType, JSLazyEventListener::createForDOMWindow(*m_frame, attributeName, attributeValue)); + setWindowAttributeEventListener(eventType, JSLazyEventListener::create(*m_domWindow, attributeName, attributeValue), isolatedWorld); } -EventListener* Document::getWindowAttributeEventListener(const AtomicString& eventType) +EventListener* Document::getWindowAttributeEventListener(const AtomicString& eventType, DOMWrapperWorld& isolatedWorld) { if (!m_domWindow) return nullptr; - return m_domWindow->getAttributeEventListener(eventType); + return m_domWindow->attributeEventListener(eventType, isolatedWorld); } -void Document::dispatchWindowEvent(PassRefPtr<Event> event, PassRefPtr<EventTarget> target) +void Document::dispatchWindowEvent(Event& event, EventTarget* target) { - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread()); if (!m_domWindow) return; m_domWindow->dispatchEvent(event, target); @@ -3693,38 +4090,117 @@ void Document::dispatchWindowEvent(PassRefPtr<Event> event, PassRefPtr<EventTar void Document::dispatchWindowLoadEvent() { - ASSERT(!NoEventDispatchAssertion::isEventDispatchForbidden()); + ASSERT_WITH_SECURITY_IMPLICATION(NoEventDispatchAssertion::isEventAllowedInMainThread()); if (!m_domWindow) return; m_domWindow->dispatchLoadEvent(); m_loadEventFinished = true; + m_cachedResourceLoader->documentDidFinishLoadEvent(); } -void Document::enqueueWindowEvent(PassRefPtr<Event> event) +void Document::enqueueWindowEvent(Ref<Event>&& event) { event->setTarget(m_domWindow.get()); - m_eventQueue.enqueueEvent(event); + m_eventQueue.enqueueEvent(WTFMove(event)); } -void Document::enqueueDocumentEvent(PassRefPtr<Event> event) +void Document::enqueueDocumentEvent(Ref<Event>&& event) { event->setTarget(this); - m_eventQueue.enqueueEvent(event); + m_eventQueue.enqueueEvent(WTFMove(event)); } -void Document::enqueueOverflowEvent(PassRefPtr<Event> event) +void Document::enqueueOverflowEvent(Ref<Event>&& event) { - m_eventQueue.enqueueEvent(event); + m_eventQueue.enqueueEvent(WTFMove(event)); } -PassRefPtr<Event> Document::createEvent(const String& eventType, ExceptionCode& ec) +ExceptionOr<Ref<Event>> Document::createEvent(const String& type) { - RefPtr<Event> event = EventFactory::create(eventType); - if (event) - return event.release(); + // Please do *not* add new event classes to this function unless they are + // required for compatibility of some actual legacy web content. - ec = NOT_SUPPORTED_ERR; - return nullptr; + // This mechanism is superceded by use of event constructors. + // That is what we should use for any new event classes. + + // The following strings are the ones from the DOM specification + // <https://dom.spec.whatwg.org/#dom-document-createevent>. + + if (equalLettersIgnoringASCIICase(type, "customevent")) + return Ref<Event> { CustomEvent::create() }; + if (equalLettersIgnoringASCIICase(type, "event") || equalLettersIgnoringASCIICase(type, "events") || equalLettersIgnoringASCIICase(type, "htmlevents")) + return Event::createForBindings(); + if (equalLettersIgnoringASCIICase(type, "keyboardevent") || equalLettersIgnoringASCIICase(type, "keyboardevents")) + return Ref<Event> { KeyboardEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "messageevent")) + return Ref<Event> { MessageEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "mouseevent") || equalLettersIgnoringASCIICase(type, "mouseevents")) + return Ref<Event> { MouseEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "uievent") || equalLettersIgnoringASCIICase(type, "uievents")) + return Ref<Event> { UIEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "popstateevent")) + return Ref<Event> { PopStateEvent::createForBindings() }; + +#if ENABLE(TOUCH_EVENTS) + if (equalLettersIgnoringASCIICase(type, "touchevent")) + return Ref<Event> { TouchEvent::createForBindings() }; +#endif + + // The following string comes from the SVG specifications + // <http://www.w3.org/TR/SVG/script.html#InterfaceSVGZoomEvent> + // <http://www.w3.org/TR/SVG2/interact.html#InterfaceSVGZoomEvent>. + // However, since there is no provision for initializing the event once it is created, + // there is no practical value in this feature. + + if (equalLettersIgnoringASCIICase(type, "svgzoomevents")) + return Ref<Event> { SVGZoomEvent::createForBindings() }; + + // The following strings are for event classes where WebKit supplies an init function. + // These strings are not part of the DOM specification and we would like to eliminate them. + // However, we currently include these because we have concerns about backward compatibility. + + // FIXME: For each of the strings below, prove there is no content depending on it and remove + // both the string and the corresponding init function for that class. + + if (equalLettersIgnoringASCIICase(type, "compositionevent")) + return Ref<Event> { CompositionEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "hashchangeevent")) + return Ref<Event> { HashChangeEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "mutationevent") || equalLettersIgnoringASCIICase(type, "mutationevents")) + return Ref<Event> { MutationEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "overflowevent")) + return Ref<Event> { OverflowEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "storageevent")) + return Ref<Event> { StorageEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "textevent")) + return Ref<Event> { TextEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "wheelevent")) + return Ref<Event> { WheelEvent::createForBindings() }; + +#if ENABLE(DEVICE_ORIENTATION) + if (equalLettersIgnoringASCIICase(type, "devicemotionevent")) + return Ref<Event> { DeviceMotionEvent::createForBindings() }; + if (equalLettersIgnoringASCIICase(type, "deviceorientationevent")) + return Ref<Event> { DeviceOrientationEvent::createForBindings() }; +#endif + + return Exception { NOT_SUPPORTED_ERR }; +} + +bool Document::hasListenerTypeForEventType(PlatformEvent::Type eventType) const +{ + switch (eventType) { + case PlatformEvent::MouseForceChanged: + return m_listenerTypes & Document::FORCECHANGED_LISTENER; + case PlatformEvent::MouseForceDown: + return m_listenerTypes & Document::FORCEDOWN_LISTENER; + case PlatformEvent::MouseForceUp: + return m_listenerTypes & Document::FORCEUP_LISTENER; + case PlatformEvent::MouseScroll: + return m_listenerTypes & Document::SCROLL_LISTENER; + default: + return false; + } } void Document::addListenerTypeIfNeeded(const AtomicString& eventType) @@ -3743,11 +4219,11 @@ void Document::addListenerTypeIfNeeded(const AtomicString& eventType) addListenerType(DOMCHARACTERDATAMODIFIED_LISTENER); else if (eventType == eventNames().overflowchangedEvent) addListenerType(OVERFLOWCHANGED_LISTENER); - else if (eventType == eventNames().webkitAnimationStartEvent) + else if (eventType == eventNames().webkitAnimationStartEvent || eventType == eventNames().animationstartEvent) addListenerType(ANIMATIONSTART_LISTENER); - else if (eventType == eventNames().webkitAnimationEndEvent) + else if (eventType == eventNames().webkitAnimationEndEvent || eventType == eventNames().animationendEvent) addListenerType(ANIMATIONEND_LISTENER); - else if (eventType == eventNames().webkitAnimationIterationEvent) + else if (eventType == eventNames().webkitAnimationIterationEvent || eventType == eventNames().animationiterationEvent) addListenerType(ANIMATIONITERATION_LISTENER); else if (eventType == eventNames().webkitTransitionEndEvent || eventType == eventNames().transitionendEvent) addListenerType(TRANSITIONEND_LISTENER); @@ -3755,6 +4231,14 @@ void Document::addListenerTypeIfNeeded(const AtomicString& eventType) addListenerType(BEFORELOAD_LISTENER); else if (eventType == eventNames().scrollEvent) addListenerType(SCROLL_LISTENER); + else if (eventType == eventNames().webkitmouseforcewillbeginEvent) + addListenerType(FORCEWILLBEGIN_LISTENER); + else if (eventType == eventNames().webkitmouseforcechangedEvent) + addListenerType(FORCECHANGED_LISTENER); + else if (eventType == eventNames().webkitmouseforcedownEvent) + addListenerType(FORCEDOWN_LISTENER); + else if (eventType == eventNames().webkitmouseforceupEvent) + addListenerType(FORCEUP_LISTENER); } CSSStyleDeclaration* Document::getOverrideStyle(Element*, const String&) @@ -3769,7 +4253,7 @@ HTMLFrameOwnerElement* Document::ownerElement() const return frame()->ownerElement(); } -String Document::cookie(ExceptionCode& ec) const +ExceptionOr<String> Document::cookie() { if (page() && !page()->settings().cookieEnabled()) return String(); @@ -3778,37 +4262,38 @@ String Document::cookie(ExceptionCode& ec) const // INVALID_STATE_ERR exception on getting if the Document has no // browsing context. - if (!securityOrigin()->canAccessCookies()) { - ec = SECURITY_ERR; - return String(); - } + if (!securityOrigin().canAccessCookies()) + return Exception { SECURITY_ERR }; URL cookieURL = this->cookieURL(); if (cookieURL.isEmpty()) return String(); - return cookies(this, cookieURL); + if (!isDOMCookieCacheValid()) + setCachedDOMCookies(cookies(*this, cookieURL)); + + return String { cachedDOMCookies() }; } -void Document::setCookie(const String& value, ExceptionCode& ec) +ExceptionOr<void> Document::setCookie(const String& value) { if (page() && !page()->settings().cookieEnabled()) - return; + return { }; // FIXME: The HTML5 DOM spec states that this attribute can raise an // INVALID_STATE_ERR exception on setting if the Document has no // browsing context. - if (!securityOrigin()->canAccessCookies()) { - ec = SECURITY_ERR; - return; - } + if (!securityOrigin().canAccessCookies()) + return Exception { SECURITY_ERR }; URL cookieURL = this->cookieURL(); if (cookieURL.isEmpty()) - return; + return { }; - setCookies(this, cookieURL, value); + invalidateDOMCookieCache(); + setCookies(*this, cookieURL, value); + return { }; } String Document::referrer() const @@ -3818,80 +4303,94 @@ String Document::referrer() const return String(); } +String Document::origin() const +{ + return securityOrigin().toString(); +} + String Document::domain() const { - return securityOrigin()->domain(); + return securityOrigin().domain(); } -void Document::setDomain(const String& newDomain, ExceptionCode& ec) +ExceptionOr<void> Document::setDomain(const String& newDomain) { - if (SchemeRegistry::isDomainRelaxationForbiddenForURLScheme(securityOrigin()->protocol())) { - ec = SECURITY_ERR; - return; - } + if (SchemeRegistry::isDomainRelaxationForbiddenForURLScheme(securityOrigin().protocol())) + return Exception { SECURITY_ERR }; // Both NS and IE specify that changing the domain is only allowed when // the new domain is a suffix of the old domain. // FIXME: We should add logging indicating why a domain was not allowed. + String oldDomain = domain(); + // If the new domain is the same as the old domain, still call - // securityOrigin()->setDomainForDOM. This will change the + // securityOrigin().setDomainForDOM. This will change the // security check behavior. For example, if a page loaded on port 8000 // assigns its current domain using document.domain, the page will // allow other pages loaded on different ports in the same domain that // have also assigned to access this page. - if (equalIgnoringCase(domain(), newDomain)) { - securityOrigin()->setDomainFromDOM(newDomain); - return; + if (equalIgnoringASCIICase(oldDomain, newDomain)) { + securityOrigin().setDomainFromDOM(newDomain); + return { }; } - int oldLength = domain().length(); - int newLength = newDomain.length(); // e.g. newDomain = webkit.org (10) and domain() = www.webkit.org (14) - if (newLength >= oldLength) { - ec = SECURITY_ERR; - return; - } + unsigned oldLength = oldDomain.length(); + unsigned newLength = newDomain.length(); + if (newLength >= oldLength) + return Exception { SECURITY_ERR }; - String test = domain(); - // Check that it's a subdomain, not e.g. "ebkit.org" - if (test[oldLength - newLength - 1] != '.') { - ec = SECURITY_ERR; - return; - } + auto ipAddressSetting = settings().treatIPAddressAsDomain() ? OriginAccessEntry::TreatIPAddressAsDomain : OriginAccessEntry::TreatIPAddressAsIPAddress; + OriginAccessEntry accessEntry { securityOrigin().protocol(), newDomain, OriginAccessEntry::AllowSubdomains, ipAddressSetting }; + if (!accessEntry.matchesOrigin(securityOrigin())) + return Exception { SECURITY_ERR }; - // Now test is "webkit.org" from domain() - // and we check that it's the same thing as newDomain - test.remove(0, oldLength - newLength); - if (test != newDomain) { - ec = SECURITY_ERR; - return; - } + if (oldDomain[oldLength - newLength - 1] != '.') + return Exception { SECURITY_ERR }; + if (StringView { oldDomain }.substring(oldLength - newLength) != newDomain) + return Exception { SECURITY_ERR }; - securityOrigin()->setDomainFromDOM(newDomain); + securityOrigin().setDomainFromDOM(newDomain); + return { }; } // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-lastmodified -String Document::lastModified() const +String Document::lastModified() { - DateComponents date; - bool foundDate = false; - if (m_frame) { - String httpLastModified; - if (DocumentLoader* documentLoader = loader()) - httpLastModified = documentLoader->response().httpHeaderField("Last-Modified"); - if (!httpLastModified.isEmpty()) { - date.setMillisecondsSinceEpochForDateTime(parseDate(httpLastModified)); - foundDate = true; - } - } + using namespace std::chrono; + std::optional<system_clock::time_point> dateTime; + if (m_frame && loader()) + dateTime = loader()->response().lastModified(); + // FIXME: If this document came from the file system, the HTML5 - // specificiation tells us to read the last modification date from the file + // specification tells us to read the last modification date from the file // system. - if (!foundDate) - date.setMillisecondsSinceEpochForDateTime(currentTimeMS()); - return String::format("%02d/%02d/%04d %02d:%02d:%02d", date.month() + 1, date.monthDay(), date.fullYear(), date.hour(), date.minute(), date.second()); + if (!dateTime) { + dateTime = system_clock::now(); +#if ENABLE(WEB_REPLAY) + auto& cursor = inputCursor(); + if (cursor.isCapturing()) + cursor.appendInput<DocumentLastModifiedDate>(duration_cast<milliseconds>(dateTime.value().time_since_epoch()).count()); + else if (cursor.isReplaying()) { + if (auto* input = cursor.fetchInput<DocumentLastModifiedDate>()) + dateTime = system_clock::time_point(milliseconds(static_cast<long long>(input->fallbackValue()))); + } +#endif + } + + auto ctime = system_clock::to_time_t(dateTime.value()); + auto localDateTime = std::localtime(&ctime); + return String::format("%02d/%02d/%04d %02d:%02d:%02d", localDateTime->tm_mon + 1, localDateTime->tm_mday, 1900 + localDateTime->tm_year, localDateTime->tm_hour, localDateTime->tm_min, localDateTime->tm_sec); +} + +void Document::setCookieURL(const URL& url) +{ + if (m_cookieURL == url) + return; + m_cookieURL = url; + invalidateDOMCookieCache(); } static bool isValidNameNonASCII(const LChar* characters, unsigned length) @@ -3964,68 +4463,57 @@ bool Document::isValidName(const String& name) return isValidNameNonASCII(characters, length); } -bool Document::parseQualifiedName(const String& qualifiedName, String& prefix, String& localName, ExceptionCode& ec) +ExceptionOr<std::pair<AtomicString, AtomicString>> Document::parseQualifiedName(const String& qualifiedName) { unsigned length = qualifiedName.length(); - if (!length) { - ec = INVALID_CHARACTER_ERR; - return false; - } + if (!length) + return Exception { INVALID_CHARACTER_ERR }; bool nameStart = true; bool sawColon = false; - int colonPos = 0; + unsigned colonPosition = 0; - const UChar* s = qualifiedName.deprecatedCharacters(); - for (unsigned i = 0; i < length;) { + for (unsigned i = 0; i < length; ) { UChar32 c; - U16_NEXT(s, i, length, c) + U16_NEXT(qualifiedName, i, length, c) if (c == ':') { - if (sawColon) { - ec = NAMESPACE_ERR; - return false; // multiple colons: not allowed - } + if (sawColon) + return Exception { NAMESPACE_ERR }; nameStart = true; sawColon = true; - colonPos = i - 1; + colonPosition = i - 1; } else if (nameStart) { - if (!isValidNameStart(c)) { - ec = INVALID_CHARACTER_ERR; - return false; - } + if (!isValidNameStart(c)) + return Exception { INVALID_CHARACTER_ERR }; nameStart = false; } else { - if (!isValidNamePart(c)) { - ec = INVALID_CHARACTER_ERR; - return false; - } + if (!isValidNamePart(c)) + return Exception { INVALID_CHARACTER_ERR }; } } - if (!sawColon) { - prefix = String(); - localName = qualifiedName; - } else { - prefix = qualifiedName.substring(0, colonPos); - if (prefix.isEmpty()) { - ec = NAMESPACE_ERR; - return false; - } - localName = qualifiedName.substring(colonPos + 1); - } + if (!sawColon) + return std::pair<AtomicString, AtomicString> { { }, { qualifiedName } }; - if (localName.isEmpty()) { - ec = NAMESPACE_ERR; - return false; - } + if (!colonPosition || length - colonPosition <= 1) + return Exception { NAMESPACE_ERR }; - return true; + return std::pair<AtomicString, AtomicString> { StringView { qualifiedName }.substring(0, colonPosition).toAtomicString(), StringView { qualifiedName }.substring(colonPosition + 1).toAtomicString() }; +} + +ExceptionOr<QualifiedName> Document::parseQualifiedName(const AtomicString& namespaceURI, const String& qualifiedName) +{ + auto parseResult = parseQualifiedName(qualifiedName); + if (parseResult.hasException()) + return parseResult.releaseException(); + auto parsedPieces = parseResult.releaseReturnValue(); + return QualifiedName { parsedPieces.first, parsedPieces.second, namespaceURI }; } -void Document::setDecoder(PassRefPtr<TextResourceDecoder> decoder) +void Document::setDecoder(RefPtr<TextResourceDecoder>&& decoder) { - m_decoder = decoder; + m_decoder = WTFMove(decoder); } URL Document::completeURL(const String& url, const URL& baseURLOverride) const @@ -4046,20 +4534,18 @@ URL Document::completeURL(const String& url) const return completeURL(url, m_baseURL); } -void Document::setInPageCache(bool flag) +void Document::setPageCacheState(PageCacheState state) { - if (m_inPageCache == flag) + if (m_pageCacheState == state) return; - m_inPageCache = flag; + m_pageCacheState = state; FrameView* v = view(); Page* page = this->page(); - if (page) - page->lockAllOverlayScrollbarsToHidden(flag); - - if (flag) { + switch (state) { + case InPageCache: if (v) { // FIXME: There is some scrolling related work that needs to happen whenever a page goes into the // page cache and similar work that needs to occur when it comes out. This is where we do the work @@ -4073,74 +4559,110 @@ void Document::setInPageCache(bool flag) v->resetScrollbarsAndClearContentsSize(); if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) scrollingCoordinator->clearStateTree(); - } else - v->resetScrollbars(); + } } + +#if ENABLE(POINTER_LOCK) + exitPointerLock(); +#endif + + styleScope().clearResolver(); + clearSelectorQueryCache(); m_styleRecalcTimer.stop(); - } else { + + clearSharedObjectPool(); + break; + case NotInPageCache: if (childNeedsStyleRecalc()) scheduleStyleRecalc(); + break; + case AboutToEnterPageCache: + break; } } void Document::documentWillBecomeInactive() { -#if USE(ACCELERATED_COMPOSITING) if (renderView()) renderView()->setIsInWindow(false); -#endif } -void Document::documentWillSuspendForPageCache() +void Document::suspend(ActiveDOMObject::ReasonForSuspension reason) { + if (m_isSuspended) + return; + documentWillBecomeInactive(); - HashSet<Element*>::iterator end = m_documentSuspensionCallbackElements.end(); - for (HashSet<Element*>::iterator i = m_documentSuspensionCallbackElements.begin(); i != end; ++i) - (*i)->documentWillSuspendForPageCache(); + for (auto* element : m_documentSuspensionCallbackElements) + element->prepareForDocumentSuspension(); #ifndef NDEBUG // Clear the update flag to be able to check if the viewport arguments update // is dispatched, after the document is restored from the page cache. m_didDispatchViewportPropertiesChanged = false; #endif + + ASSERT(page()); + page()->lockAllOverlayScrollbarsToHidden(true); + + if (RenderView* view = renderView()) { + if (view->usesCompositing()) + view->compositor().cancelCompositingLayerUpdate(); + } + + suspendScheduledTasks(reason); + + ASSERT(m_frame); + m_frame->clearTimers(); + + m_visualUpdatesAllowed = false; + m_visualUpdatesSuppressionTimer.stop(); + + m_isSuspended = true; } -void Document::documentDidResumeFromPageCache() +void Document::resume(ActiveDOMObject::ReasonForSuspension reason) { + if (!m_isSuspended) + return; + Vector<Element*> elements; copyToVector(m_documentSuspensionCallbackElements, elements); - Vector<Element*>::iterator end = elements.end(); - for (Vector<Element*>::iterator i = elements.begin(); i != end; ++i) - (*i)->documentDidResumeFromPageCache(); + for (auto* element : elements) + element->resumeFromDocumentSuspension(); -#if USE(ACCELERATED_COMPOSITING) if (renderView()) renderView()->setIsInWindow(true); -#endif ASSERT(page()); page()->lockAllOverlayScrollbarsToHidden(false); ASSERT(m_frame); m_frame->loader().client().dispatchDidBecomeFrameset(isFrameSet()); + m_frame->animation().resumeAnimationsForDocument(this); + + resumeScheduledTasks(reason); + + m_visualUpdatesAllowed = true; + + m_isSuspended = false; } -void Document::registerForPageCacheSuspensionCallbacks(Element* e) +void Document::registerForDocumentSuspensionCallbacks(Element* e) { m_documentSuspensionCallbackElements.add(e); } -void Document::unregisterForPageCacheSuspensionCallbacks(Element* e) +void Document::unregisterForDocumentSuspensionCallbacks(Element* e) { m_documentSuspensionCallbackElements.remove(e); } void Document::mediaVolumeDidChange() { - HashSet<Element*>::iterator end = m_mediaVolumeCallbackElements.end(); - for (HashSet<Element*>::iterator i = m_mediaVolumeCallbackElements.begin(); i != end; ++i) - (*i)->mediaVolumeDidChange(); + for (auto* element : m_mediaVolumeCallbackElements) + element->mediaVolumeDidChange(); } void Document::registerForMediaVolumeCallbacks(Element* e) @@ -4153,17 +4675,39 @@ void Document::unregisterForMediaVolumeCallbacks(Element* e) m_mediaVolumeCallbackElements.remove(e); } +bool Document::audioPlaybackRequiresUserGesture() const +{ + if (DocumentLoader* loader = this->loader()) { + // If an audio playback policy was set during navigation, use it. If not, use the global settings. + AutoplayPolicy policy = loader->autoplayPolicy(); + if (policy != AutoplayPolicy::Default) + return policy == AutoplayPolicy::AllowWithoutSound || policy == AutoplayPolicy::Deny; + } + + return settings().audioPlaybackRequiresUserGesture(); +} + +bool Document::videoPlaybackRequiresUserGesture() const +{ + if (DocumentLoader* loader = this->loader()) { + // If a video playback policy was set during navigation, use it. If not, use the global settings. + AutoplayPolicy policy = loader->autoplayPolicy(); + if (policy != AutoplayPolicy::Default) + return policy == AutoplayPolicy::Deny; + } + + return settings().videoPlaybackRequiresUserGesture(); +} + void Document::storageBlockingStateDidChange() { - if (Settings* settings = this->settings()) - securityOrigin()->setStorageBlockingPolicy(settings->storageBlockingPolicy()); + securityOrigin().setStorageBlockingPolicy(settings().storageBlockingPolicy()); } void Document::privateBrowsingStateDidChange() { - HashSet<Element*>::iterator end = m_privateBrowsingStateChangedElements.end(); - for (HashSet<Element*>::iterator it = m_privateBrowsingStateChangedElements.begin(); it != end; ++it) - (*it)->privateBrowsingStateDidChange(); + for (auto* element : m_privateBrowsingStateChangedElements) + element->privateBrowsingStateDidChange(); } void Document::registerForPrivateBrowsingStateChangedCallbacks(Element* e) @@ -4177,10 +4721,11 @@ void Document::unregisterForPrivateBrowsingStateChangedCallbacks(Element* e) } #if ENABLE(VIDEO_TRACK) + void Document::registerForCaptionPreferencesChangedCallbacks(Element* e) { if (page()) - page()->group().captionPreferences()->setInterestedInCaptionPreferenceChanges(); + page()->group().captionPreferences().setInterestedInCaptionPreferenceChanges(); m_captionPreferencesChangedElements.add(e); } @@ -4192,10 +4737,46 @@ void Document::unregisterForCaptionPreferencesChangedCallbacks(Element* e) void Document::captionPreferencesChanged() { - HashSet<Element*>::iterator end = m_captionPreferencesChangedElements.end(); - for (HashSet<Element*>::iterator it = m_captionPreferencesChangedElements.begin(); it != end; ++it) - (*it)->captionPreferencesChanged(); + for (auto* element : m_captionPreferencesChangedElements) + element->captionPreferencesChanged(); } + +#endif + +#if ENABLE(MEDIA_CONTROLS_SCRIPT) + +void Document::registerForPageScaleFactorChangedCallbacks(HTMLMediaElement* element) +{ + m_pageScaleFactorChangedElements.add(element); +} + +void Document::unregisterForPageScaleFactorChangedCallbacks(HTMLMediaElement* element) +{ + m_pageScaleFactorChangedElements.remove(element); +} + +void Document::pageScaleFactorChangedAndStable() +{ + for (HTMLMediaElement* mediaElement : m_pageScaleFactorChangedElements) + mediaElement->pageScaleFactorChanged(); +} + +void Document::registerForUserInterfaceLayoutDirectionChangedCallbacks(HTMLMediaElement& element) +{ + m_userInterfaceLayoutDirectionChangedElements.add(&element); +} + +void Document::unregisterForUserInterfaceLayoutDirectionChangedCallbacks(HTMLMediaElement& element) +{ + m_userInterfaceLayoutDirectionChangedElements.remove(&element); +} + +void Document::userInterfaceLayoutDirectionChanged() +{ + for (auto* mediaElement : m_userInterfaceLayoutDirectionChangedElements) + mediaElement->userInterfaceLayoutDirectionChanged(); +} + #endif void Document::setShouldCreateRenderers(bool f) @@ -4224,6 +4805,7 @@ static Editor::Command command(Document* document, const String& commandName, bo bool Document::execCommand(const String& commandName, bool userInterface, const String& value) { + EventQueueScope eventQueueScope; return command(this, commandName, userInterface).execute(value); } @@ -4252,9 +4834,8 @@ String Document::queryCommandValue(const String& commandName) return command(this, commandName).value(); } -void Document::pushCurrentScript(PassRefPtr<HTMLScriptElement> newCurrentScript) +void Document::pushCurrentScript(HTMLScriptElement* newCurrentScript) { - ASSERT(newCurrentScript); m_currentScriptStack.append(newCurrentScript); } @@ -4269,7 +4850,7 @@ void Document::popCurrentScript() void Document::applyXSLTransform(ProcessingInstruction* pi) { RefPtr<XSLTProcessor> processor = XSLTProcessor::create(); - processor->setXSLStyleSheet(static_cast<XSLStyleSheet*>(pi->sheet())); + processor->setXSLStyleSheet(downcast<XSLStyleSheet>(pi->sheet())); String resultMIMEType; String newSource; String resultEncoding; @@ -4278,12 +4859,11 @@ void Document::applyXSLTransform(ProcessingInstruction* pi) // FIXME: If the transform failed we should probably report an error (like Mozilla does). Frame* ownerFrame = frame(); processor->createDocumentFromSource(newSource, resultEncoding, resultMIMEType, this, ownerFrame); - InspectorInstrumentation::frameDocumentUpdated(ownerFrame); } -void Document::setTransformSource(PassOwnPtr<TransformSource> source) +void Document::setTransformSource(std::unique_ptr<TransformSource> source) { - m_transformSource = source; + m_transformSource = WTFMove(source); } #endif @@ -4295,7 +4875,24 @@ void Document::setDesignMode(InheritedBool value) frame->document()->scheduleForcedStyleRecalc(); } -Document::InheritedBool Document::getDesignMode() const +String Document::designMode() const +{ + return inDesignMode() ? ASCIILiteral("on") : ASCIILiteral("off"); +} + +void Document::setDesignMode(const String& value) +{ + InheritedBool mode; + if (equalLettersIgnoringASCIICase(value, "on")) + mode = on; + else if (equalLettersIgnoringASCIICase(value, "off")) + mode = off; + else + mode = inherit; + setDesignMode(mode); +} + +auto Document::getDesignMode() const -> InheritedBool { return m_designMode; } @@ -4321,109 +4918,113 @@ Document* Document::parentDocument() const Document& Document::topDocument() const { + // FIXME: This special-casing avoids incorrectly determined top documents during the process + // of AXObjectCache teardown or notification posting for cached or being-destroyed documents. + if (pageCacheState() == NotInPageCache && !m_renderTreeBeingDestroyed) { + if (!m_frame) + return const_cast<Document&>(*this); + // This should always be non-null. + Document* mainFrameDocument = m_frame->mainFrame().document(); + return mainFrameDocument ? *mainFrameDocument : const_cast<Document&>(*this); + } + Document* document = const_cast<Document*>(this); - while (Element* element = document->ownerElement()) + while (HTMLFrameOwnerElement* element = document->ownerElement()) document = &element->document(); return *document; } -PassRefPtr<Attr> Document::createAttribute(const String& name, ExceptionCode& ec) +ExceptionOr<Ref<Attr>> Document::createAttribute(const String& name) { - return createAttributeNS(String(), name, ec, true); + return createAttributeNS({ }, isHTMLDocument() ? name.convertToASCIILowercase() : name, true); } -PassRefPtr<Attr> Document::createAttributeNS(const String& namespaceURI, const String& qualifiedName, ExceptionCode& ec, bool shouldIgnoreNamespaceChecks) +ExceptionOr<Ref<Attr>> Document::createAttributeNS(const AtomicString& namespaceURI, const String& qualifiedName, bool shouldIgnoreNamespaceChecks) { - String prefix, localName; - if (!parseQualifiedName(qualifiedName, prefix, localName, ec)) - return nullptr; - - QualifiedName qName(prefix, localName, namespaceURI); - - if (!shouldIgnoreNamespaceChecks && !hasValidNamespaceForAttributes(qName)) { - ec = NAMESPACE_ERR; - return nullptr; - } - - return Attr::create(*this, qName, emptyString()); + auto parseResult = parseQualifiedName(namespaceURI, qualifiedName); + if (parseResult.hasException()) + return parseResult.releaseException(); + QualifiedName parsedName { parseResult.releaseReturnValue() }; + if (!shouldIgnoreNamespaceChecks && !hasValidNamespaceForAttributes(parsedName)) + return Exception { NAMESPACE_ERR }; + return Attr::create(*this, parsedName, emptyString()); } -#if ENABLE(SVG) const SVGDocumentExtensions* Document::svgExtensions() { return m_svgExtensions.get(); } -SVGDocumentExtensions* Document::accessSVGExtensions() +SVGDocumentExtensions& Document::accessSVGExtensions() { if (!m_svgExtensions) - m_svgExtensions = adoptPtr(new SVGDocumentExtensions(this)); - return m_svgExtensions.get(); + m_svgExtensions = std::make_unique<SVGDocumentExtensions>(this); + return *m_svgExtensions; } bool Document::hasSVGRootNode() const { return documentElement() && documentElement()->hasTagName(SVGNames::svgTag); } -#endif -PassRefPtr<HTMLCollection> Document::ensureCachedCollection(CollectionType type) +template <CollectionType collectionType> +Ref<HTMLCollection> Document::ensureCachedCollection() { - return ensureRareData().ensureNodeLists().addCachedCollection<HTMLCollection>(*this, type); + return ensureRareData().ensureNodeLists().addCachedCollection<GenericCachedHTMLCollection<CollectionTypeTraits<collectionType>::traversalType>>(*this, collectionType); } -PassRefPtr<HTMLCollection> Document::images() +Ref<HTMLCollection> Document::images() { - return ensureCachedCollection(DocImages); + return ensureCachedCollection<DocImages>(); } -PassRefPtr<HTMLCollection> Document::applets() +Ref<HTMLCollection> Document::applets() { - return ensureCachedCollection(DocApplets); + return ensureCachedCollection<DocApplets>(); } -PassRefPtr<HTMLCollection> Document::embeds() +Ref<HTMLCollection> Document::embeds() { - return ensureCachedCollection(DocEmbeds); + return ensureCachedCollection<DocEmbeds>(); } -PassRefPtr<HTMLCollection> Document::plugins() +Ref<HTMLCollection> Document::plugins() { // This is an alias for embeds() required for the JS DOM bindings. - return ensureCachedCollection(DocEmbeds); + return ensureCachedCollection<DocEmbeds>(); } -PassRefPtr<HTMLCollection> Document::scripts() +Ref<HTMLCollection> Document::scripts() { - return ensureCachedCollection(DocScripts); + return ensureCachedCollection<DocScripts>(); } -PassRefPtr<HTMLCollection> Document::links() +Ref<HTMLCollection> Document::links() { - return ensureCachedCollection(DocLinks); + return ensureCachedCollection<DocLinks>(); } -PassRefPtr<HTMLCollection> Document::forms() +Ref<HTMLCollection> Document::forms() { - return ensureCachedCollection(DocForms); + return ensureCachedCollection<DocForms>(); } -PassRefPtr<HTMLCollection> Document::anchors() +Ref<HTMLCollection> Document::anchors() { - return ensureCachedCollection(DocAnchors); + return ensureCachedCollection<DocAnchors>(); } -PassRefPtr<HTMLCollection> Document::all() +Ref<HTMLCollection> Document::all() { return ensureRareData().ensureNodeLists().addCachedCollection<HTMLAllCollection>(*this, DocAll); } -PassRefPtr<HTMLCollection> Document::windowNamedItems(const AtomicString& name) +Ref<HTMLCollection> Document::windowNamedItems(const AtomicString& name) { return ensureRareData().ensureNodeLists().addCachedCollection<WindowNameCollection>(*this, WindowNamedItems, name); } -PassRefPtr<HTMLCollection> Document::documentNamedItems(const AtomicString& name) +Ref<HTMLCollection> Document::documentNamedItems(const AtomicString& name) { return ensureRareData().ensureNodeLists().addCachedCollection<DocumentNameCollection>(*this, DocumentNamedItems, name); } @@ -4436,14 +5037,14 @@ void Document::finishedParsing() #if ENABLE(WEB_TIMING) if (!m_documentTiming.domContentLoadedEventStart) - m_documentTiming.domContentLoadedEventStart = monotonicallyIncreasingTime(); + m_documentTiming.domContentLoadedEventStart = MonotonicTime::now(); #endif dispatchEvent(Event::create(eventNames().DOMContentLoadedEvent, true, false)); #if ENABLE(WEB_TIMING) if (!m_documentTiming.domContentLoadedEventEnd) - m_documentTiming.domContentLoadedEventEnd = monotonicallyIncreasingTime(); + m_documentTiming.domContentLoadedEventEnd = MonotonicTime::now(); #endif if (RefPtr<Frame> f = frame()) { @@ -4458,7 +5059,7 @@ void Document::finishedParsing() f->loader().finishedParsing(); - InspectorInstrumentation::domContentLoadedEventFired(f.get()); + InspectorInstrumentation::domContentLoadedEventFired(*f); } // Schedule dropping of the DocumentSharedObjectPool. We keep it alive for a while after parsing finishes @@ -4468,32 +5069,23 @@ void Document::finishedParsing() static const int timeToKeepSharedObjectPoolAliveAfterParsingFinishedInSeconds = 10; m_sharedObjectPoolClearTimer.startOneShot(timeToKeepSharedObjectPoolAliveAfterParsingFinishedInSeconds); - // Parser should have picked up all preloads by now - m_cachedResourceLoader->clearPreloads(); + // Parser should have picked up all speculative preloads by now + m_cachedResourceLoader->clearPreloads(CachedResourceLoader::ClearPreloadsMode::ClearSpeculativePreloads); } -void Document::sharedObjectPoolClearTimerFired(Timer<Document>&) +void Document::clearSharedObjectPool() { - m_sharedObjectPool.clear(); + m_sharedObjectPool = nullptr; + m_sharedObjectPoolClearTimer.stop(); } -void Document::didAccessStyleResolver() -{ - m_styleResolverThrowawayTimer.restart(); -} +#if ENABLE(TELEPHONE_NUMBER_DETECTION) -void Document::styleResolverThrowawayTimerFired(DeferrableOneShotTimer<Document>&) -{ - ASSERT(!m_inStyleRecalc); - clearStyleResolver(); -} +// FIXME: Find a better place for this code. -#if PLATFORM(IOS) -// FIXME: Find a better place for this functionality. bool Document::isTelephoneNumberParsingEnabled() const { - Settings* settings = this->settings(); - return settings && settings->telephoneNumberParsingEnabled() && m_isTelephoneNumberParsingAllowed; + return settings().telephoneNumberParsingEnabled() && m_isTelephoneNumberParsingAllowed; } void Document::setIsTelephoneNumberParsingAllowed(bool isTelephoneNumberParsingAllowed) @@ -4505,196 +5097,185 @@ bool Document::isTelephoneNumberParsingAllowed() const { return m_isTelephoneNumberParsingAllowed; } + #endif -PassRefPtr<XPathExpression> Document::createExpression(const String& expression, - XPathNSResolver* resolver, - ExceptionCode& ec) +ExceptionOr<Ref<XPathExpression>> Document::createExpression(const String& expression, RefPtr<XPathNSResolver>&& resolver) { if (!m_xpathEvaluator) m_xpathEvaluator = XPathEvaluator::create(); - return m_xpathEvaluator->createExpression(expression, resolver, ec); + return m_xpathEvaluator->createExpression(expression, WTFMove(resolver)); } -PassRefPtr<XPathNSResolver> Document::createNSResolver(Node* nodeResolver) +Ref<XPathNSResolver> Document::createNSResolver(Node* nodeResolver) { if (!m_xpathEvaluator) m_xpathEvaluator = XPathEvaluator::create(); return m_xpathEvaluator->createNSResolver(nodeResolver); } -PassRefPtr<XPathResult> Document::evaluate(const String& expression, - Node* contextNode, - XPathNSResolver* resolver, - unsigned short type, - XPathResult* result, - ExceptionCode& ec) +ExceptionOr<Ref<XPathResult>> Document::evaluate(const String& expression, Node* contextNode, RefPtr<XPathNSResolver>&& resolver, unsigned short type, XPathResult* result) { if (!m_xpathEvaluator) m_xpathEvaluator = XPathEvaluator::create(); - return m_xpathEvaluator->evaluate(expression, contextNode, resolver, type, result, ec); -} - -const Vector<IconURL>& Document::shortcutIconURLs() -{ - // Include any icons where type = link, rel = "shortcut icon". - return iconURLs(Favicon); -} - -const Vector<IconURL>& Document::iconURLs(int iconTypesMask) -{ - m_iconURLs.clear(); - - if (!head() || !(head()->children())) - return m_iconURLs; - - RefPtr<HTMLCollection> children = head()->children(); - unsigned int length = children->length(); - for (unsigned int i = 0; i < length; ++i) { - Node* child = children->item(i); - if (!child->hasTagName(linkTag)) - continue; - HTMLLinkElement* linkElement = toHTMLLinkElement(child); - if (!(linkElement->iconType() & iconTypesMask)) - continue; - if (linkElement->href().isEmpty()) - continue; - - // Put it at the front to ensure that icons seen later take precedence as required by the spec. - IconURL newURL(linkElement->href(), linkElement->iconSizes(), linkElement->type(), linkElement->iconType()); - m_iconURLs.append(newURL); - } - - m_iconURLs.reverse(); - return m_iconURLs; -} - -void Document::addIconURL(const String& url, const String&, const String&, IconType iconType) -{ - if (url.isEmpty()) - return; - - Frame* f = frame(); - if (!f) - return; - - f->loader().didChangeIcons(iconType); + return m_xpathEvaluator->evaluate(expression, contextNode, WTFMove(resolver), type, result); } -static bool isEligibleForSeamless(Document* parent, Document* child) +static bool shouldInheritSecurityOriginFromOwner(const URL& url) { - // It should not matter what we return for the top-most document. - if (!parent) - return false; - if (parent->isSandboxed(SandboxSeamlessIframes)) - return false; - if (child->isSrcdocDocument()) - return true; - if (parent->securityOrigin()->canAccess(child->securityOrigin())) - return true; - return parent->securityOrigin()->canRequest(child->url()); + // Paraphrased from <https://html.spec.whatwg.org/multipage/browsers.html#origin> (8 July 2016) + // + // If a Document has the address "about:blank" + // The origin of the document is the origin it was assigned when its browsing context was created. + // If a Document has the address "about:srcdoc" + // The origin of the document is the origin of its parent document. + // + // Note: We generalize this to invalid URLs because we treat such URLs as about:blank. + // + return url.isEmpty() || equalIgnoringASCIICase(url.string(), blankURL()) || equalLettersIgnoringASCIICase(url.string(), "about:srcdoc"); } void Document::initSecurityContext() { if (haveInitializedSecurityOrigin()) { - ASSERT(securityOrigin()); + ASSERT(SecurityContext::securityOrigin()); return; } if (!m_frame) { // No source for a security context. // This can occur via document.implementation.createDocument(). - m_cookieURL = URL(ParsedURLString, emptyString()); - setSecurityOrigin(SecurityOrigin::createUnique()); - setContentSecurityPolicy(ContentSecurityPolicy::create(this)); + setCookieURL(URL(ParsedURLString, emptyString())); + setSecurityOriginPolicy(SecurityOriginPolicy::create(SecurityOrigin::createUnique())); + setContentSecurityPolicy(std::make_unique<ContentSecurityPolicy>(*this)); return; } // In the common case, create the security context from the currently // loading URL with a fresh content security policy. - m_cookieURL = m_url; + setCookieURL(m_url); enforceSandboxFlags(m_frame->loader().effectiveSandboxFlags()); -#if PLATFORM(IOS) - // On iOS we display attachments inline regardless of whether the response includes - // the HTTP header "Content-Disposition: attachment". So, we enforce a unique - // security origin for such documents. As an optimization, we don't need to parse - // the responde header (i.e. call ResourceResponse::isAttachment()) for a synthesized - // document because such documents cannot be an attachment. - if (!m_isSynthesized && m_frame->loader().activeDocumentLoader()->response().isAttachment()) - enforceSandboxFlags(SandboxOrigin); + if (shouldEnforceContentDispositionAttachmentSandbox()) + applyContentDispositionAttachmentSandbox(); + + setSecurityOriginPolicy(SecurityOriginPolicy::create(isSandboxed(SandboxOrigin) ? SecurityOrigin::createUnique() : SecurityOrigin::create(m_url))); + setContentSecurityPolicy(std::make_unique<ContentSecurityPolicy>(*this)); + + String overrideContentSecurityPolicy = m_frame->loader().client().overrideContentSecurityPolicy(); + if (!overrideContentSecurityPolicy.isNull()) + contentSecurityPolicy()->didReceiveHeader(overrideContentSecurityPolicy, ContentSecurityPolicyHeaderType::Enforce, ContentSecurityPolicy::PolicyFrom::API); + +#if USE(QUICK_LOOK) + if (shouldEnforceQuickLookSandbox()) + applyQuickLookSandbox(); #endif - setSecurityOrigin(isSandboxed(SandboxOrigin) ? SecurityOrigin::createUnique() : SecurityOrigin::create(m_url)); - setContentSecurityPolicy(ContentSecurityPolicy::create(this)); - - if (Settings* settings = this->settings()) { - if (!settings->webSecurityEnabled()) { - // Web security is turned off. We should let this document access every other document. This is used primary by testing - // harnesses for web sites. - securityOrigin()->grantUniversalAccess(); - } else if (securityOrigin()->isLocal()) { - if (settings->allowUniversalAccessFromFileURLs() || m_frame->loader().client().shouldForceUniversalAccessFromLocalURL(m_url)) { - // Some clients want local URLs to have universal access, but that setting is dangerous for other clients. - securityOrigin()->grantUniversalAccess(); - } else if (!settings->allowFileAccessFromFileURLs()) { - // Some clients want local URLs to have even tighter restrictions by default, and not be able to access other local files. - // FIXME 81578: The naming of this is confusing. Files with restricted access to other local files - // still can have other privileges that can be remembered, thereby not making them unique origins. - securityOrigin()->enforceFilePathSeparation(); - } + if (shouldEnforceHTTP09Sandbox()) { + String message = makeString("Sandboxing '", m_url.stringCenterEllipsizedToLength(), "' because it is using HTTP/0.9."); + addConsoleMessage(MessageSource::Security, MessageLevel::Error, message); + enforceSandboxFlags(SandboxScripts | SandboxPlugins); + } + + if (settings().needsStorageAccessFromFileURLsQuirk()) + securityOrigin().grantStorageAccessFromFileURLsQuirk(); + if (!settings().webSecurityEnabled()) { + // Web security is turned off. We should let this document access every other document. This is used primary by testing + // harnesses for web sites. + securityOrigin().grantUniversalAccess(); + } else if (securityOrigin().isLocal()) { + if (settings().allowUniversalAccessFromFileURLs() || m_frame->loader().client().shouldForceUniversalAccessFromLocalURL(m_url)) { + // Some clients want local URLs to have universal access, but that setting is dangerous for other clients. + securityOrigin().grantUniversalAccess(); + } else if (!settings().allowFileAccessFromFileURLs()) { + // Some clients want local URLs to have even tighter restrictions by default, and not be able to access other local files. + // FIXME 81578: The naming of this is confusing. Files with restricted access to other local files + // still can have other privileges that can be remembered, thereby not making them unique origins. + securityOrigin().enforceFilePathSeparation(); } - securityOrigin()->setStorageBlockingPolicy(settings->storageBlockingPolicy()); } + securityOrigin().setStorageBlockingPolicy(settings().storageBlockingPolicy()); Document* parentDocument = ownerElement() ? &ownerElement()->document() : nullptr; if (parentDocument && m_frame->loader().shouldTreatURLAsSrcdocDocument(url())) { m_isSrcdocDocument = true; setBaseURLOverride(parentDocument->baseURL()); } - - // FIXME: What happens if we inherit the security origin? This check may need to be later. - // <iframe seamless src="about:blank"> likely won't work as-is. - m_mayDisplaySeamlesslyWithParent = isEligibleForSeamless(parentDocument, this); + if (parentDocument) + setStrictMixedContentMode(parentDocument->isStrictMixedContentMode()); if (!shouldInheritSecurityOriginFromOwner(m_url)) return; // If we do not obtain a meaningful origin from the URL, then we try to // find one via the frame hierarchy. + Frame* parentFrame = m_frame->tree().parent(); + Frame* openerFrame = m_frame->loader().opener(); - Frame* ownerFrame = m_frame->tree().parent(); + Frame* ownerFrame = parentFrame; if (!ownerFrame) - ownerFrame = m_frame->loader().opener(); + ownerFrame = openerFrame; if (!ownerFrame) { didFailToInitializeSecurityOrigin(); return; } + Document* openerDocument = openerFrame ? openerFrame->document() : nullptr; + + // Per <http://www.w3.org/TR/upgrade-insecure-requests/>, new browsing contexts must inherit from an + // ongoing set of upgraded requests. When opening a new browsing context, we need to capture its + // existing upgrade request. Nested browsing contexts are handled during DocumentWriter::begin. + if (openerDocument) + contentSecurityPolicy()->inheritInsecureNavigationRequestsToUpgradeFromOpener(*openerDocument->contentSecurityPolicy()); + if (isSandboxed(SandboxOrigin)) { // If we're supposed to inherit our security origin from our owner, // but we're also sandboxed, the only thing we inherit is the ability // to load local resources. This lets about:blank iframes in file:// // URL documents load images and other resources from the file system. - if (ownerFrame->document()->securityOrigin()->canLoadLocalResources()) - securityOrigin()->grantLoadLocalResources(); + if (ownerFrame->document()->securityOrigin().canLoadLocalResources()) + securityOrigin().grantLoadLocalResources(); return; } - m_cookieURL = ownerFrame->document()->cookieURL(); + setCookieURL(ownerFrame->document()->cookieURL()); // We alias the SecurityOrigins to match Firefox, see Bug 15313 // https://bugs.webkit.org/show_bug.cgi?id=15313 - setSecurityOrigin(ownerFrame->document()->securityOrigin()); + setSecurityOriginPolicy(ownerFrame->document()->securityOriginPolicy()); +} + +bool Document::shouldInheritContentSecurityPolicyFromOwner() const +{ + ASSERT(m_frame); + if (shouldInheritSecurityOriginFromOwner(m_url)) + return true; + if (!isPluginDocument()) + return false; + if (m_frame->tree().parent()) + return true; + Frame* openerFrame = m_frame->loader().opener(); + if (!openerFrame) + return false; + return openerFrame->document()->securityOrigin().canAccess(securityOrigin()); } void Document::initContentSecurityPolicy() { - if (!m_frame->tree().parent() || (!shouldInheritSecurityOriginFromOwner(m_url) && !isPluginDocument())) - return; + // 1. Inherit Upgrade Insecure Requests + Frame* parentFrame = m_frame->tree().parent(); + if (parentFrame) + contentSecurityPolicy()->copyUpgradeInsecureRequestStateFrom(*parentFrame->document()->contentSecurityPolicy()); - contentSecurityPolicy()->copyStateFrom(m_frame->tree().parent()->document()->contentSecurityPolicy()); + // 2. Inherit Content Security Policy + if (!shouldInheritContentSecurityPolicyFromOwner()) + return; + Frame* ownerFrame = parentFrame; + if (!ownerFrame) + ownerFrame = m_frame->loader().opener(); + if (!ownerFrame) + return; + contentSecurityPolicy()->copyStateFrom(ownerFrame->document()->contentSecurityPolicy()); // Does not copy Upgrade Insecure Requests state. } bool Document::isContextThread() const @@ -4715,7 +5296,7 @@ void Document::updateURLForPushOrReplaceState(const URL& url) documentLoader->replaceRequestURLForSameDocumentNavigation(url); } -void Document::statePopped(PassRefPtr<SerializedScriptValue> stateObject) +void Document::statePopped(Ref<SerializedScriptValue>&& stateObject) { if (!frame()) return; @@ -4723,14 +5304,14 @@ void Document::statePopped(PassRefPtr<SerializedScriptValue> stateObject) // 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 (m_readyState == Complete) - enqueuePopstateEvent(stateObject); + dispatchPopstateEvent(WTFMove(stateObject)); else - m_pendingStateObject = stateObject; + m_pendingStateObject = WTFMove(stateObject); } -void Document::updateFocusAppearanceSoon(bool restorePreviousSelection) +void Document::updateFocusAppearanceSoon(SelectionRestorationMode mode) { - m_updateFocusAppearanceRestoresSelection = restorePreviousSelection; + m_updateFocusAppearanceRestoresSelection = mode; if (!m_updateFocusAppearanceTimer.isActive()) m_updateFocusAppearanceTimer.startOneShot(0); } @@ -4740,13 +5321,7 @@ void Document::cancelFocusAppearanceUpdate() m_updateFocusAppearanceTimer.stop(); } -void Document::resetHiddenFocusElementSoon() -{ - if (!m_resetHiddenFocusElementTimer.isActive() && m_focusedElement) - m_resetHiddenFocusElementTimer.startOneShot(0); -} - -void Document::updateFocusAppearanceTimerFired(Timer<Document>&) +void Document::updateFocusAppearanceTimerFired() { Element* element = focusedElement(); if (!element) @@ -4757,15 +5332,6 @@ void Document::updateFocusAppearanceTimerFired(Timer<Document>&) element->updateFocusAppearance(m_updateFocusAppearanceRestoresSelection); } -void Document::resetHiddenFocusElementTimer(Timer<Document>&) -{ - if (view() && view()->needsLayout()) - return; - - if (m_focusedElement && !m_focusedElement->isFocusable()) - setFocusedElement(nullptr); -} - void Document::attachRange(Range* range) { ASSERT(!m_ranges.contains(range)); @@ -4796,53 +5362,35 @@ HTMLCanvasElement* Document::getCSSCanvasElement(const String& name) return element.get(); } -#if ENABLE(IOS_TEXT_AUTOSIZING) -void Document::addAutoSizingNode(Node* node, float candidateSize) +#if ENABLE(TEXT_AUTOSIZING) + +void Document::addAutoSizedNode(Text& node, float candidateSize) { - TextAutoSizingKey key(&node->renderer()->style(), &document()); - TextAutoSizingMap::AddResult result = m_textAutoSizedNodes.add(key, nullptr); - if (result.isNewEntry) - result.iterator->value = TextAutoSizingValue::create(); - result.iterator->value->addNode(node, candidateSize); + LOG(TextAutosizing, " addAutoSizedNode %p candidateSize=%f", &node, candidateSize); + auto addResult = m_textAutoSizedNodes.add<TextAutoSizingHashTranslator>(node.renderer()->style(), nullptr); + if (addResult.isNewEntry) + addResult.iterator->value = std::make_unique<TextAutoSizingValue>(); + addResult.iterator->value->addTextNode(node, candidateSize); } -void Document::validateAutoSizingNodes() +void Document::updateAutoSizedNodes() { - Vector<TextAutoSizingKey> nodesForRemoval; - for (auto it = m_textAutoSizedNodes.begin(), end = m_textAutoSizedNodes.end(); it != end; ++it) { - RefPtr<TextAutoSizingValue> value = it->value; - // Update all the nodes in the collection to reflect the new - // candidate size. - if (!value) - continue; - - value->adjustNodeSizes(); - if (!value->numNodes()) - nodesForRemoval.append(it->key); - } - unsigned count = nodesForRemoval.size(); - for (unsigned i = 0; i < count; i++) - m_textAutoSizedNodes.remove(nodesForRemoval[i]); + m_textAutoSizedNodes.removeIf([](auto& keyAndValue) { + return keyAndValue.value->adjustTextNodeSizes() == TextAutoSizingValue::StillHasNodes::No; + }); } -void Document::resetAutoSizingNodes() +void Document::clearAutoSizedNodes() { - for (auto it = m_textAutoSizedNodes.begin(), end = m_textAutoSizedNodes.end(); it != end; ++it) { - RefPtr<TextAutoSizingValue> value = it->value; - if (value) - value->reset(); - } m_textAutoSizedNodes.clear(); } -#endif // ENABLE(IOS_TEXT_AUTOSIZING) +#endif // ENABLE(TEXT_AUTOSIZING) void Document::initDNSPrefetch() { - Settings* settings = this->settings(); - m_haveExplicitlyDisabledDNSPrefetch = false; - m_isDNSPrefetchEnabled = settings && settings->dnsPrefetchingEnabled() && securityOrigin()->protocol() == "http"; + m_isDNSPrefetchEnabled = settings().dnsPrefetchingEnabled() && securityOrigin().protocol() == "http"; // Inherit DNS prefetch opt-out from parent frame if (Document* parent = parentDocument()) { @@ -4853,7 +5401,7 @@ void Document::initDNSPrefetch() void Document::parseDNSPrefetchControlHeader(const String& dnsPrefetchControl) { - if (equalIgnoringCase(dnsPrefetchControl, "on") && !m_haveExplicitlyDisabledDNSPrefetch) { + if (equalLettersIgnoringASCIICase(dnsPrefetchControl, "on") && !m_haveExplicitlyDisabledDNSPrefetch) { m_isDNSPrefetchEnabled = true; return; } @@ -4865,7 +5413,7 @@ void Document::parseDNSPrefetchControlHeader(const String& dnsPrefetchControl) void Document::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, unsigned long requestIdentifier) { if (!isContextThread()) { - postTask(AddConsoleMessageTask::create(source, level, message)); + postTask(AddConsoleMessageTask(source, level, message)); return; } @@ -4873,79 +5421,50 @@ void Document::addConsoleMessage(MessageSource source, MessageLevel level, const page->console().addMessage(source, level, message, requestIdentifier, this); } -void Document::addMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, PassRefPtr<ScriptCallStack> callStack, JSC::ExecState* state, unsigned long requestIdentifier) +void Document::addMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, RefPtr<Inspector::ScriptCallStack>&& callStack, JSC::ExecState* state, unsigned long requestIdentifier) { if (!isContextThread()) { - postTask(AddConsoleMessageTask::create(source, level, message)); + postTask(AddConsoleMessageTask(source, level, message)); return; } if (Page* page = this->page()) - page->console().addMessage(source, level, message, sourceURL, lineNumber, columnNumber, callStack, state, requestIdentifier); + page->console().addMessage(source, level, message, sourceURL, lineNumber, columnNumber, WTFMove(callStack), state, requestIdentifier); } -SecurityOrigin* Document::topOrigin() const +void Document::postTask(Task&& task) { - return topDocument().securityOrigin(); -} - -struct PerformTaskContext { - WTF_MAKE_NONCOPYABLE(PerformTaskContext); WTF_MAKE_FAST_ALLOCATED; -public: - PerformTaskContext(WeakPtr<Document> document, PassOwnPtr<ScriptExecutionContext::Task> task) - : documentReference(document) - , task(task) - { - } - - WeakPtr<Document> documentReference; - OwnPtr<ScriptExecutionContext::Task> task; -}; - -void Document::didReceiveTask(void* untypedContext) -{ - ASSERT(isMainThread()); - - OwnPtr<PerformTaskContext> context = adoptPtr(static_cast<PerformTaskContext*>(untypedContext)); - ASSERT(context); - - Document* document = context->documentReference.get(); - if (!document) - return; + callOnMainThread([documentReference = m_weakFactory.createWeakPtr(), task = WTFMove(task)]() mutable { + ASSERT(isMainThread()); - Page* page = document->page(); - if ((page && page->defersLoading()) || !document->m_pendingTasks.isEmpty()) { - document->m_pendingTasks.append(context->task.release()); - return; - } - - context->task->performTask(document); -} + Document* document = documentReference.get(); + if (!document) + return; -void Document::postTask(PassOwnPtr<Task> task) -{ - callOnMainThread(didReceiveTask, new PerformTaskContext(m_weakFactory.createWeakPtr(), task)); + Page* page = document->page(); + if ((page && page->defersLoading() && document->activeDOMObjectsAreSuspended()) || !document->m_pendingTasks.isEmpty()) + document->m_pendingTasks.append(WTFMove(task)); + else + task.performTask(*document); + }); } -void Document::pendingTasksTimerFired(Timer<Document>&) +void Document::pendingTasksTimerFired() { - while (!m_pendingTasks.isEmpty()) { - OwnPtr<Task> task = m_pendingTasks[0].release(); - m_pendingTasks.remove(0); - task->performTask(this); - } + Vector<Task> pendingTasks = WTFMove(m_pendingTasks); + for (auto& task : pendingTasks) + task.performTask(*this); } void Document::suspendScheduledTasks(ActiveDOMObject::ReasonForSuspension reason) { -#if PLATFORM(IOS) if (m_scheduledTasksAreSuspended) { - ASSERT(reasonForSuspendingActiveDOMObjects() == ActiveDOMObject::DocumentWillBePaused); + // A page may subsequently suspend DOM objects, say as part of handling a scroll or zoom gesture, after the + // embedding client requested the page be suspended. We ignore such requests so long as the embedding client + // requested the suspension first. See <rdar://problem/13754896> for more details. + ASSERT(reasonForSuspendingActiveDOMObjects() == ActiveDOMObject::PageWillBeSuspended); return; } -#endif - - ASSERT(!m_scheduledTasksAreSuspended); suspendScriptedAnimationControllerCallbacks(); suspendActiveDOMObjects(reason); @@ -4982,73 +5501,41 @@ void Document::resumeScheduledTasks(ActiveDOMObject::ReasonForSuspension reason) void Document::suspendScriptedAnimationControllerCallbacks() { -#if ENABLE(REQUEST_ANIMATION_FRAME) if (m_scriptedAnimationController) m_scriptedAnimationController->suspend(); -#endif } void Document::resumeScriptedAnimationControllerCallbacks() { -#if ENABLE(REQUEST_ANIMATION_FRAME) if (m_scriptedAnimationController) m_scriptedAnimationController->resume(); -#endif } void Document::scriptedAnimationControllerSetThrottled(bool isThrottled) { -#if ENABLE(REQUEST_ANIMATION_FRAME) if (m_scriptedAnimationController) m_scriptedAnimationController->setThrottled(isThrottled); -#else - UNUSED_PARAM(isThrottled); -#endif } void Document::windowScreenDidChange(PlatformDisplayID displayID) { - UNUSED_PARAM(displayID); - -#if ENABLE(REQUEST_ANIMATION_FRAME) if (m_scriptedAnimationController) m_scriptedAnimationController->windowScreenDidChange(displayID); -#endif -#if USE(ACCELERATED_COMPOSITING) if (RenderView* view = renderView()) { if (view->usesCompositing()) view->compositor().windowScreenDidChange(displayID); } -#endif -} - -String Document::displayStringModifiedByEncoding(const String& str) const -{ - if (m_decoder) - return m_decoder->encoding().displayString(str.impl()); - return str; -} - -PassRefPtr<StringImpl> Document::displayStringModifiedByEncoding(PassRefPtr<StringImpl> str) const -{ - if (m_decoder) - return m_decoder->encoding().displayString(str); - return str; } -template <typename CharacterType> -void Document::displayBufferModifiedByEncodingInternal(CharacterType* buffer, unsigned len) const +String Document::displayStringModifiedByEncoding(const String& string) const { - if (m_decoder) - m_decoder->encoding().displayBuffer(buffer, len); + if (!m_decoder) + return string; + return String { string }.replace('\\', m_decoder->encoding().backslashAsCurrencySymbol()); } -// Generate definitions for both character types -template void Document::displayBufferModifiedByEncodingInternal<LChar>(LChar*, unsigned) const; -template void Document::displayBufferModifiedByEncodingInternal<UChar>(UChar*, unsigned) const; - -void Document::enqueuePageshowEvent(PageshowEventPersistence persisted) +void Document::dispatchPageshowEvent(PageshowEventPersistence persisted) { // FIXME: https://bugs.webkit.org/show_bug.cgi?id=36334 Pageshow event needs to fire asynchronously. dispatchWindowEvent(PageTransitionEvent::create(eventNames().pageshowEvent, persisted), this); @@ -5059,10 +5546,9 @@ void Document::enqueueHashchangeEvent(const String& oldURL, const String& newURL enqueueWindowEvent(HashChangeEvent::create(oldURL, newURL)); } -void Document::enqueuePopstateEvent(PassRefPtr<SerializedScriptValue> stateObject) +void Document::dispatchPopstateEvent(RefPtr<SerializedScriptValue>&& stateObject) { - // FIXME: https://bugs.webkit.org/show_bug.cgi?id=36202 Popstate event needs to fire asynchronously - dispatchWindowEvent(PopStateEvent::create(stateObject, m_domWindow ? m_domWindow->history() : nullptr)); + dispatchWindowEvent(PopStateEvent::create(WTFMove(stateObject), m_domWindow ? m_domWindow->history() : nullptr)); } void Document::addMediaCanStartListener(MediaCanStartListener* listener) @@ -5079,15 +5565,11 @@ void Document::removeMediaCanStartListener(MediaCanStartListener* listener) MediaCanStartListener* Document::takeAnyMediaCanStartListener() { - HashSet<MediaCanStartListener*>::iterator slot = m_mediaCanStartListeners.begin(); - if (slot == m_mediaCanStartListeners.end()) - return nullptr; - MediaCanStartListener* listener = *slot; - m_mediaCanStartListeners.remove(slot); - return listener; + return m_mediaCanStartListeners.takeAny(); } #if ENABLE(DEVICE_ORIENTATION) && PLATFORM(IOS) + DeviceMotionController* Document::deviceMotionController() const { return m_deviceMotionController.get(); @@ -5097,21 +5579,19 @@ DeviceOrientationController* Document::deviceOrientationController() const { return m_deviceOrientationController.get(); } + #endif #if ENABLE(FULLSCREEN_API) + bool Document::fullScreenIsAllowedForElement(Element* element) const { ASSERT(element); return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, element->document().ownerElement()); } -void Document::requestFullScreenForElement(Element* element, unsigned short flags, FullScreenCheckType checkType) +void Document::requestFullScreenForElement(Element* element, FullScreenCheckType checkType) { - // The Mozilla Full Screen API <https://wiki.mozilla.org/Gecko:FullScreenAPI> has different requirements - // for full screen mode, and do not have the concept of a full screen element stack. - bool inLegacyMozillaMode = (flags & Element::LEGACY_MOZILLA_REQUEST); - do { if (!element) element = documentElement(); @@ -5121,7 +5601,7 @@ void Document::requestFullScreenForElement(Element* element, unsigned short flag // node document: // The context object is not in a document. - if (!element->inDocument()) + if (!element->isConnected()) break; // The context object's node document, or an ancestor browsing context's document does not have @@ -5130,9 +5610,8 @@ void Document::requestFullScreenForElement(Element* element, unsigned short flag break; // The context object's node document fullscreen element stack is not empty and its top element - // is not an ancestor of the context object. (NOTE: Ignore this requirement if the request was - // made via the legacy Mozilla-style API.) - if (!m_fullScreenElementStack.isEmpty() && !m_fullScreenElementStack.last()->contains(element) && !inLegacyMozillaMode) + // is not an ancestor of the context object. + if (!m_fullScreenElementStack.isEmpty() && !m_fullScreenElementStack.last()->contains(element)) break; // A descendant browsing context's document has a non-empty fullscreen element stack. @@ -5143,7 +5622,7 @@ void Document::requestFullScreenForElement(Element* element, unsigned short flag break; } } - if (descendentHasNonEmptyStack && !inLegacyMozillaMode) + if (descendentHasNonEmptyStack) break; // This algorithm is not allowed to show a pop-up: @@ -5157,14 +5636,13 @@ void Document::requestFullScreenForElement(Element* element, unsigned short flag if (!page() || !page()->settings().fullScreenEnabled()) break; - if (!page()->chrome().client().supportsFullScreenForElement(element, flags & Element::ALLOW_KEYBOARD_INPUT)) { + bool hasKeyboardAccess = true; + if (!page()->chrome().client().supportsFullScreenForElement(*element, hasKeyboardAccess)) { // The new full screen API does not accept a "flags" parameter, so fall back to disallowing // keyboard input if the chrome client refuses to allow keyboard input. - if (!inLegacyMozillaMode && flags & Element::ALLOW_KEYBOARD_INPUT) { - flags &= ~Element::ALLOW_KEYBOARD_INPUT; - if (!page()->chrome().client().supportsFullScreenForElement(element, false)) - break; - } else + hasKeyboardAccess = false; + + if (!page()->chrome().client().supportsFullScreenForElement(*element, hasKeyboardAccess)) break; } @@ -5216,8 +5694,8 @@ void Document::requestFullScreenForElement(Element* element, unsigned short flag // 5. Return, and run the remaining steps asynchronously. // 6. Optionally, perform some animation. - m_areKeysEnabledInFullScreen = flags & Element::ALLOW_KEYBOARD_INPUT; - page()->chrome().client().enterFullScreenForElement(element); + m_areKeysEnabledInFullScreen = hasKeyboardAccess; + page()->chrome().client().enterFullScreenForElement(*element); // 7. Optionally, display a message indicating how the user can exit displaying the context object fullscreen. return; @@ -5268,9 +5746,9 @@ void Document::webkitExitFullscreen() // 4. For each descendant in descendants, empty descendant's fullscreen element stack, and queue a // task to fire an event named fullscreenchange with its bubbles attribute set to true on descendant. - for (Deque<RefPtr<Document>>::iterator i = descendants.begin(); i != descendants.end(); ++i) { - (*i)->clearFullscreenElementStack(); - addDocumentToFullScreenChangeEventQueue(i->get()); + for (auto& document : descendants) { + document->clearFullscreenElementStack(); + addDocumentToFullScreenChangeEventQueue(document.get()); } // 5. While doc is not null, run these substeps: @@ -5282,7 +5760,7 @@ void Document::webkitExitFullscreen() // If doc's fullscreen element stack is non-empty and the element now at the top is either // not in a document or its node document is not doc, repeat this substep. newTop = currentDoc->webkitFullscreenElement(); - if (newTop && (!newTop->inDocument() || &newTop->document() != currentDoc)) + if (newTop && (!newTop->isConnected() || &newTop->document() != currentDoc)) continue; // 2. Queue a task to fire an event named fullscreenchange with its bubbles attribute set to true @@ -5314,7 +5792,7 @@ void Document::webkitExitFullscreen() } // Otherwise, notify the chrome of the new full screen element. - page()->chrome().client().enterFullScreenForElement(newTop); + page()->chrome().client().enterFullScreenForElement(*newTop); } bool Document::webkitFullscreenEnabled() const @@ -5326,9 +5804,20 @@ bool Document::webkitFullscreenEnabled() const return isAttributeOnAllOwners(allowfullscreenAttr, webkitallowfullscreenAttr, ownerElement()); } +static void unwrapFullScreenRenderer(RenderFullScreen* fullScreenRenderer, Element* fullScreenElement) +{ + if (!fullScreenRenderer) + return; + bool requiresRenderTreeRebuild; + fullScreenRenderer->unwrapRenderer(requiresRenderTreeRebuild); + + if (requiresRenderTreeRebuild && fullScreenElement && fullScreenElement->parentElement()) + fullScreenElement->parentElement()->invalidateStyleAndRenderersForSubtree(); +} + void Document::webkitWillEnterFullScreenForElement(Element* element) { - if (!hasLivingRenderTree() || inPageCache()) + if (!hasLivingRenderTree() || pageCacheState() != NotInPageCache) return; ASSERT(element); @@ -5339,9 +5828,11 @@ void Document::webkitWillEnterFullScreenForElement(Element* element) ASSERT(page()->settings().fullScreenEnabled()); - if (m_fullScreenRenderer) - m_fullScreenRenderer->unwrapRenderer(); + unwrapFullScreenRenderer(m_fullScreenRenderer, m_fullScreenElement.get()); + if (element) + element->willBecomeFullscreenElement(); + m_fullScreenElement = element; #if USE(NATIVE_FULLSCREEN_VIDEO) @@ -5354,17 +5845,17 @@ void Document::webkitWillEnterFullScreenForElement(Element* element) // a box will have a frameRect. The placeholder will be created in setFullScreenRenderer() // during layout. auto renderer = m_fullScreenElement->renderer(); - bool shouldCreatePlaceholder = renderer && renderer->isBox(); + bool shouldCreatePlaceholder = is<RenderBox>(renderer); if (shouldCreatePlaceholder) { - m_savedPlaceholderFrameRect = toRenderBox(renderer)->frameRect(); - m_savedPlaceholderRenderStyle = RenderStyle::clone(&renderer->style()); + m_savedPlaceholderFrameRect = downcast<RenderBox>(*renderer).frameRect(); + m_savedPlaceholderRenderStyle = RenderStyle::clonePtr(renderer->style()); } if (m_fullScreenElement != documentElement()) RenderFullScreen::wrapRenderer(renderer, renderer ? renderer->parent() : nullptr, *this); m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(true); - + recalcStyle(Style::Force); } @@ -5373,7 +5864,7 @@ void Document::webkitDidEnterFullScreenForElement(Element*) if (!m_fullScreenElement) return; - if (!hasLivingRenderTree() || inPageCache()) + if (!hasLivingRenderTree() || pageCacheState() != NotInPageCache) return; m_fullScreenElement->didBecomeFullscreenElement(); @@ -5386,7 +5877,7 @@ void Document::webkitWillExitFullScreenForElement(Element*) if (!m_fullScreenElement) return; - if (!hasLivingRenderTree() || inPageCache()) + if (!hasLivingRenderTree() || pageCacheState() != NotInPageCache) return; m_fullScreenElement->willStopBeingFullscreenElement(); @@ -5397,40 +5888,37 @@ void Document::webkitDidExitFullScreenForElement(Element*) if (!m_fullScreenElement) return; - if (!hasLivingRenderTree() || inPageCache()) + if (!hasLivingRenderTree() || pageCacheState() != NotInPageCache) return; m_fullScreenElement->setContainsFullScreenElementOnAncestorsCrossingFrameBoundaries(false); m_areKeysEnabledInFullScreen = false; - - if (m_fullScreenRenderer) - m_fullScreenRenderer->unwrapRenderer(); - if (m_fullScreenElement->parentNode()) - m_fullScreenElement->parentNode()->setNeedsStyleRecalc(ReconstructRenderTree); + unwrapFullScreenRenderer(m_fullScreenRenderer, m_fullScreenElement.get()); m_fullScreenElement = nullptr; scheduleForcedStyleRecalc(); - + // When webkitCancelFullScreen is called, we call webkitExitFullScreen on the topDocument(). That // means that the events will be queued there. So if we have no events here, start the timer on // the exiting document. bool eventTargetQueuesEmpty = m_fullScreenChangeEventTargetQueue.isEmpty() && m_fullScreenErrorEventTargetQueue.isEmpty(); Document& exitingDocument = eventTargetQueuesEmpty ? topDocument() : *this; + exitingDocument.m_fullScreenChangeDelayTimer.startOneShot(0); } - + void Document::setFullScreenRenderer(RenderFullScreen* renderer) { if (renderer == m_fullScreenRenderer) return; if (renderer && m_savedPlaceholderRenderStyle) - renderer->createPlaceholder(m_savedPlaceholderRenderStyle.releaseNonNull(), m_savedPlaceholderFrameRect); + renderer->createPlaceholder(WTFMove(m_savedPlaceholderRenderStyle), m_savedPlaceholderFrameRect); else if (renderer && m_fullScreenRenderer && m_fullScreenRenderer->placeholder()) { RenderBlock* placeholder = m_fullScreenRenderer->placeholder(); - renderer->createPlaceholder(RenderStyle::clone(&placeholder->style()), placeholder->frameRect()); + renderer->createPlaceholder(RenderStyle::clonePtr(placeholder->style()), placeholder->frameRect()); } if (m_fullScreenRenderer) @@ -5445,12 +5933,12 @@ void Document::fullScreenRendererDestroyed() m_fullScreenRenderer = nullptr; } -void Document::fullScreenChangeDelayTimerFired(Timer<Document>&) +void Document::fullScreenChangeDelayTimerFired() { // Since we dispatch events in this function, it's possible that the // document will be detached and GC'd. We protect it here to make sure we // can finish the function successfully. - Ref<Document> protect(*this); + Ref<Document> protectedThis(*this); Deque<RefPtr<Node>> changeQueue; m_fullScreenChangeEventTargetQueue.swap(changeQueue); Deque<RefPtr<Node>> errorQueue; @@ -5471,12 +5959,12 @@ void Document::dispatchFullScreenChangeOrErrorEvent(Deque<RefPtr<Node>>& queue, // If the element was removed from our tree, also message the documentElement. Since we may // have a document hierarchy, check that node isn't in another document. - if (!node->inDocument()) + if (!node->isConnected()) queue.append(documentElement()); #if ENABLE(VIDEO) - if (shouldNotifyMediaElement && isHTMLMediaElement(*node)) - toHTMLMediaElement(*node).enteredOrExitedFullscreen(); + if (shouldNotifyMediaElement && is<HTMLMediaElement>(*node)) + downcast<HTMLMediaElement>(*node).enteredOrExitedFullscreen(); #endif node->dispatchEvent(Event::create(eventName, true, false)); } @@ -5488,7 +5976,7 @@ void Document::fullScreenElementRemoved() webkitCancelFullScreen(); } -void Document::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOnly) +void Document::removeFullScreenElementOfSubtree(Node& node, bool amongChildrenOnly) { if (!m_fullScreenElement) return; @@ -5497,7 +5985,7 @@ void Document::removeFullScreenElementOfSubtree(Node* node, bool amongChildrenOn if (amongChildrenOnly) elementInSubtree = m_fullScreenElement->isDescendantOf(node); else - elementInSubtree = (m_fullScreenElement == node) || m_fullScreenElement->isDescendantOf(node); + elementInSubtree = (m_fullScreenElement == &node) || m_fullScreenElement->isDescendantOf(node); if (elementInSubtree) fullScreenElementRemoved(); @@ -5514,8 +6002,8 @@ void Document::setAnimatingFullScreen(bool flag) return; m_isAnimatingFullScreen = flag; - if (m_fullScreenElement && m_fullScreenElement->isDescendantOf(this)) { - m_fullScreenElement->setNeedsStyleRecalc(); + if (m_fullScreenElement && m_fullScreenElement->isDescendantOf(*this)) { + m_fullScreenElement->invalidateStyleForSubtree(); scheduleForcedStyleRecalc(); } } @@ -5548,30 +6036,23 @@ void Document::addDocumentToFullScreenChangeEventQueue(Document* doc) target = doc; m_fullScreenChangeEventTargetQueue.append(target); } + #endif #if ENABLE(POINTER_LOCK) -void Document::webkitExitPointerLock() + +void Document::exitPointerLock() { - if (!page()) + Page* page = this->page(); + if (!page) return; - if (Element* target = page()->pointerLockController()->element()) { - if (target->document() != this) + if (auto* target = page->pointerLockController().element()) { + if (&target->document() != this) return; } - page()->pointerLockController()->requestPointerUnlock(); + page->pointerLockController().requestPointerUnlock(); } -Element* Document::webkitPointerLockElement() const -{ - if (!page() || page()->pointerLockController()->lockPending()) - return nullptr; - if (Element* element = page()->pointerLockController()->element()) { - if (element->document() == this) - return element; - } - return nullptr; -} #endif void Document::decrementLoadEventDelayCount() @@ -5583,14 +6064,27 @@ void Document::decrementLoadEventDelayCount() m_loadEventDelayTimer.startOneShot(0); } -void Document::loadEventDelayTimerFired(Timer<Document>&) +void Document::loadEventDelayTimerFired() { - if (frame()) - frame()->loader().checkCompleted(); + checkCompleted(); } -#if ENABLE(REQUEST_ANIMATION_FRAME) -int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> callback) +void Document::checkCompleted() +{ + if (auto* frame = this->frame()) + frame->loader().checkCompleted(); +} + +double Document::monotonicTimestamp() const +{ + auto* loader = this->loader(); + if (!loader) + return 0; + + return loader->timing().secondsSinceStartTime(MonotonicTime::now()).seconds(); +} + +int Document::requestAnimationFrame(Ref<RequestAnimationFrameCallback>&& callback) { if (!m_scriptedAnimationController) { #if USE(REQUEST_ANIMATION_FRAME_DISPLAY_MONITOR) @@ -5605,7 +6099,7 @@ int Document::requestAnimationFrame(PassRefPtr<RequestAnimationFrameCallback> ca m_scriptedAnimationController->suspend(); } - return m_scriptedAnimationController->registerCallback(callback); + return m_scriptedAnimationController->registerCallback(WTFMove(callback)); } void Document::cancelAnimationFrame(int id) @@ -5615,11 +6109,11 @@ void Document::cancelAnimationFrame(int id) m_scriptedAnimationController->cancelCallback(id); } -void Document::serviceScriptedAnimations(double monotonicAnimationStartTime) +void Document::serviceScriptedAnimations(double timestamp) { if (!m_scriptedAnimationController) return; - m_scriptedAnimationController->serviceScriptedAnimations(monotonicAnimationStartTime); + m_scriptedAnimationController->serviceScriptedAnimations(timestamp); } void Document::clearScriptedAnimationController() @@ -5627,13 +6121,83 @@ void Document::clearScriptedAnimationController() // FIXME: consider using ActiveDOMObject. if (m_scriptedAnimationController) m_scriptedAnimationController->clearDocumentPointer(); - m_scriptedAnimationController.clear(); + m_scriptedAnimationController = nullptr; } + +void Document::sendWillRevealEdgeEventsIfNeeded(const IntPoint& oldPosition, const IntPoint& newPosition, const IntRect& visibleRect, const IntSize& contentsSize, Element* target) +{ + // For each edge (top, bottom, left and right), send the will reveal edge event for that direction + // if newPosition is at or beyond the notification point, if the scroll direction is heading in the + // direction of that edge point, and if oldPosition is before the notification point (which indicates + // that this is the first moment that we know we crossed the magic line). + +#if ENABLE(WILL_REVEAL_EDGE_EVENTS) + // FIXME: broken in RTL documents. + int willRevealBottomNotificationPoint = std::max(0, contentsSize.height() - 2 * visibleRect.height()); + int willRevealTopNotificationPoint = visibleRect.height(); + + // Bottom edge. + if (newPosition.y() >= willRevealBottomNotificationPoint && newPosition.y() > oldPosition.y() + && willRevealBottomNotificationPoint >= oldPosition.y()) { + Ref<Event> willRevealEvent = Event::create(eventNames().webkitwillrevealbottomEvent, false, false); + if (!target) + enqueueWindowEvent(WTFMove(willRevealEvent)); + else { + willRevealEvent->setTarget(target); + m_eventQueue.enqueueEvent(WTFMove(willRevealEvent)); + } + } + + // Top edge. + if (newPosition.y() <= willRevealTopNotificationPoint && newPosition.y() < oldPosition.y() + && willRevealTopNotificationPoint <= oldPosition.y()) { + Ref<Event> willRevealEvent = Event::create(eventNames().webkitwillrevealtopEvent, false, false); + if (!target) + enqueueWindowEvent(WTFMove(willRevealEvent)); + else { + willRevealEvent->setTarget(target); + m_eventQueue.enqueueEvent(WTFMove(willRevealEvent)); + } + } + + int willRevealRightNotificationPoint = std::max(0, contentsSize.width() - 2 * visibleRect.width()); + int willRevealLeftNotificationPoint = visibleRect.width(); + + // Right edge. + if (newPosition.x() >= willRevealRightNotificationPoint && newPosition.x() > oldPosition.x() + && willRevealRightNotificationPoint >= oldPosition.x()) { + Ref<Event> willRevealEvent = Event::create(eventNames().webkitwillrevealrightEvent, false, false); + if (!target) + enqueueWindowEvent(WTFMove(willRevealEvent)); + else { + willRevealEvent->setTarget(target); + m_eventQueue.enqueueEvent(WTFMove(willRevealEvent)); + } + } + + // Left edge. + if (newPosition.x() <= willRevealLeftNotificationPoint && newPosition.x() < oldPosition.x() + && willRevealLeftNotificationPoint <= oldPosition.x()) { + Ref<Event> willRevealEvent = Event::create(eventNames().webkitwillrevealleftEvent, false, false); + if (!target) + enqueueWindowEvent(WTFMove(willRevealEvent)); + else { + willRevealEvent->setTarget(target); + m_eventQueue.enqueueEvent(WTFMove(willRevealEvent)); + } + } +#else + UNUSED_PARAM(oldPosition); + UNUSED_PARAM(newPosition); + UNUSED_PARAM(visibleRect); + UNUSED_PARAM(contentsSize); + UNUSED_PARAM(target); #endif +} -#if !PLATFORM(IOS) -#if ENABLE(TOUCH_EVENTS) -PassRefPtr<Touch> Document::createTouch(DOMWindow* window, EventTarget* target, int identifier, int pageX, int pageY, int screenX, int screenY, int radiusX, int radiusY, float rotationAngle, float force, ExceptionCode&) const +#if ENABLE(TOUCH_EVENTS) && !PLATFORM(IOS) + +Ref<Touch> Document::createTouch(DOMWindow* window, EventTarget* target, int identifier, int pageX, int pageY, int screenX, int screenY, int radiusX, int radiusY, float rotationAngle, float force) const { // FIXME: It's not clear from the documentation at // http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html @@ -5642,124 +6206,206 @@ PassRefPtr<Touch> Document::createTouch(DOMWindow* window, EventTarget* target, Frame* frame = window ? window->frame() : this->frame(); return Touch::create(frame, target, identifier, screenX, screenY, pageX, pageY, radiusX, radiusY, rotationAngle, force); } + #endif -#endif // !PLATFORM(IOS) -static void wheelEventHandlerCountChanged(Document* document) +void Document::wheelEventHandlersChanged() { - Page* page = document->page(); + Page* page = this->page(); if (!page) return; - pageWheelEventHandlerCountChanged(*page); + if (FrameView* frameView = view()) { + if (ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator()) + scrollingCoordinator->frameViewEventTrackingRegionsChanged(*frameView); + } - ScrollingCoordinator* scrollingCoordinator = page->scrollingCoordinator(); - if (!scrollingCoordinator) - return; + bool haveHandlers = m_wheelEventTargets && !m_wheelEventTargets->isEmpty(); + page->chrome().client().wheelEventHandlersChanged(haveHandlers); +} - FrameView* frameView = document->view(); - if (!frameView) - return; +void Document::didAddWheelEventHandler(Node& node) +{ + if (!m_wheelEventTargets) + m_wheelEventTargets = std::make_unique<EventTargetSet>(); + + m_wheelEventTargets->add(&node); - // FIXME: Why doesn't this need to be called in didBecomeCurrentDocumentInFrame? - scrollingCoordinator->frameViewWheelEventHandlerCountChanged(frameView); + wheelEventHandlersChanged(); + + if (Frame* frame = this->frame()) + DebugPageOverlays::didChangeEventHandlers(*frame); +} + +HttpEquivPolicy Document::httpEquivPolicy() const +{ + if (shouldEnforceContentDispositionAttachmentSandbox()) + return HttpEquivPolicy::DisabledByContentDispositionAttachmentSandbox; + if (page() && !page()->settings().httpEquivEnabled()) + return HttpEquivPolicy::DisabledBySettings; + return HttpEquivPolicy::Enabled; +} + +static bool removeHandlerFromSet(EventTargetSet& handlerSet, Node& node, EventHandlerRemoval removal) +{ + switch (removal) { + case EventHandlerRemoval::One: + return handlerSet.remove(&node); + case EventHandlerRemoval::All: + return handlerSet.removeAll(&node); + } + return false; } -void Document::didAddWheelEventHandler() +void Document::didRemoveWheelEventHandler(Node& node, EventHandlerRemoval removal) { - ++m_wheelEventHandlerCount; - wheelEventHandlerCountChanged(this); + if (!m_wheelEventTargets) + return; + + if (!removeHandlerFromSet(*m_wheelEventTargets, node, removal)) + return; + + wheelEventHandlersChanged(); + + if (Frame* frame = this->frame()) + DebugPageOverlays::didChangeEventHandlers(*frame); } -void Document::didRemoveWheelEventHandler() +unsigned Document::wheelEventHandlerCount() const { - ASSERT(m_wheelEventHandlerCount > 0); - --m_wheelEventHandlerCount; - wheelEventHandlerCountChanged(this); + if (!m_wheelEventTargets) + return 0; + + unsigned count = 0; + for (auto& handler : *m_wheelEventTargets) + count += handler.value; + + return count; } -void Document::didAddTouchEventHandler(Node* handler) +void Document::didAddTouchEventHandler(Node& handler) { #if ENABLE(TOUCH_EVENTS) - if (!m_touchEventTargets.get()) - m_touchEventTargets = adoptPtr(new TouchEventTargetSet); - m_touchEventTargets->add(handler); + if (!m_touchEventTargets) + m_touchEventTargets = std::make_unique<EventTargetSet>(); + + m_touchEventTargets->add(&handler); + if (Document* parent = parentDocument()) { - parent->didAddTouchEventHandler(this); + parent->didAddTouchEventHandler(*this); return; } - if (Page* page = this->page()) { - if (m_touchEventTargets->size() == 1) - page->chrome().client().needTouchEvents(true); - } #else UNUSED_PARAM(handler); #endif } -void Document::didRemoveTouchEventHandler(Node* handler) +void Document::didRemoveTouchEventHandler(Node& handler, EventHandlerRemoval removal) { #if ENABLE(TOUCH_EVENTS) - if (!m_touchEventTargets.get()) - return; - ASSERT(m_touchEventTargets->contains(handler)); - m_touchEventTargets->remove(handler); - if (Document* parent = parentDocument()) { - parent->didRemoveTouchEventHandler(this); + if (!m_touchEventTargets) return; - } - Page* page = this->page(); - if (!page) - return; - if (m_touchEventTargets->size()) - return; - for (const Frame* frame = &page->mainFrame(); frame; frame = frame->tree().traverseNext()) { - if (frame->document() && frame->document()->hasTouchEventHandlers()) - return; - } - page->chrome().client().needTouchEvents(false); + removeHandlerFromSet(*m_touchEventTargets, handler, removal); + + if (Document* parent = parentDocument()) + parent->didRemoveTouchEventHandler(*this); #else UNUSED_PARAM(handler); + UNUSED_PARAM(removal); #endif } -#if ENABLE(TOUCH_EVENTS) -void Document::didRemoveEventTargetNode(Node* handler) +void Document::didRemoveEventTargetNode(Node& handler) { +#if ENABLE(TOUCH_EVENTS) if (m_touchEventTargets) { - m_touchEventTargets->removeAll(handler); - if ((handler == this || m_touchEventTargets->isEmpty()) && parentDocument()) - parentDocument()->didRemoveEventTargetNode(this); + m_touchEventTargets->removeAll(&handler); + if ((&handler == this || m_touchEventTargets->isEmpty()) && parentDocument()) + parentDocument()->didRemoveEventTargetNode(*this); + } +#endif + + if (m_wheelEventTargets) { + m_wheelEventTargets->removeAll(&handler); + if ((&handler == this || m_wheelEventTargets->isEmpty()) && parentDocument()) + parentDocument()->didRemoveEventTargetNode(*this); } } + +unsigned Document::touchEventHandlerCount() const +{ +#if ENABLE(TOUCH_EVENTS) + if (!m_touchEventTargets) + return 0; + + unsigned count = 0; + for (auto& handler : *m_touchEventTargets) + count += handler.value; + + return count; +#else + return 0; #endif +} -void Document::resetLastHandledUserGestureTimestamp() +LayoutRect Document::absoluteEventHandlerBounds(bool& includesFixedPositionElements) { - m_lastHandledUserGestureTimestamp = monotonicallyIncreasingTime(); + includesFixedPositionElements = false; + if (RenderView* renderView = this->renderView()) + return renderView->documentRect(); + + return LayoutRect(); } -HTMLIFrameElement* Document::seamlessParentIFrame() const +Document::RegionFixedPair Document::absoluteRegionForEventTargets(const EventTargetSet* targets) { - if (!shouldDisplaySeamlesslyWithParent()) - return nullptr; + if (!targets) + return RegionFixedPair(Region(), false); + + Region targetRegion; + bool insideFixedPosition = false; + + for (auto& keyValuePair : *targets) { + LayoutRect rootRelativeBounds; - return toHTMLIFrameElement(ownerElement()); + if (is<Document>(keyValuePair.key)) { + Document* document = downcast<Document>(keyValuePair.key); + if (document == this) + rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); + else if (Element* element = document->ownerElement()) + rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); + } else if (is<Element>(keyValuePair.key)) { + Element* element = downcast<Element>(keyValuePair.key); + if (is<HTMLBodyElement>(element)) { + // For the body, just use the document bounds. + // The body may not cover this whole area, but it's OK for this region to be an overestimate. + rootRelativeBounds = absoluteEventHandlerBounds(insideFixedPosition); + } else + rootRelativeBounds = element->absoluteEventHandlerBounds(insideFixedPosition); + } + + if (!rootRelativeBounds.isEmpty()) + targetRegion.unite(Region(enclosingIntRect(rootRelativeBounds))); + } + + return RegionFixedPair(targetRegion, insideFixedPosition); } -bool Document::shouldDisplaySeamlesslyWithParent() const +void Document::updateLastHandledUserGestureTimestamp() { -#if ENABLE(IFRAME_SEAMLESS) - if (!RuntimeEnabledFeatures::sharedFeatures().seamlessIFramesEnabled()) - return false; - HTMLFrameOwnerElement* ownerElement = this->ownerElement(); - if (!ownerElement) - return false; - return m_mayDisplaySeamlesslyWithParent && ownerElement->hasTagName(iframeTag) && ownerElement->fastHasAttribute(seamlessAttr); -#else - return false; -#endif + m_lastHandledUserGestureTimestamp = monotonicallyIncreasingTime(); + ResourceLoadObserver::sharedObserver().logUserInteractionWithReducedTimeResolution(*this); +} + +void Document::startTrackingStyleRecalcs() +{ + m_styleRecalcCount = 0; +} + +unsigned Document::styleRecalcCount() const +{ + return m_styleRecalcCount; } DocumentLoader* Document::loader() const @@ -5778,27 +6424,27 @@ DocumentLoader* Document::loader() const } #if ENABLE(CSS_DEVICE_ADAPTATION) + IntSize Document::initialViewportSize() const { if (!view()) return IntSize(); return view()->initialViewportSize(); } + #endif -Element* eventTargetElementForDocument(Document* doc) +Element* eventTargetElementForDocument(Document* document) { - if (!doc) + if (!document) return nullptr; - Element* element = doc->focusedElement(); - if (!element && doc->isPluginDocument()) { - PluginDocument* pluginDocument = toPluginDocument(doc); - element = pluginDocument->pluginElement(); - } - if (!element && doc->isHTMLDocument()) - element = doc->body(); + Element* element = document->focusedElement(); + if (!element && is<PluginDocument>(*document)) + element = downcast<PluginDocument>(*document).pluginElement(); + if (!element && is<HTMLDocument>(*document)) + element = document->bodyOrFrameset(); if (!element) - element = doc->documentElement(); + element = document->documentElement(); return element; } @@ -5813,12 +6459,12 @@ void Document::adjustFloatQuadsForScrollAndAbsoluteZoomAndFrameScale(Vector<Floa inverseFrameScale = 1 / frame()->frameScaleFactor(); LayoutRect visibleContentRect = view()->visibleContentRect(); - for (size_t i = 0; i < quads.size(); ++i) { - quads[i].move(-visibleContentRect.x(), -visibleContentRect.y()); + for (auto& quad : quads) { + quad.move(-visibleContentRect.x(), -visibleContentRect.y()); if (zoom != 1) - quads[i].scale(1 / zoom, 1 / zoom); + quad.scale(1 / zoom); if (inverseFrameScale != 1) - quads[i].scale(inverseFrameScale, inverseFrameScale); + quad.scale(inverseFrameScale); } } @@ -5872,27 +6518,24 @@ static RenderElement* nearestCommonHoverAncestor(RenderElement* obj1, RenderElem return nullptr; } -void Document::updateHoverActiveState(const HitTestRequest& request, Element* innerElement, const PlatformMouseEvent* event, StyleResolverUpdateFlag updateFlag) +void Document::updateHoverActiveState(const HitTestRequest& request, Element* innerElement) { ASSERT(!request.readOnly()); Element* innerElementInDocument = innerElement; while (innerElementInDocument && &innerElementInDocument->document() != this) { - innerElementInDocument->document().updateHoverActiveState(request, innerElementInDocument, event); + innerElementInDocument->document().updateHoverActiveState(request, innerElementInDocument); innerElementInDocument = innerElementInDocument->document().ownerElement(); } Element* oldActiveElement = m_activeElement.get(); if (oldActiveElement && !request.active()) { // We are clearing the :active chain because the mouse has been released. - for (RenderElement* curr = oldActiveElement->renderer(); curr; curr = curr->parent()) { - Element* element = curr->element(); - if (!element) - continue; - element->setActive(false); - m_userActionElements.setInActiveChain(element, false); + for (Element* currentElement = oldActiveElement; currentElement; currentElement = currentElement->parentElementInComposedTree()) { + currentElement->setActive(false); + m_userActionElements.setInActiveChain(currentElement, false); } - m_activeElement.clear(); + m_activeElement = nullptr; } else { Element* newActiveElement = innerElementInDocument; if (!oldActiveElement && newActiveElement && request.active() && !request.touchMove()) { @@ -5917,7 +6560,7 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in // at the time the mouse went down. bool mustBeInActiveChain = request.active() && request.move(); - RefPtr<Element> oldHoveredElement = m_hoveredElement.release(); + RefPtr<Element> oldHoveredElement = WTFMove(m_hoveredElement); // A touch release does not set a new hover target; clearing the element we're working with // will clear the chain of hovered elements all the way to the top of the tree. @@ -5928,7 +6571,7 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in // If it hasn't, we do not need to do anything. Element* newHoveredElement = innerElementInDocument; while (newHoveredElement && !newHoveredElement->renderer()) - newHoveredElement = newHoveredElement->parentOrShadowHostElement(); + newHoveredElement = newHoveredElement->parentElementInComposedTree(); m_hoveredElement = newHoveredElement; @@ -5942,32 +6585,12 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in Vector<RefPtr<Element>, 32> elementsToRemoveFromChain; Vector<RefPtr<Element>, 32> elementsToAddToChain; - // mouseenter and mouseleave events are only dispatched if there is a capturing eventhandler on an ancestor - // or a normal eventhandler on the element itself (they don't bubble). - // This optimization is necessary since these events can cause O(n²) capturing event-handler checks. - bool hasCapturingMouseEnterListener = false; - bool hasCapturingMouseLeaveListener = false; - if (event && newHoveredElement != oldHoveredElement.get()) { - for (ContainerNode* curr = newHoveredElement; curr; curr = curr->parentOrShadowHostNode()) { - if (curr->hasCapturingEventListeners(eventNames().mouseenterEvent)) { - hasCapturingMouseEnterListener = true; - break; - } - } - for (ContainerNode* curr = oldHoveredElement.get(); curr; curr = curr->parentOrShadowHostNode()) { - if (curr->hasCapturingEventListeners(eventNames().mouseleaveEvent)) { - hasCapturingMouseLeaveListener = true; - break; - } - } - } - if (oldHoverObj != newHoverObj) { // If the old hovered element is not nil but it's renderer is, it was probably detached as part of the :hover style // (for instance by setting display:none in the :hover pseudo-class). In this case, the old hovered element (and its ancestors) // must be updated, to ensure it's normal style is re-applied. if (oldHoveredElement && !oldHoverObj) { - for (Element* element = oldHoveredElement.get(); element; element = element->parentElement()) { + for (Element* element = oldHoveredElement.get(); element; element = element->parentElementInComposedTree()) { if (!mustBeInActiveChain || element->inActiveChain()) elementsToRemoveFromChain.append(element); } @@ -5982,9 +6605,9 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in elementsToRemoveFromChain.append(element); } // Unset hovered nodes in sub frame documents if the old hovered node was a frame owner. - if (oldHoveredElement && oldHoveredElement->isFrameOwnerElement()) { - if (Document* contentDocument = toHTMLFrameOwnerElement(*oldHoveredElement).contentDocument()) - contentDocument->updateHoverActiveState(request, nullptr, event); + if (is<HTMLFrameOwnerElement>(oldHoveredElement.get())) { + if (Document* contentDocument = downcast<HTMLFrameOwnerElement>(*oldHoveredElement).contentDocument()) + contentDocument->updateHoverActiveState(request, nullptr); } } @@ -5997,41 +6620,31 @@ void Document::updateHoverActiveState(const HitTestRequest& request, Element* in elementsToAddToChain.append(element); } - size_t removeCount = elementsToRemoveFromChain.size(); - for (size_t i = 0; i < removeCount; ++i) { - elementsToRemoveFromChain[i]->setHovered(false); - if (event && (hasCapturingMouseLeaveListener || elementsToRemoveFromChain[i]->hasEventListeners(eventNames().mouseleaveEvent))) - elementsToRemoveFromChain[i]->dispatchMouseEvent(*event, eventNames().mouseleaveEvent, 0, newHoveredElement); - } + for (auto& element : elementsToRemoveFromChain) + element->setHovered(false); bool sawCommonAncestor = false; - for (size_t i = 0, size = elementsToAddToChain.size(); i < size; ++i) { + for (auto& element : elementsToAddToChain) { if (allowActiveChanges) - elementsToAddToChain[i]->setActive(true); - if (ancestor && elementsToAddToChain[i] == ancestor->element()) + element->setActive(true); + if (ancestor && element == ancestor->element()) sawCommonAncestor = true; if (!sawCommonAncestor) { // Elements after the common hover ancestor does not change hover state, but are iterated over because they may change active state. - elementsToAddToChain[i]->setHovered(true); - if (event && (hasCapturingMouseEnterListener || elementsToAddToChain[i]->hasEventListeners(eventNames().mouseenterEvent))) - elementsToAddToChain[i]->dispatchMouseEvent(*event, eventNames().mouseenterEvent, 0, oldHoveredElement.get()); + element->setHovered(true); } } - - ASSERT(updateFlag == RecalcStyleIfNeeded || updateFlag == DeferRecalcStyleIfNeeded); - if (updateFlag == RecalcStyleIfNeeded) - updateStyleIfNeeded(); } bool Document::haveStylesheetsLoaded() const { - return !m_styleSheetCollection.hasPendingSheets() || m_ignorePendingStylesheets; + return !styleScope().hasPendingSheets() || m_ignorePendingStylesheets; } Locale& Document::getCachedLocale(const AtomicString& locale) { AtomicString localeKey = locale; - if (locale.isEmpty() || !RuntimeEnabledFeatures::sharedFeatures().langAttributeAwareFormControlUIEnabled()) + if (locale.isEmpty() || !settings().langAttributeAwareFormControlUIEnabled()) localeKey = defaultLanguage(); LocaleIdentifierToLocaleMap::AddResult result = m_localeCache.add(localeKey, nullptr); if (result.isNewEntry) @@ -6039,31 +6652,27 @@ Locale& Document::getCachedLocale(const AtomicString& locale) return *(result.iterator->value); } -#if ENABLE(TEMPLATE_ELEMENT) -Document* Document::ensureTemplateDocument() +Document& Document::ensureTemplateDocument() { if (const Document* document = templateDocument()) - return const_cast<Document*>(document); + return const_cast<Document&>(*document); if (isHTMLDocument()) m_templateDocument = HTMLDocument::create(nullptr, blankURL()); else m_templateDocument = Document::create(nullptr, blankURL()); + m_templateDocument->setContextDocument(contextDocument()); m_templateDocument->setTemplateDocumentHost(this); // balanced in dtor. - return m_templateDocument.get(); + return *m_templateDocument; } -#endif -#if ENABLE(FONT_LOAD_EVENTS) -PassRefPtr<FontLoader> Document::fontloader() +Ref<FontFaceSet> Document::fonts() { - if (!m_fontloader) - m_fontloader = FontLoader::create(this); - return m_fontloader; + updateStyleIfNeeded(); + return fontSelector().fontFaceSet(); } -#endif float Document::deviceScaleFactor() const { @@ -6081,10 +6690,8 @@ void Document::didAssociateFormControl(Element* element) m_didAssociateFormControlsTimer.startOneShot(0); } -void Document::didAssociateFormControlsTimerFired(Timer<Document>& timer) +void Document::didAssociateFormControlsTimerFired() { - ASSERT_UNUSED(timer, &timer == &m_didAssociateFormControlsTimer); - if (!frame() || !frame()->page()) return; @@ -6095,6 +6702,27 @@ void Document::didAssociateFormControlsTimerFired(Timer<Document>& timer) m_associatedFormControls.clear(); } +void Document::setCachedDOMCookies(const String& cookies) +{ + ASSERT(!isDOMCookieCacheValid()); + m_cachedDOMCookies = cookies; + // The cookie cache is valid at most until we go back to the event loop. + m_cookieCacheExpiryTimer.startOneShot(0); +} + +void Document::invalidateDOMCookieCache() +{ + m_cookieCacheExpiryTimer.stop(); + m_cachedDOMCookies = String(); +} + +void Document::didLoadResourceSynchronously() +{ + // Synchronous resources loading can set cookies so we invalidate the cookies cache + // in this case, to be safe. + invalidateDOMCookieCache(); +} + void Document::ensurePlugInsInjectedScript(DOMWrapperWorld& world) { if (m_hasInjectedPlugInsScript) @@ -6103,11 +6731,273 @@ void Document::ensurePlugInsInjectedScript(DOMWrapperWorld& world) // Use the JS file provided by the Chrome client, or fallback to the default one. String jsString = page()->chrome().client().plugInExtraScript(); if (!jsString) - jsString = plugInsJavaScript; + jsString = String(plugInsJavaScript, sizeof(plugInsJavaScript)); - m_frame->mainFrame().script().evaluateInWorld(ScriptSourceCode(jsString), world); + frame()->script().evaluateInWorld(ScriptSourceCode(jsString), world); m_hasInjectedPlugInsScript = true; } +#if ENABLE(SUBTLE_CRYPTO) + +bool Document::wrapCryptoKey(const Vector<uint8_t>& key, Vector<uint8_t>& wrappedKey) +{ + Page* page = this->page(); + if (!page) + return false; + return page->chrome().client().wrapCryptoKey(key, wrappedKey); +} + +bool Document::unwrapCryptoKey(const Vector<uint8_t>& wrappedKey, Vector<uint8_t>& key) +{ + Page* page = this->page(); + if (!page) + return false; + return page->chrome().client().unwrapCryptoKey(wrappedKey, key); +} + +#endif // ENABLE(SUBTLE_CRYPTO) + +Element* Document::activeElement() +{ + if (Element* element = treeScope().focusedElementInScope()) + return element; + return bodyOrFrameset(); +} + +bool Document::hasFocus() const +{ + Page* page = this->page(); + if (!page || !page->focusController().isActive()) + return false; + if (Frame* focusedFrame = page->focusController().focusedFrame()) { + if (focusedFrame->tree().isDescendantOf(frame())) + return true; + } + return false; +} + +#if ENABLE(WEB_REPLAY) + +JSC::InputCursor& Document::inputCursor() +{ + return m_inputCursor; +} + +void Document::setInputCursor(Ref<InputCursor>&& cursor) +{ + m_inputCursor = WTFMove(cursor); +} + +#endif + +#if ENABLE(WIRELESS_PLAYBACK_TARGET) + +static uint64_t nextPlaybackTargetClientContextId() +{ + static uint64_t contextId = 0; + return ++contextId; +} + +void Document::addPlaybackTargetPickerClient(MediaPlaybackTargetClient& client) +{ + Page* page = this->page(); + if (!page) + return; + + // FIXME: change this back to an ASSERT once https://webkit.org/b/144970 is fixed. + if (m_clientToIDMap.contains(&client)) + return; + + uint64_t contextId = nextPlaybackTargetClientContextId(); + m_clientToIDMap.add(&client, contextId); + m_idToClientMap.add(contextId, &client); + page->addPlaybackTargetPickerClient(contextId); +} + +void Document::removePlaybackTargetPickerClient(MediaPlaybackTargetClient& client) +{ + auto it = m_clientToIDMap.find(&client); + if (it == m_clientToIDMap.end()) + return; + + uint64_t clientId = it->value; + m_idToClientMap.remove(clientId); + m_clientToIDMap.remove(it); + + Page* page = this->page(); + if (!page) + return; + page->removePlaybackTargetPickerClient(clientId); +} + +void Document::showPlaybackTargetPicker(MediaPlaybackTargetClient& client, bool isVideo) +{ + Page* page = this->page(); + if (!page) + return; + + auto it = m_clientToIDMap.find(&client); + if (it == m_clientToIDMap.end()) + return; + + page->showPlaybackTargetPicker(it->value, view()->lastKnownMousePosition(), isVideo); +} + +void Document::playbackTargetPickerClientStateDidChange(MediaPlaybackTargetClient& client, MediaProducer::MediaStateFlags state) +{ + Page* page = this->page(); + if (!page) + return; + + auto it = m_clientToIDMap.find(&client); + if (it == m_clientToIDMap.end()) + return; + + page->playbackTargetPickerClientStateDidChange(it->value, state); +} + +void Document::playbackTargetAvailabilityDidChange(uint64_t clientId, bool available) +{ + auto it = m_idToClientMap.find(clientId); + if (it == m_idToClientMap.end()) + return; + + it->value->externalOutputDeviceAvailableDidChange(available); +} + +void Document::setPlaybackTarget(uint64_t clientId, Ref<MediaPlaybackTarget>&& target) +{ + auto it = m_idToClientMap.find(clientId); + if (it == m_idToClientMap.end()) + return; + + it->value->setPlaybackTarget(target.copyRef()); +} + +void Document::setShouldPlayToPlaybackTarget(uint64_t clientId, bool shouldPlay) +{ + auto it = m_idToClientMap.find(clientId); + if (it == m_idToClientMap.end()) + return; + + it->value->setShouldPlayToPlaybackTarget(shouldPlay); +} + +#endif // ENABLE(WIRELESS_PLAYBACK_TARGET) + +#if ENABLE(MEDIA_SESSION) + +MediaSession& Document::defaultMediaSession() +{ + if (!m_defaultMediaSession) + m_defaultMediaSession = MediaSession::create(*scriptExecutionContext()); + return *m_defaultMediaSession; +} + +#endif + +ShouldOpenExternalURLsPolicy Document::shouldOpenExternalURLsPolicyToPropagate() const +{ + if (DocumentLoader* documentLoader = loader()) + return documentLoader->shouldOpenExternalURLsPolicyToPropagate(); + + return ShouldOpenExternalURLsPolicy::ShouldNotAllow; +} + +bool Document::shouldEnforceHTTP09Sandbox() const +{ + if (m_isSynthesized || !m_frame) + return false; + DocumentLoader* documentLoader = m_frame->loader().activeDocumentLoader(); + return documentLoader && documentLoader->response().isHTTP09(); +} + +#if USE(QUICK_LOOK) +bool Document::shouldEnforceQuickLookSandbox() const +{ + if (m_isSynthesized || !m_frame) + return false; + DocumentLoader* documentLoader = m_frame->loader().activeDocumentLoader(); + return documentLoader && documentLoader->response().isQuickLook(); +} + +void Document::applyQuickLookSandbox() +{ + static NeverDestroyed<String> quickLookCSP = makeString("default-src ", QLPreviewProtocol(), ": 'unsafe-inline'; base-uri 'none'; sandbox allow-scripts"); + ASSERT_WITH_SECURITY_IMPLICATION(contentSecurityPolicy()); + // The sandbox directive is only allowed if the policy is from an HTTP header. + contentSecurityPolicy()->didReceiveHeader(quickLookCSP, ContentSecurityPolicyHeaderType::Enforce, ContentSecurityPolicy::PolicyFrom::HTTPHeader); + + setReferrerPolicy(ReferrerPolicy::Never); +} +#endif + +bool Document::shouldEnforceContentDispositionAttachmentSandbox() const +{ + if (m_isSynthesized) + return false; + + bool contentDispositionAttachmentSandboxEnabled = settings().contentDispositionAttachmentSandboxEnabled(); + bool responseIsAttachment = false; + if (DocumentLoader* documentLoader = m_frame ? m_frame->loader().activeDocumentLoader() : nullptr) + responseIsAttachment = documentLoader->response().isAttachment(); + + return contentDispositionAttachmentSandboxEnabled && responseIsAttachment; +} + +void Document::applyContentDispositionAttachmentSandbox() +{ + ASSERT(shouldEnforceContentDispositionAttachmentSandbox()); + + setReferrerPolicy(ReferrerPolicy::Never); + if (!isMediaDocument()) + enforceSandboxFlags(SandboxAll); + else + enforceSandboxFlags(SandboxOrigin); +} + +void Document::addViewportDependentPicture(HTMLPictureElement& picture) +{ + m_viewportDependentPictures.add(&picture); +} + +void Document::removeViewportDependentPicture(HTMLPictureElement& picture) +{ + m_viewportDependentPictures.remove(&picture); +} + +const AtomicString& Document::dir() const +{ + auto* documentElement = this->documentElement(); + if (!is<HTMLHtmlElement>(documentElement)) + return nullAtom; + return downcast<HTMLHtmlElement>(*documentElement).dir(); +} + +void Document::setDir(const AtomicString& value) +{ + auto* documentElement = this->documentElement(); + if (is<HTMLHtmlElement>(documentElement)) + downcast<HTMLHtmlElement>(*documentElement).setDir(value); +} + +DOMSelection* Document::getSelection() +{ + return m_domWindow ? m_domWindow->getSelection() : nullptr; +} + +void Document::didInsertInDocumentShadowRoot(ShadowRoot& shadowRoot) +{ + ASSERT(shadowRoot.isConnected()); + ASSERT(!m_inDocumentShadowRoots.contains(&shadowRoot)); + m_inDocumentShadowRoots.add(&shadowRoot); +} + +void Document::didRemoveInDocumentShadowRoot(ShadowRoot& shadowRoot) +{ + ASSERT(m_inDocumentShadowRoots.contains(&shadowRoot)); + m_inDocumentShadowRoots.remove(&shadowRoot); +} + } // namespace WebCore |