| /* |
| * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
| * (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 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) |
| * Copyright (C) Research In Motion Limited 2010-2011. All rights reserved. |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Library General Public |
| * License as published by the Free Software Foundation; either |
| * version 2 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Library General Public License for more details. |
| * |
| * You should have received a copy of the GNU Library General Public License |
| * along with this library; see the file COPYING.LIB. If not, write to |
| * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| * Boston, MA 02110-1301, USA. |
| */ |
| |
| #include "core/dom/Document.h" |
| |
| #include "bindings/core/v8/DOMDataStore.h" |
| #include "bindings/core/v8/ExceptionMessages.h" |
| #include "bindings/core/v8/ExceptionState.h" |
| #include "bindings/core/v8/HTMLScriptElementOrSVGScriptElement.h" |
| #include "bindings/core/v8/Microtask.h" |
| #include "bindings/core/v8/ScriptController.h" |
| #include "bindings/core/v8/SourceLocation.h" |
| #include "bindings/core/v8/StringOrDictionary.h" |
| #include "bindings/core/v8/V0CustomElementConstructorBuilder.h" |
| #include "bindings/core/v8/V8DOMWrapper.h" |
| #include "bindings/core/v8/V8ElementCreationOptions.h" |
| #include "bindings/core/v8/V8PerIsolateData.h" |
| #include "bindings/core/v8/WindowProxy.h" |
| #include "core/HTMLElementFactory.h" |
| #include "core/HTMLElementTypeHelpers.h" |
| #include "core/HTMLNames.h" |
| #include "core/SVGElementFactory.h" |
| #include "core/SVGNames.h" |
| #include "core/XMLNSNames.h" |
| #include "core/XMLNames.h" |
| #include "core/animation/CompositorPendingAnimations.h" |
| #include "core/animation/DocumentAnimations.h" |
| #include "core/animation/DocumentTimeline.h" |
| #include "core/css/CSSFontSelector.h" |
| #include "core/css/CSSStyleDeclaration.h" |
| #include "core/css/CSSStyleSheet.h" |
| #include "core/css/CSSTiming.h" |
| #include "core/css/FontFaceSet.h" |
| #include "core/css/MediaQueryMatcher.h" |
| #include "core/css/PropertyRegistry.h" |
| #include "core/css/StylePropertySet.h" |
| #include "core/css/StyleSheetContents.h" |
| #include "core/css/StyleSheetList.h" |
| #include "core/css/invalidation/StyleInvalidator.h" |
| #include "core/css/parser/CSSParser.h" |
| #include "core/css/resolver/FontBuilder.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/css/resolver/StyleResolverStats.h" |
| #include "core/dom/AXObjectCache.h" |
| #include "core/dom/Attr.h" |
| #include "core/dom/CDATASection.h" |
| #include "core/dom/ClientRect.h" |
| #include "core/dom/Comment.h" |
| #include "core/dom/ContextFeatures.h" |
| #include "core/dom/DOMImplementation.h" |
| #include "core/dom/DocumentFragment.h" |
| #include "core/dom/DocumentParserTiming.h" |
| #include "core/dom/DocumentType.h" |
| #include "core/dom/Element.h" |
| #include "core/dom/ElementCreationOptions.h" |
| #include "core/dom/ElementDataCache.h" |
| #include "core/dom/ElementRegistrationOptions.h" |
| #include "core/dom/ElementTraversal.h" |
| #include "core/dom/ExceptionCode.h" |
| #include "core/dom/ExecutionContextTask.h" |
| #include "core/dom/FrameRequestCallback.h" |
| #include "core/dom/IntersectionObserverController.h" |
| #include "core/dom/LayoutTreeBuilderTraversal.h" |
| #include "core/dom/LiveNodeList.h" |
| #include "core/dom/MutationObserver.h" |
| #include "core/dom/NodeChildRemovalTracker.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/NodeFilter.h" |
| #include "core/dom/NodeIterator.h" |
| #include "core/dom/NodeRareData.h" |
| #include "core/dom/NodeTraversal.h" |
| #include "core/dom/NodeWithIndex.h" |
| #include "core/dom/NthIndexCache.h" |
| #include "core/dom/ProcessingInstruction.h" |
| #include "core/dom/ScriptRunner.h" |
| #include "core/dom/ScriptedAnimationController.h" |
| #include "core/dom/ScriptedIdleTaskController.h" |
| #include "core/dom/SelectorQuery.h" |
| #include "core/dom/StaticNodeList.h" |
| #include "core/dom/StyleChangeReason.h" |
| #include "core/dom/StyleEngine.h" |
| #include "core/dom/TaskRunnerHelper.h" |
| #include "core/dom/TouchList.h" |
| #include "core/dom/TransformSource.h" |
| #include "core/dom/TreeWalker.h" |
| #include "core/dom/VisitedLinkState.h" |
| #include "core/dom/XMLDocument.h" |
| #include "core/dom/custom/CustomElement.h" |
| #include "core/dom/custom/CustomElementDefinition.h" |
| #include "core/dom/custom/CustomElementDescriptor.h" |
| #include "core/dom/custom/CustomElementRegistry.h" |
| #include "core/dom/custom/V0CustomElementMicrotaskRunQueue.h" |
| #include "core/dom/custom/V0CustomElementRegistrationContext.h" |
| #include "core/dom/shadow/ElementShadow.h" |
| #include "core/dom/shadow/FlatTreeTraversal.h" |
| #include "core/dom/shadow/ShadowRoot.h" |
| #include "core/editing/EditingUtilities.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/markers/DocumentMarkerController.h" |
| #include "core/editing/serializers/Serialization.h" |
| #include "core/editing/spellcheck/SpellChecker.h" |
| #include "core/events/BeforeUnloadEvent.h" |
| #include "core/events/Event.h" |
| #include "core/events/EventFactory.h" |
| #include "core/events/EventListener.h" |
| #include "core/events/HashChangeEvent.h" |
| #include "core/events/PageTransitionEvent.h" |
| #include "core/events/ScopedEventQueue.h" |
| #include "core/events/VisualViewportResizeEvent.h" |
| #include "core/events/VisualViewportScrollEvent.h" |
| #include "core/fetch/ResourceFetcher.h" |
| #include "core/frame/DOMTimer.h" |
| #include "core/frame/DOMVisualViewport.h" |
| #include "core/frame/EventHandlerRegistry.h" |
| #include "core/frame/FrameConsole.h" |
| #include "core/frame/FrameHost.h" |
| #include "core/frame/FrameView.h" |
| #include "core/frame/History.h" |
| #include "core/frame/HostsUsingFeatures.h" |
| #include "core/frame/LocalDOMWindow.h" |
| #include "core/frame/LocalFrame.h" |
| #include "core/frame/PerformanceMonitor.h" |
| #include "core/frame/Settings.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "core/html/DocumentNameCollection.h" |
| #include "core/html/HTMLAllCollection.h" |
| #include "core/html/HTMLAnchorElement.h" |
| #include "core/html/HTMLBaseElement.h" |
| #include "core/html/HTMLBodyElement.h" |
| #include "core/html/HTMLCanvasElement.h" |
| #include "core/html/HTMLCollection.h" |
| #include "core/html/HTMLDialogElement.h" |
| #include "core/html/HTMLDocument.h" |
| #include "core/html/HTMLFrameOwnerElement.h" |
| #include "core/html/HTMLHeadElement.h" |
| #include "core/html/HTMLHtmlElement.h" |
| #include "core/html/HTMLIFrameElement.h" |
| #include "core/html/HTMLInputElement.h" |
| #include "core/html/HTMLLinkElement.h" |
| #include "core/html/HTMLMetaElement.h" |
| #include "core/html/HTMLScriptElement.h" |
| #include "core/html/HTMLTemplateElement.h" |
| #include "core/html/HTMLTitleElement.h" |
| #include "core/html/PluginDocument.h" |
| #include "core/html/WindowNameCollection.h" |
| #include "core/html/canvas/CanvasContextCreationAttributes.h" |
| #include "core/html/canvas/CanvasFontCache.h" |
| #include "core/html/canvas/CanvasRenderingContext.h" |
| #include "core/html/forms/FormController.h" |
| #include "core/html/imports/HTMLImportLoader.h" |
| #include "core/html/imports/HTMLImportsController.h" |
| #include "core/html/parser/HTMLDocumentParser.h" |
| #include "core/html/parser/HTMLParserIdioms.h" |
| #include "core/html/parser/NestingLevelIncrementer.h" |
| #include "core/html/parser/TextResourceDecoder.h" |
| #include "core/input/EventHandler.h" |
| #include "core/inspector/ConsoleMessage.h" |
| #include "core/inspector/InspectorInstrumentation.h" |
| #include "core/inspector/InspectorTraceEvents.h" |
| #include "core/inspector/MainThreadDebugger.h" |
| #include "core/layout/HitTestCanvasResult.h" |
| #include "core/layout/HitTestResult.h" |
| #include "core/layout/LayoutPart.h" |
| #include "core/layout/LayoutView.h" |
| #include "core/layout/TextAutosizer.h" |
| #include "core/layout/api/LayoutPartItem.h" |
| #include "core/layout/api/LayoutViewItem.h" |
| #include "core/layout/compositing/PaintLayerCompositor.h" |
| #include "core/loader/CookieJar.h" |
| #include "core/loader/DocumentLoader.h" |
| #include "core/loader/FrameFetchContext.h" |
| #include "core/loader/FrameLoader.h" |
| #include "core/loader/FrameLoaderClient.h" |
| #include "core/loader/ImageLoader.h" |
| #include "core/loader/NavigationScheduler.h" |
| #include "core/loader/PrerendererClient.h" |
| #include "core/loader/appcache/ApplicationCacheHost.h" |
| #include "core/observer/ResizeObserverController.h" |
| #include "core/page/ChromeClient.h" |
| #include "core/page/EventWithHitTestResults.h" |
| #include "core/page/FocusController.h" |
| #include "core/page/FrameTree.h" |
| #include "core/page/Page.h" |
| #include "core/page/PointerLockController.h" |
| #include "core/page/scrolling/RootScrollerController.h" |
| #include "core/page/scrolling/ScrollStateCallback.h" |
| #include "core/page/scrolling/ScrollingCoordinator.h" |
| #include "core/page/scrolling/SnapCoordinator.h" |
| #include "core/page/scrolling/TopDocumentRootScrollerController.h" |
| #include "core/svg/SVGDocumentExtensions.h" |
| #include "core/svg/SVGScriptElement.h" |
| #include "core/svg/SVGTitleElement.h" |
| #include "core/svg/SVGUseElement.h" |
| #include "core/timing/DOMWindowPerformance.h" |
| #include "core/timing/Performance.h" |
| #include "core/workers/SharedWorkerRepositoryClient.h" |
| #include "core/xml/parser/XMLDocumentParser.h" |
| #include "platform/DateComponents.h" |
| #include "platform/EventDispatchForbiddenScope.h" |
| #include "platform/Histogram.h" |
| #include "platform/InstanceCounters.h" |
| #include "platform/Language.h" |
| #include "platform/LengthFunctions.h" |
| #include "platform/PluginScriptForbiddenScope.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/ScriptForbiddenScope.h" |
| #include "platform/WebFrameScheduler.h" |
| #include "platform/instrumentation/tracing/TraceEvent.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/scroll/Scrollbar.h" |
| #include "platform/scroll/ScrollbarTheme.h" |
| #include "platform/text/PlatformLocale.h" |
| #include "platform/text/SegmentedString.h" |
| #include "platform/weborigin/OriginAccessEntry.h" |
| #include "platform/weborigin/SchemeRegistry.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "public/platform/InterfaceProvider.h" |
| #include "public/platform/Platform.h" |
| #include "public/platform/WebAddressSpace.h" |
| #include "public/platform/WebPrerenderingSupport.h" |
| #include "public/platform/WebScheduler.h" |
| #include "public/platform/modules/sensitive_input_visibility/sensitive_input_visibility_service.mojom-blink.h" |
| #include "wtf/AutoReset.h" |
| #include "wtf/CurrentTime.h" |
| #include "wtf/DateMath.h" |
| #include "wtf/Functional.h" |
| #include "wtf/HashFunctions.h" |
| #include "wtf/PtrUtil.h" |
| #include "wtf/StdLibExtras.h" |
| #include "wtf/text/CharacterNames.h" |
| #include "wtf/text/StringBuffer.h" |
| #include "wtf/text/TextEncodingRegistry.h" |
| #include <memory> |
| |
| using namespace WTF; |
| using namespace Unicode; |
| |
| #ifndef NDEBUG |
| using WeakDocumentSet = |
| blink::PersistentHeapHashSet<blink::WeakMember<blink::Document>>; |
| static WeakDocumentSet& liveDocumentSet(); |
| #endif |
| |
| namespace blink { |
| |
| using namespace HTMLNames; |
| |
| static const unsigned cMaxWriteRecursionDepth = 21; |
| |
| // This amount of time must have elapsed before we will even consider scheduling |
| // a layout without a delay. |
| // FIXME: For faster machines this value can really be lowered to 200. 250 is |
| // adequate, but a little high for dual G5s. :) |
| static const int cLayoutScheduleThreshold = 250; |
| |
| // DOM Level 2 says (letters added): |
| // |
| // a) Name start characters must have one of the categories Ll, Lu, Lo, Lt, Nl. |
| // b) Name characters other than Name-start characters must have one of the |
| // categories Mc, Me, Mn, Lm, or Nd. |
| // c) Characters in the compatibility area (i.e. with character code greater |
| // than #xF900 and less than #xFFFE) are not allowed in XML names. |
| // d) Characters which have a font or compatibility decomposition (i.e. those |
| // with a "compatibility formatting tag" in field 5 of the database -- marked |
| // by field 5 beginning with a "<") are not allowed. |
| // e) The following characters are treated as name-start characters rather than |
| // name characters, because the property file classifies them as Alphabetic: |
| // [#x02BB-#x02C1], #x0559, #x06E5, #x06E6. |
| // f) Characters #x20DD-#x20E0 are excluded (in accordance with Unicode, section |
| // 5.14). |
| // g) Character #x00B7 is classified as an extender, because the property list |
| // so identifies it. |
| // h) Character #x0387 is added as a name character, because #x00B7 is its |
| // canonical equivalent. |
| // i) Characters ':' and '_' are allowed as name-start characters. |
| // j) Characters '-' and '.' are allowed as name characters. |
| // |
| // It also contains complete tables. If we decide it's better, we could include |
| // those instead of the following code. |
| |
| static inline bool isValidNameStart(UChar32 c) { |
| // rule (e) above |
| if ((c >= 0x02BB && c <= 0x02C1) || c == 0x559 || c == 0x6E5 || c == 0x6E6) |
| return true; |
| |
| // rule (i) above |
| if (c == ':' || c == '_') |
| return true; |
| |
| // rules (a) and (f) above |
| const uint32_t nameStartMask = Letter_Lowercase | Letter_Uppercase | |
| Letter_Other | Letter_Titlecase | |
| Number_Letter; |
| if (!(Unicode::category(c) & nameStartMask)) |
| return false; |
| |
| // rule (c) above |
| if (c >= 0xF900 && c < 0xFFFE) |
| return false; |
| |
| // rule (d) above |
| CharDecompositionType decompType = decompositionType(c); |
| if (decompType == DecompositionFont || decompType == DecompositionCompat) |
| return false; |
| |
| return true; |
| } |
| |
| static inline bool isValidNamePart(UChar32 c) { |
| // rules (a), (e), and (i) above |
| if (isValidNameStart(c)) |
| return true; |
| |
| // rules (g) and (h) above |
| if (c == 0x00B7 || c == 0x0387) |
| return true; |
| |
| // rule (j) above |
| if (c == '-' || c == '.') |
| return true; |
| |
| // rules (b) and (f) above |
| const uint32_t otherNamePartMask = Mark_NonSpacing | Mark_Enclosing | |
| Mark_SpacingCombining | Letter_Modifier | |
| Number_DecimalDigit; |
| if (!(Unicode::category(c) & otherNamePartMask)) |
| return false; |
| |
| // rule (c) above |
| if (c >= 0xF900 && c < 0xFFFE) |
| return false; |
| |
| // rule (d) above |
| CharDecompositionType decompType = decompositionType(c); |
| if (decompType == DecompositionFont || decompType == DecompositionCompat) |
| return false; |
| |
| return true; |
| } |
| |
| static Widget* widgetForElement(const Element& focusedElement) { |
| LayoutObject* layoutObject = focusedElement.layoutObject(); |
| if (!layoutObject || !layoutObject->isLayoutPart()) |
| return 0; |
| return toLayoutPart(layoutObject)->widget(); |
| } |
| |
| static bool acceptsEditingFocus(const Element& element) { |
| DCHECK(hasEditableStyle(element)); |
| |
| return element.document().frame() && rootEditableElement(element); |
| } |
| |
| uint64_t Document::s_globalTreeVersion = 0; |
| |
| static bool s_threadedParsingEnabledForTesting = true; |
| |
| // This doesn't work with non-Document ExecutionContext. |
| static void runAutofocusTask(ExecutionContext* context) { |
| Document* document = toDocument(context); |
| if (Element* element = document->autofocusElement()) { |
| document->setAutofocusElement(0); |
| element->focus(); |
| } |
| } |
| |
| static void recordLoadReasonToHistogram(WouldLoadReason reason) { |
| DEFINE_STATIC_LOCAL( |
| EnumerationHistogram, unseenFrameHistogram, |
| ("Navigation.DeferredDocumentLoading.StatesV3", WouldLoadReasonEnd)); |
| unseenFrameHistogram.count(reason); |
| } |
| |
| Document::Document(const DocumentInit& initializer, |
| DocumentClassFlags documentClasses) |
| : ContainerNode(0, CreateDocument), |
| TreeScope(*this), |
| m_hasNodesWithPlaceholderStyle(false), |
| m_evaluateMediaQueriesOnStyleRecalc(false), |
| m_pendingSheetLayout(NoLayoutWithPendingSheets), |
| m_frame(initializer.frame()), |
| // TODO(dcheng): Why does this need both a LocalFrame and LocalDOMWindow |
| // pointer? |
| m_domWindow(m_frame ? m_frame->domWindow() : nullptr), |
| m_importsController(this, initializer.importsController()), |
| m_contextFeatures(ContextFeatures::defaultSwitch()), |
| m_wellFormed(false), |
| m_implementation(this, nullptr), |
| m_printing(NotPrinting), |
| m_paginatedForScreen(false), |
| m_compatibilityMode(NoQuirksMode), |
| m_compatibilityModeLocked(false), |
| m_hasAutofocused(false), |
| m_clearFocusedElementTimer( |
| TaskRunnerHelper::get(TaskType::UnspecedTimer, this), |
| this, |
| &Document::clearFocusedElementTimerFired), |
| m_domTreeVersion(++s_globalTreeVersion), |
| m_styleVersion(0), |
| m_listenerTypes(0), |
| m_mutationObserverTypes(0), |
| m_styleEngine(this, nullptr), |
| m_styleSheetList(this, nullptr), |
| m_visitedLinkState(VisitedLinkState::create(*this)), |
| m_visuallyOrdered(false), |
| m_readyState(Complete), |
| m_parsingState(FinishedParsing), |
| m_gotoAnchorNeededAfterStylesheetsLoad(false), |
| m_containsValidityStyleRules(false), |
| m_containsPlugins(false), |
| m_updateFocusAppearanceSelectionBahavior(SelectionBehaviorOnFocus::Reset), |
| m_ignoreDestructiveWriteCount(0), |
| m_throwOnDynamicMarkupInsertionCount(0), |
| m_markers(new DocumentMarkerController(*this)), |
| m_updateFocusAppearanceTimer( |
| TaskRunnerHelper::get(TaskType::UnspecedTimer, this), |
| this, |
| &Document::updateFocusAppearanceTimerFired), |
| m_cssTarget(nullptr), |
| m_loadEventProgress(LoadEventNotRun), |
| m_startTime(currentTime()), |
| m_scriptRunner(ScriptRunner::create(this)), |
| m_xmlVersion("1.0"), |
| m_xmlStandalone(StandaloneUnspecified), |
| m_hasXMLDeclaration(0), |
| m_designMode(false), |
| m_isRunningExecCommand(false), |
| m_hasReceivedUserGesture(false), |
| m_hasAnnotatedRegions(false), |
| m_annotatedRegionsDirty(false), |
| m_documentClasses(documentClasses), |
| m_isViewSource(false), |
| m_sawElementsInKnownNamespaces(false), |
| m_isSrcdocDocument(false), |
| m_isMobileDocument(false), |
| m_layoutView(0), |
| m_contextDocument(initializer.contextDocument()), |
| m_hasFullscreenSupplement(false), |
| m_loadEventDelayCount(0), |
| m_loadEventDelayTimer(TaskRunnerHelper::get(TaskType::Networking, this), |
| this, |
| &Document::loadEventDelayTimerFired), |
| m_pluginLoadingTimer( |
| TaskRunnerHelper::get(TaskType::UnspecedLoading, this), |
| this, |
| &Document::pluginLoadingTimerFired), |
| m_documentTiming(*this), |
| m_writeRecursionIsTooDeep(false), |
| m_writeRecursionDepth(0), |
| m_registrationContext(initializer.registrationContext(this)), |
| m_elementDataCacheClearTimer( |
| TaskRunnerHelper::get(TaskType::UnspecedTimer, this), |
| this, |
| &Document::elementDataCacheClearTimerFired), |
| m_timeline(DocumentTimeline::create(this)), |
| m_compositorPendingAnimations(new CompositorPendingAnimations(*this)), |
| m_templateDocumentHost(nullptr), |
| m_didAssociateFormControlsTimer( |
| TaskRunnerHelper::get(TaskType::UnspecedLoading, this), |
| this, |
| &Document::didAssociateFormControlsTimerFired), |
| m_timers(TaskRunnerHelper::get(TaskType::Timer, this)), |
| m_hasViewportUnits(false), |
| m_parserSyncPolicy(AllowAsynchronousParsing), |
| m_nodeCount(0), |
| m_wouldLoadReason(Created), |
| m_passwordCount(0), |
| m_engagementLevel(mojom::blink::EngagementLevel::NONE) { |
| if (m_frame) { |
| DCHECK(m_frame->page()); |
| provideContextFeaturesToDocumentFrom(*this, *m_frame->page()); |
| |
| m_fetcher = m_frame->loader().documentLoader()->fetcher(); |
| FrameFetchContext::provideDocumentToContext(m_fetcher->context(), this); |
| |
| // TODO(dcheng): Why does this need to check that DOMWindow is non-null? |
| CustomElementRegistry* registry = |
| m_frame->domWindow() ? m_frame->domWindow()->maybeCustomElements() |
| : nullptr; |
| if (registry && m_registrationContext) |
| registry->entangle(m_registrationContext); |
| } else if (m_importsController) { |
| m_fetcher = FrameFetchContext::createFetcherFromDocument(this); |
| } else { |
| m_fetcher = ResourceFetcher::create(nullptr); |
| } |
| DCHECK(m_fetcher); |
| |
| m_rootScrollerController = RootScrollerController::create(*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 |
| // and fast/dom/location-new-window-no-crash.html, respectively. |
| // FIXME: Can/should we unify this behavior? |
| if (initializer.shouldSetURL()) |
| setURL(initializer.url()); |
| |
| initSecurityContext(initializer); |
| |
| initDNSPrefetch(); |
| |
| InstanceCounters::incrementCounter(InstanceCounters::DocumentCounter); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::Inactive); |
| |
| // Since CSSFontSelector requires Document::m_fetcher and StyleEngine owns |
| // CSSFontSelector, need to initialize m_styleEngine after initializing |
| // m_fetcher. |
| m_styleEngine = StyleEngine::create(*this); |
| |
| // The parent's parser should be suspended together with all the other |
| // objects, else this new Document would have a new ExecutionContext which |
| // suspended state would not match the one from the parent, and could start |
| // loading resources ignoring the defersLoading flag. |
| DCHECK(!parentDocument() || !parentDocument()->isContextSuspended()); |
| |
| #ifndef NDEBUG |
| liveDocumentSet().add(this); |
| #endif |
| } |
| |
| Document::~Document() { |
| DCHECK(layoutViewItem().isNull()); |
| DCHECK(!parentTreeScope()); |
| // If a top document with a cache, verify that it was comprehensively |
| // cleared during detach. |
| DCHECK(!m_axObjectCache); |
| InstanceCounters::decrementCounter(InstanceCounters::DocumentCounter); |
| } |
| |
| SelectorQueryCache& Document::selectorQueryCache() { |
| if (!m_selectorQueryCache) |
| m_selectorQueryCache = WTF::makeUnique<SelectorQueryCache>(); |
| return *m_selectorQueryCache; |
| } |
| |
| MediaQueryMatcher& Document::mediaQueryMatcher() { |
| if (!m_mediaQueryMatcher) |
| m_mediaQueryMatcher = MediaQueryMatcher::create(*this); |
| return *m_mediaQueryMatcher; |
| } |
| |
| void Document::mediaQueryAffectingValueChanged() { |
| styleEngine().mediaQueryAffectingValueChanged(); |
| if (needsLayoutTreeUpdate()) |
| m_evaluateMediaQueriesOnStyleRecalc = true; |
| else |
| evaluateMediaQueryList(); |
| InspectorInstrumentation::mediaQueryResultChanged(this); |
| } |
| |
| void Document::setCompatibilityMode(CompatibilityMode mode) { |
| if (m_compatibilityModeLocked || mode == m_compatibilityMode) |
| return; |
| m_compatibilityMode = mode; |
| selectorQueryCache().invalidate(); |
| } |
| |
| String Document::compatMode() const { |
| return inQuirksMode() ? "BackCompat" : "CSS1Compat"; |
| } |
| |
| void Document::setDoctype(DocumentType* docType) { |
| // This should never be called more than once. |
| DCHECK(!m_docType || !docType); |
| m_docType = docType; |
| if (m_docType) { |
| this->adoptIfNeeded(*m_docType); |
| if (m_docType->publicId().startsWith("-//wapforum//dtd xhtml mobile 1.", |
| TextCaseASCIIInsensitive)) { |
| m_isMobileDocument = true; |
| m_styleEngine->viewportRulesChanged(); |
| } |
| } |
| } |
| |
| DOMImplementation& Document::implementation() { |
| if (!m_implementation) |
| m_implementation = DOMImplementation::create(*this); |
| return *m_implementation; |
| } |
| |
| bool Document::hasAppCacheManifest() const { |
| return isHTMLHtmlElement(documentElement()) && |
| documentElement()->hasAttribute(manifestAttr); |
| } |
| |
| Location* Document::location() const { |
| if (!frame()) |
| return 0; |
| |
| return domWindow()->location(); |
| } |
| |
| void Document::childrenChanged(const ChildrenChange& change) { |
| ContainerNode::childrenChanged(change); |
| m_documentElement = ElementTraversal::firstWithin(*this); |
| |
| // For non-HTML documents the willInsertBody notification won't happen |
| // so we resume as soon as we have a document element. Even for XHTML |
| // documents there may never be a <body> (since the parser won't always |
| // insert one), so we resume here too. That does mean XHTML documents make |
| // frames when there's only a <head>, but such documents are pretty rare. |
| if (m_documentElement && !isHTMLDocument()) |
| beginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| void Document::setRootScroller(Element* newScroller, |
| ExceptionState& exceptionState) { |
| m_rootScrollerController->set(newScroller); |
| } |
| |
| Element* Document::rootScroller() const { |
| return m_rootScrollerController->get(); |
| } |
| |
| bool Document::isInMainFrame() const { |
| return frame() && frame()->isMainFrame(); |
| } |
| |
| AtomicString Document::convertLocalName(const AtomicString& name) { |
| return isHTMLDocument() ? name.lower() : name; |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| Element* Document::createElement(const AtomicString& name, |
| ExceptionState& exceptionState) { |
| if (!isValidName(name)) { |
| exceptionState.throwDOMException( |
| InvalidCharacterError, |
| "The tag name provided ('" + name + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| if (isXHTMLDocument() || isHTMLDocument()) { |
| // 2. If the context object is an HTML document, let localName be |
| // converted to ASCII lowercase. |
| AtomicString localName = convertLocalName(name); |
| if (CustomElement::shouldCreateCustomElement(localName)) { |
| return CustomElement::createCustomElementSync( |
| *this, |
| QualifiedName(nullAtom, localName, HTMLNames::xhtmlNamespaceURI)); |
| } |
| return HTMLElementFactory::createHTMLElement(localName, *this, |
| CreatedByCreateElement); |
| } |
| return Element::create(QualifiedName(nullAtom, name, nullAtom), this); |
| } |
| |
| String getTypeExtension(Document* document, |
| const StringOrDictionary& stringOrOptions, |
| ExceptionState& exceptionState) { |
| if (stringOrOptions.isNull()) |
| return emptyString(); |
| |
| if (stringOrOptions.isString()) { |
| UseCounter::count(document, |
| UseCounter::DocumentCreateElement2ndArgStringHandling); |
| return stringOrOptions.getAsString(); |
| } |
| |
| if (stringOrOptions.isDictionary()) { |
| Dictionary dict = stringOrOptions.getAsDictionary(); |
| ElementCreationOptions impl; |
| V8ElementCreationOptions::toImpl(dict.isolate(), dict.v8Value(), impl, |
| exceptionState); |
| if (impl.hasIs()) |
| return impl.is(); |
| |
| return toCoreString(dict.v8Value()->ToString()); |
| } |
| |
| return emptyString(); |
| } |
| |
| // https://dom.spec.whatwg.org/#dom-document-createelement |
| Element* Document::createElement(const AtomicString& localName, |
| const StringOrDictionary& stringOrOptions, |
| ExceptionState& exceptionState) { |
| // 1. If localName does not match Name production, throw InvalidCharacterError |
| if (!isValidName(localName)) { |
| exceptionState.throwDOMException( |
| InvalidCharacterError, |
| "The tag name provided ('" + localName + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| // 2. localName converted to ASCII lowercase |
| const AtomicString& convertedLocalName = convertLocalName(localName); |
| |
| bool isV1 = stringOrOptions.isDictionary() || !registrationContext(); |
| bool createV1Builtin = stringOrOptions.isDictionary() && |
| RuntimeEnabledFeatures::customElementsBuiltinEnabled(); |
| bool shouldCreateBuiltin = createV1Builtin || stringOrOptions.isString(); |
| |
| // 3. |
| const AtomicString& is = |
| AtomicString(getTypeExtension(this, stringOrOptions, exceptionState)); |
| const AtomicString& name = shouldCreateBuiltin ? is : convertedLocalName; |
| |
| // 4. Let definition be result of lookup up custom element definition |
| CustomElementDefinition* definition = nullptr; |
| if (isV1) { |
| // Is the runtime flag enabled for customized builtin elements? |
| const CustomElementDescriptor desc = |
| RuntimeEnabledFeatures::customElementsBuiltinEnabled() |
| ? CustomElementDescriptor(name, convertedLocalName) |
| : CustomElementDescriptor(convertedLocalName, convertedLocalName); |
| if (CustomElementRegistry* registry = CustomElement::registry(*this)) |
| definition = registry->definitionFor(desc); |
| |
| // 5. If 'is' is non-null and definition is null, throw NotFoundError |
| // TODO(yurak): update when https://github.com/w3c/webcomponents/issues/608 |
| // is resolved |
| if (!definition && createV1Builtin) { |
| exceptionState.throwDOMException(NotFoundError, |
| "Custom element definition not found."); |
| return nullptr; |
| } |
| } |
| |
| // 7. Let element be the result of creating an element |
| Element* element; |
| |
| if (definition) { |
| element = CustomElement::createCustomElementSync(*this, convertedLocalName, |
| definition); |
| } else if (V0CustomElement::isValidName(localName) && registrationContext()) { |
| element = registrationContext()->createCustomTagElement( |
| *this, QualifiedName(nullAtom, convertedLocalName, xhtmlNamespaceURI)); |
| } else { |
| element = createElement(localName, exceptionState); |
| if (exceptionState.hadException()) |
| return nullptr; |
| } |
| |
| // 8. If 'is' is non-null, set 'is' attribute |
| if (!is.isEmpty()) { |
| if (stringOrOptions.isString()) { |
| V0CustomElementRegistrationContext::setIsAttributeAndTypeExtension( |
| element, is); |
| } else if (stringOrOptions.isDictionary()) { |
| element->setAttribute(HTMLNames::isAttr, is); |
| } |
| } |
| |
| return element; |
| } |
| |
| static inline QualifiedName createQualifiedName( |
| const AtomicString& namespaceURI, |
| const AtomicString& qualifiedName, |
| ExceptionState& exceptionState) { |
| AtomicString prefix, localName; |
| if (!Document::parseQualifiedName(qualifiedName, prefix, localName, |
| exceptionState)) |
| return QualifiedName::null(); |
| |
| QualifiedName qName(prefix, localName, namespaceURI); |
| if (!Document::hasValidNamespaceForElements(qName)) { |
| exceptionState.throwDOMException( |
| NamespaceError, |
| "The namespace URI provided ('" + namespaceURI + |
| "') is not valid for the qualified name provided ('" + |
| qualifiedName + "')."); |
| return QualifiedName::null(); |
| } |
| |
| return qName; |
| } |
| |
| Element* Document::createElementNS(const AtomicString& namespaceURI, |
| const AtomicString& qualifiedName, |
| ExceptionState& exceptionState) { |
| QualifiedName qName( |
| createQualifiedName(namespaceURI, qualifiedName, exceptionState)); |
| if (qName == QualifiedName::null()) |
| return nullptr; |
| |
| if (CustomElement::shouldCreateCustomElement(qName)) |
| return CustomElement::createCustomElementSync(*this, qName); |
| return createElement(qName, CreatedByCreateElement); |
| } |
| |
| // https://dom.spec.whatwg.org/#internal-createelementns-steps |
| Element* Document::createElementNS(const AtomicString& namespaceURI, |
| const AtomicString& qualifiedName, |
| const StringOrDictionary& stringOrOptions, |
| ExceptionState& exceptionState) { |
| // 1. Validate and extract |
| QualifiedName qName( |
| createQualifiedName(namespaceURI, qualifiedName, exceptionState)); |
| if (qName == QualifiedName::null()) |
| return nullptr; |
| |
| bool isV1 = stringOrOptions.isDictionary() || !registrationContext(); |
| bool createV1Builtin = stringOrOptions.isDictionary() && |
| RuntimeEnabledFeatures::customElementsBuiltinEnabled(); |
| bool shouldCreateBuiltin = createV1Builtin || stringOrOptions.isString(); |
| |
| // 2. |
| const AtomicString& is = |
| AtomicString(getTypeExtension(this, stringOrOptions, exceptionState)); |
| const AtomicString& name = shouldCreateBuiltin ? is : qualifiedName; |
| |
| if (!isValidName(qualifiedName)) { |
| exceptionState.throwDOMException( |
| InvalidCharacterError, |
| "The tag name provided ('" + qualifiedName + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| // 3. Let definition be result of lookup up custom element definition |
| CustomElementDefinition* definition = nullptr; |
| if (isV1) { |
| const CustomElementDescriptor desc = |
| RuntimeEnabledFeatures::customElementsBuiltinEnabled() |
| ? CustomElementDescriptor(name, qualifiedName) |
| : CustomElementDescriptor(qualifiedName, qualifiedName); |
| if (CustomElementRegistry* registry = CustomElement::registry(*this)) |
| definition = registry->definitionFor(desc); |
| |
| // 4. If 'is' is non-null and definition is null, throw NotFoundError |
| if (!definition && createV1Builtin) { |
| exceptionState.throwDOMException(NotFoundError, |
| "Custom element definition not found."); |
| return nullptr; |
| } |
| } |
| |
| // 5. Let element be the result of creating an element |
| Element* element; |
| |
| if (CustomElement::shouldCreateCustomElement(qName) || createV1Builtin) { |
| element = CustomElement::createCustomElementSync(*this, qName, definition); |
| } else if (V0CustomElement::isValidName(qName.localName()) && |
| registrationContext()) { |
| element = registrationContext()->createCustomTagElement(*this, qName); |
| } else { |
| element = createElement(qName, CreatedByCreateElement); |
| } |
| |
| // 6. If 'is' is non-null, set 'is' attribute |
| if (!is.isEmpty()) { |
| if (element->getCustomElementState() != CustomElementState::Custom) { |
| V0CustomElementRegistrationContext::setIsAttributeAndTypeExtension( |
| element, is); |
| } else if (stringOrOptions.isDictionary()) { |
| element->setAttribute(HTMLNames::isAttr, is); |
| } |
| } |
| |
| return element; |
| } |
| |
| ScriptValue Document::registerElement(ScriptState* scriptState, |
| const AtomicString& name, |
| const ElementRegistrationOptions& options, |
| ExceptionState& exceptionState, |
| V0CustomElement::NameSet validNames) { |
| HostsUsingFeatures::countMainWorldOnly( |
| scriptState, *this, HostsUsingFeatures::Feature::DocumentRegisterElement); |
| |
| if (!registrationContext()) { |
| exceptionState.throwDOMException( |
| NotSupportedError, "No element registration context is available."); |
| return ScriptValue(); |
| } |
| |
| V0CustomElementConstructorBuilder constructorBuilder(scriptState, options); |
| registrationContext()->registerElement(this, &constructorBuilder, name, |
| validNames, exceptionState); |
| return constructorBuilder.bindingsReturnValue(); |
| } |
| |
| V0CustomElementMicrotaskRunQueue* Document::customElementMicrotaskRunQueue() { |
| if (!m_customElementMicrotaskRunQueue) |
| m_customElementMicrotaskRunQueue = |
| V0CustomElementMicrotaskRunQueue::create(); |
| return m_customElementMicrotaskRunQueue.get(); |
| } |
| |
| void Document::clearImportsController() { |
| m_importsController = nullptr; |
| if (!loader()) |
| m_fetcher->clearContext(); |
| } |
| |
| void Document::createImportsController() { |
| DCHECK(!m_importsController); |
| m_importsController = HTMLImportsController::create(*this); |
| } |
| |
| HTMLImportLoader* Document::importLoader() const { |
| if (!m_importsController) |
| return 0; |
| return m_importsController->loaderFor(*this); |
| } |
| |
| bool Document::haveImportsLoaded() const { |
| if (!m_importsController) |
| return true; |
| return !m_importsController->shouldBlockScriptExecution(*this); |
| } |
| |
| LocalDOMWindow* Document::executingWindow() const { |
| if (LocalDOMWindow* owningWindow = domWindow()) |
| return owningWindow; |
| if (HTMLImportsController* import = this->importsController()) |
| return import->master()->domWindow(); |
| return 0; |
| } |
| |
| LocalFrame* Document::executingFrame() { |
| LocalDOMWindow* window = executingWindow(); |
| if (!window) |
| return 0; |
| return window->frame(); |
| } |
| |
| DocumentFragment* Document::createDocumentFragment() { |
| return DocumentFragment::create(*this); |
| } |
| |
| Text* Document::createTextNode(const String& data) { |
| return Text::create(*this, data); |
| } |
| |
| Comment* Document::createComment(const String& data) { |
| return Comment::create(*this, data); |
| } |
| |
| CDATASection* Document::createCDATASection(const String& data, |
| ExceptionState& exceptionState) { |
| if (isHTMLDocument()) { |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| "This operation is not supported for HTML documents."); |
| return nullptr; |
| } |
| if (data.contains("]]>")) { |
| exceptionState.throwDOMException(InvalidCharacterError, |
| "String cannot contain ']]>' since that " |
| "is the end delimiter of a CData " |
| "section."); |
| return nullptr; |
| } |
| return CDATASection::create(*this, data); |
| } |
| |
| ProcessingInstruction* Document::createProcessingInstruction( |
| const String& target, |
| const String& data, |
| ExceptionState& exceptionState) { |
| if (!isValidName(target)) { |
| exceptionState.throwDOMException( |
| InvalidCharacterError, |
| "The target provided ('" + target + "') is not a valid name."); |
| return nullptr; |
| } |
| if (data.contains("?>")) { |
| exceptionState.throwDOMException( |
| InvalidCharacterError, |
| "The data provided ('" + data + "') contains '?>'."); |
| return nullptr; |
| } |
| return ProcessingInstruction::create(*this, target, data); |
| } |
| |
| Text* Document::createEditingTextNode(const String& text) { |
| return Text::createEditingText(*this, text); |
| } |
| |
| bool Document::importContainerNodeChildren(ContainerNode* oldContainerNode, |
| ContainerNode* newContainerNode, |
| ExceptionState& exceptionState) { |
| for (Node& oldChild : NodeTraversal::childrenOf(*oldContainerNode)) { |
| Node* newChild = importNode(&oldChild, true, exceptionState); |
| if (exceptionState.hadException()) |
| return false; |
| newContainerNode->appendChild(newChild, exceptionState); |
| if (exceptionState.hadException()) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Node* Document::importNode(Node* importedNode, |
| bool deep, |
| ExceptionState& exceptionState) { |
| switch (importedNode->getNodeType()) { |
| case kTextNode: |
| return createTextNode(importedNode->nodeValue()); |
| case kCdataSectionNode: |
| return CDATASection::create(*this, importedNode->nodeValue()); |
| case kProcessingInstructionNode: |
| return createProcessingInstruction( |
| importedNode->nodeName(), importedNode->nodeValue(), exceptionState); |
| case kCommentNode: |
| return createComment(importedNode->nodeValue()); |
| case kDocumentTypeNode: { |
| DocumentType* doctype = toDocumentType(importedNode); |
| return DocumentType::create(this, doctype->name(), doctype->publicId(), |
| doctype->systemId()); |
| } |
| case kElementNode: { |
| Element* oldElement = toElement(importedNode); |
| // FIXME: The following check might be unnecessary. Is it possible that |
| // oldElement has mismatched prefix/namespace? |
| if (!hasValidNamespaceForElements(oldElement->tagQName())) { |
| exceptionState.throwDOMException( |
| NamespaceError, "The imported node has an invalid namespace."); |
| return nullptr; |
| } |
| Element* newElement = |
| createElement(oldElement->tagQName(), CreatedByImportNode); |
| |
| newElement->cloneDataFromElement(*oldElement); |
| |
| if (deep) { |
| if (!importContainerNodeChildren(oldElement, newElement, |
| exceptionState)) |
| return nullptr; |
| if (isHTMLTemplateElement(*oldElement) && |
| !ensureTemplateDocument().importContainerNodeChildren( |
| toHTMLTemplateElement(oldElement)->content(), |
| toHTMLTemplateElement(newElement)->content(), exceptionState)) |
| return nullptr; |
| } |
| |
| return newElement; |
| } |
| case kAttributeNode: |
| return Attr::create( |
| *this, |
| QualifiedName(nullAtom, AtomicString(toAttr(importedNode)->name()), |
| nullAtom), |
| toAttr(importedNode)->value()); |
| case kDocumentFragmentNode: { |
| if (importedNode->isShadowRoot()) { |
| // ShadowRoot nodes should not be explicitly importable. |
| // Either they are imported along with their host node, or created |
| // implicitly. |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| "The node provided is a shadow root, which may not be imported."); |
| return nullptr; |
| } |
| DocumentFragment* oldFragment = toDocumentFragment(importedNode); |
| DocumentFragment* newFragment = createDocumentFragment(); |
| if (deep && |
| !importContainerNodeChildren(oldFragment, newFragment, |
| exceptionState)) |
| return nullptr; |
| |
| return newFragment; |
| } |
| case kDocumentNode: |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| "The node provided is a document, which may not be imported."); |
| return nullptr; |
| } |
| |
| NOTREACHED(); |
| return nullptr; |
| } |
| |
| Node* Document::adoptNode(Node* source, ExceptionState& exceptionState) { |
| EventQueueScope scope; |
| |
| switch (source->getNodeType()) { |
| case kDocumentNode: |
| exceptionState.throwDOMException(NotSupportedError, |
| "The node provided is of type '" + |
| source->nodeName() + |
| "', which may not be adopted."); |
| return nullptr; |
| case kAttributeNode: { |
| Attr* attr = toAttr(source); |
| if (Element* ownerElement = attr->ownerElement()) |
| ownerElement->removeAttributeNode(attr, exceptionState); |
| break; |
| } |
| default: |
| if (source->isShadowRoot()) { |
| // ShadowRoot cannot disconnect itself from the host node. |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| "The node provided is a shadow root, which may not be adopted."); |
| return nullptr; |
| } |
| |
| if (source->isFrameOwnerElement()) { |
| HTMLFrameOwnerElement* frameOwnerElement = |
| toHTMLFrameOwnerElement(source); |
| if (frame() && |
| frame()->tree().isDescendantOf(frameOwnerElement->contentFrame())) { |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| "The node provided is a frame which contains this document."); |
| return nullptr; |
| } |
| } |
| if (source->parentNode()) { |
| source->parentNode()->removeChild(source, exceptionState); |
| if (exceptionState.hadException()) |
| return nullptr; |
| RELEASE_ASSERT(!source->parentNode()); |
| } |
| } |
| |
| this->adoptIfNeeded(*source); |
| |
| return source; |
| } |
| |
| bool Document::hasValidNamespaceForElements(const QualifiedName& qName) { |
| // These checks are from DOM Core Level 2, createElementNS |
| // http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-DocCrElNS |
| // createElementNS(null, "html:div") |
| if (!qName.prefix().isEmpty() && qName.namespaceURI().isNull()) |
| return false; |
| // createElementNS("http://www.example.com", "xml:lang") |
| if (qName.prefix() == xmlAtom && |
| qName.namespaceURI() != XMLNames::xmlNamespaceURI) |
| return false; |
| |
| // Required by DOM Level 3 Core and unspecified by DOM Level 2 Core: |
| // http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS |
| // createElementNS("http://www.w3.org/2000/xmlns/", "foo:bar"), |
| // createElementNS(null, "xmlns:bar"), createElementNS(null, "xmlns") |
| if (qName.prefix() == xmlnsAtom || |
| (qName.prefix().isEmpty() && qName.localName() == xmlnsAtom)) |
| return qName.namespaceURI() == XMLNSNames::xmlnsNamespaceURI; |
| return qName.namespaceURI() != XMLNSNames::xmlnsNamespaceURI; |
| } |
| |
| bool Document::hasValidNamespaceForAttributes(const QualifiedName& qName) { |
| return hasValidNamespaceForElements(qName); |
| } |
| |
| // FIXME: This should really be in a possible ElementFactory class |
| Element* Document::createElement(const QualifiedName& qName, |
| CreateElementFlags flags) { |
| Element* e = nullptr; |
| |
| // FIXME: Use registered namespaces and look up in a hash to find the right |
| // factory. |
| if (qName.namespaceURI() == xhtmlNamespaceURI) |
| e = HTMLElementFactory::createHTMLElement(qName.localName(), *this, flags); |
| else if (qName.namespaceURI() == SVGNames::svgNamespaceURI) |
| e = SVGElementFactory::createSVGElement(qName.localName(), *this, flags); |
| |
| if (e) |
| m_sawElementsInKnownNamespaces = true; |
| else |
| e = Element::create(qName, this); |
| |
| if (e->prefix() != qName.prefix()) |
| e->setTagNameForCreateElementNS(qName); |
| |
| DCHECK(qName == e->tagQName()); |
| |
| return e; |
| } |
| |
| String Document::readyState() const { |
| DEFINE_STATIC_LOCAL(const String, loading, ("loading")); |
| DEFINE_STATIC_LOCAL(const String, interactive, ("interactive")); |
| DEFINE_STATIC_LOCAL(const String, complete, ("complete")); |
| |
| switch (m_readyState) { |
| case Loading: |
| return loading; |
| case Interactive: |
| return interactive; |
| case Complete: |
| return complete; |
| } |
| |
| NOTREACHED(); |
| return String(); |
| } |
| |
| void Document::setReadyState(DocumentReadyState readyState) { |
| if (readyState == m_readyState) |
| return; |
| |
| switch (readyState) { |
| case Loading: |
| if (!m_documentTiming.domLoading()) { |
| m_documentTiming.markDomLoading(); |
| } |
| break; |
| case Interactive: |
| if (!m_documentTiming.domInteractive()) |
| m_documentTiming.markDomInteractive(); |
| break; |
| case Complete: |
| if (!m_documentTiming.domComplete()) |
| m_documentTiming.markDomComplete(); |
| break; |
| } |
| |
| m_readyState = readyState; |
| dispatchEvent(Event::create(EventTypeNames::readystatechange)); |
| } |
| |
| bool Document::isLoadCompleted() { |
| return m_readyState == Complete; |
| } |
| |
| AtomicString Document::encodingName() const { |
| // TextEncoding::name() returns a char*, no need to allocate a new |
| // String for it each time. |
| // FIXME: We should fix TextEncoding to speak AtomicString anyway. |
| return AtomicString(encoding().name()); |
| } |
| |
| void Document::setContentLanguage(const AtomicString& language) { |
| if (m_contentLanguage == language) |
| return; |
| m_contentLanguage = language; |
| |
| // Document's style depends on the content language. |
| setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create( |
| StyleChangeReason::Language)); |
| } |
| |
| void Document::setXMLVersion(const String& version, |
| ExceptionState& exceptionState) { |
| if (!XMLDocumentParser::supportsXMLVersion(version)) { |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| "This document does not support the XML version '" + version + "'."); |
| return; |
| } |
| |
| m_xmlVersion = version; |
| } |
| |
| void Document::setXMLStandalone(bool standalone, |
| ExceptionState& exceptionState) { |
| m_xmlStandalone = standalone ? Standalone : NotStandalone; |
| } |
| |
| void Document::setContent(const String& content) { |
| open(); |
| m_parser->append(content); |
| close(); |
| } |
| |
| String Document::suggestedMIMEType() const { |
| if (isXMLDocument()) { |
| if (isXHTMLDocument()) |
| return "application/xhtml+xml"; |
| if (isSVGDocument()) |
| return "image/svg+xml"; |
| return "application/xml"; |
| } |
| if (xmlStandalone()) |
| return "text/xml"; |
| if (isHTMLDocument()) |
| return "text/html"; |
| |
| if (DocumentLoader* documentLoader = loader()) |
| return documentLoader->responseMIMEType(); |
| return String(); |
| } |
| |
| void Document::setMimeType(const AtomicString& mimeType) { |
| m_mimeType = mimeType; |
| } |
| |
| AtomicString Document::contentType() const { |
| if (!m_mimeType.isEmpty()) |
| return m_mimeType; |
| |
| if (DocumentLoader* documentLoader = loader()) |
| return documentLoader->mimeType(); |
| |
| String mimeType = suggestedMIMEType(); |
| if (!mimeType.isEmpty()) |
| return AtomicString(mimeType); |
| |
| return AtomicString("application/xml"); |
| } |
| |
| Element* Document::elementFromPoint(int x, int y) const { |
| if (layoutViewItem().isNull()) |
| return 0; |
| |
| return TreeScope::elementFromPoint(x, y); |
| } |
| |
| HeapVector<Member<Element>> Document::elementsFromPoint(int x, int y) const { |
| if (layoutViewItem().isNull()) |
| return HeapVector<Member<Element>>(); |
| return TreeScope::elementsFromPoint(x, y); |
| } |
| |
| Range* Document::caretRangeFromPoint(int x, int y) { |
| if (layoutViewItem().isNull()) |
| return nullptr; |
| |
| HitTestResult result = hitTestInDocument(this, x, y); |
| PositionWithAffinity positionWithAffinity = result.position(); |
| if (positionWithAffinity.isNull()) |
| return nullptr; |
| |
| Position rangeCompliantPosition = |
| positionWithAffinity.position().parentAnchoredEquivalent(); |
| return Range::createAdjustedToTreeScope(*this, rangeCompliantPosition); |
| } |
| |
| Element* Document::scrollingElement() { |
| if (RuntimeEnabledFeatures::scrollTopLeftInteropEnabled() && inQuirksMode()) |
| updateStyleAndLayoutTree(); |
| return scrollingElementNoLayout(); |
| } |
| |
| Element* Document::scrollingElementNoLayout() { |
| if (RuntimeEnabledFeatures::scrollTopLeftInteropEnabled()) { |
| if (inQuirksMode()) { |
| DCHECK(m_lifecycle.state() >= DocumentLifecycle::StyleClean); |
| HTMLBodyElement* body = firstBodyElement(); |
| if (body && body->layoutObject() && |
| body->layoutObject()->hasOverflowClip()) |
| return nullptr; |
| |
| return body; |
| } |
| |
| return documentElement(); |
| } |
| |
| return body(); |
| } |
| |
| // We use HashMap::set over HashMap::add here as we want to |
| // replace the ComputedStyle but not the Node if the Node is |
| // already present. |
| void Document::addStyleReattachData(Node& node, |
| StyleReattachData& styleReattachData) { |
| DCHECK(node.isElementNode() || node.isTextNode()); |
| m_styleReattachDataMap.set(&node, styleReattachData); |
| } |
| |
| StyleReattachData Document::getStyleReattachData(Node& node) { |
| return m_styleReattachDataMap.get(&node); |
| } |
| |
| /* |
| * Performs three operations: |
| * 1. Convert control characters to spaces |
| * 2. Trim leading and trailing spaces |
| * 3. Collapse internal whitespace. |
| */ |
| template <typename CharacterType> |
| static inline String canonicalizedTitle(Document* document, |
| const String& title) { |
| unsigned length = title.length(); |
| unsigned builderIndex = 0; |
| const CharacterType* characters = title.getCharacters<CharacterType>(); |
| |
| StringBuffer<CharacterType> buffer(length); |
| |
| // Replace control characters with spaces and collapse whitespace. |
| bool pendingWhitespace = false; |
| for (unsigned i = 0; i < length; ++i) { |
| UChar32 c = characters[i]; |
| if ((c <= spaceCharacter && c != lineTabulationCharacter) || |
| c == deleteCharacter) { |
| if (builderIndex != 0) |
| pendingWhitespace = true; |
| } else { |
| if (pendingWhitespace) { |
| buffer[builderIndex++] = ' '; |
| pendingWhitespace = false; |
| } |
| buffer[builderIndex++] = c; |
| } |
| } |
| buffer.shrink(builderIndex); |
| |
| return String::adopt(buffer); |
| } |
| |
| void Document::updateTitle(const String& title) { |
| if (m_rawTitle == title) |
| return; |
| |
| m_rawTitle = title; |
| |
| String oldTitle = m_title; |
| if (m_rawTitle.isEmpty()) |
| m_title = String(); |
| else if (m_rawTitle.is8Bit()) |
| m_title = canonicalizedTitle<LChar>(this, m_rawTitle); |
| else |
| m_title = canonicalizedTitle<UChar>(this, m_rawTitle); |
| |
| if (!m_frame || oldTitle == m_title) |
| return; |
| m_frame->loader().client()->dispatchDidReceiveTitle(m_title); |
| } |
| |
| void Document::setTitle(const String& title) { |
| // Title set by JavaScript -- overrides any title elements. |
| if (!m_titleElement) { |
| if (isHTMLDocument() || isXHTMLDocument()) { |
| HTMLElement* headElement = head(); |
| if (!headElement) |
| return; |
| m_titleElement = HTMLTitleElement::create(*this); |
| headElement->appendChild(m_titleElement.get()); |
| } else if (isSVGDocument()) { |
| Element* element = documentElement(); |
| if (!isSVGSVGElement(element)) |
| return; |
| m_titleElement = SVGTitleElement::create(*this); |
| element->insertBefore(m_titleElement.get(), element->firstChild()); |
| } |
| } else { |
| if (!isHTMLDocument() && !isXHTMLDocument() && !isSVGDocument()) |
| m_titleElement = nullptr; |
| } |
| |
| if (isHTMLTitleElement(m_titleElement)) |
| toHTMLTitleElement(m_titleElement)->setText(title); |
| else if (isSVGTitleElement(m_titleElement)) |
| toSVGTitleElement(m_titleElement)->setText(title); |
| else |
| updateTitle(title); |
| } |
| |
| void Document::setTitleElement(Element* titleElement) { |
| // If the root element is an svg element in the SVG namespace, then let value |
| // be the child text content of the first title element in the SVG namespace |
| // that is a child of the root element. |
| if (isSVGSVGElement(documentElement())) { |
| m_titleElement = Traversal<SVGTitleElement>::firstChild(*documentElement()); |
| } else { |
| if (m_titleElement && m_titleElement != titleElement) |
| m_titleElement = Traversal<HTMLTitleElement>::firstWithin(*this); |
| else |
| m_titleElement = titleElement; |
| |
| // If the root element isn't an svg element in the SVG namespace and the |
| // title element is in the SVG namespace, it is ignored. |
| if (isSVGTitleElement(m_titleElement)) { |
| m_titleElement = nullptr; |
| return; |
| } |
| } |
| |
| if (isHTMLTitleElement(m_titleElement)) |
| updateTitle(toHTMLTitleElement(m_titleElement)->text()); |
| else if (isSVGTitleElement(m_titleElement)) |
| updateTitle(toSVGTitleElement(m_titleElement)->textContent()); |
| } |
| |
| void Document::removeTitle(Element* titleElement) { |
| if (m_titleElement != titleElement) |
| return; |
| |
| m_titleElement = nullptr; |
| |
| // Update title based on first title element in the document, if one exists. |
| if (isHTMLDocument() || isXHTMLDocument()) { |
| if (HTMLTitleElement* title = |
| Traversal<HTMLTitleElement>::firstWithin(*this)) |
| setTitleElement(title); |
| } else if (isSVGDocument()) { |
| if (SVGTitleElement* title = Traversal<SVGTitleElement>::firstWithin(*this)) |
| setTitleElement(title); |
| } |
| |
| if (!m_titleElement) |
| updateTitle(String()); |
| } |
| |
| const AtomicString& Document::dir() { |
| Element* rootElement = documentElement(); |
| if (isHTMLHtmlElement(rootElement)) |
| return toHTMLHtmlElement(rootElement)->dir(); |
| return nullAtom; |
| } |
| |
| void Document::setDir(const AtomicString& value) { |
| Element* rootElement = documentElement(); |
| if (isHTMLHtmlElement(rootElement)) |
| toHTMLHtmlElement(rootElement)->setDir(value); |
| } |
| |
| PageVisibilityState Document::pageVisibilityState() const { |
| // 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: |
| // https://w3c.github.io/page-visibility/#hidden-attribute |
| if (!m_frame || !m_frame->page()) |
| return PageVisibilityStateHidden; |
| // While visibilitychange is being dispatched during unloading it is |
| // expected that the visibility is hidden regardless of the page's |
| // visibility. |
| if (m_loadEventProgress >= UnloadVisibilityChangeInProgress) |
| return PageVisibilityStateHidden; |
| return m_frame->page()->visibilityState(); |
| } |
| |
| bool Document::isPrefetchOnly() const { |
| if (!m_frame || !m_frame->page()) |
| return false; |
| |
| PrerendererClient* prerendererClient = |
| PrerendererClient::from(m_frame->page()); |
| return prerendererClient && prerendererClient->isPrefetchOnly(); |
| } |
| |
| String Document::visibilityState() const { |
| return pageVisibilityStateString(pageVisibilityState()); |
| } |
| |
| bool Document::hidden() const { |
| return pageVisibilityState() != PageVisibilityStateVisible; |
| } |
| |
| void Document::didChangeVisibilityState() { |
| dispatchEvent(Event::createBubble(EventTypeNames::visibilitychange)); |
| // Also send out the deprecated version until it can be removed. |
| dispatchEvent(Event::createBubble(EventTypeNames::webkitvisibilitychange)); |
| |
| if (pageVisibilityState() == PageVisibilityStateVisible) |
| timeline().setAllCompositorPending(); |
| |
| if (hidden() && m_canvasFontCache) |
| m_canvasFontCache->pruneAll(); |
| } |
| |
| String Document::nodeName() const { |
| return "#document"; |
| } |
| |
| Node::NodeType Document::getNodeType() const { |
| return kDocumentNode; |
| } |
| |
| FormController& Document::formController() { |
| if (!m_formController) { |
| m_formController = FormController::create(); |
| if (m_frame && m_frame->loader().currentItem() && |
| m_frame->loader().currentItem()->isCurrentDocument(this)) |
| m_frame->loader().currentItem()->setDocumentState( |
| m_formController->formElementsState()); |
| } |
| return *m_formController; |
| } |
| |
| DocumentState* Document::formElementsState() const { |
| if (!m_formController) |
| return 0; |
| return m_formController->formElementsState(); |
| } |
| |
| void Document::setStateForNewFormElements(const Vector<String>& stateVector) { |
| if (!stateVector.size() && !m_formController) |
| return; |
| formController().setStateForNewFormElements(stateVector); |
| } |
| |
| FrameView* Document::view() const { |
| return m_frame ? m_frame->view() : nullptr; |
| } |
| |
| Page* Document::page() const { |
| return m_frame ? m_frame->page() : nullptr; |
| } |
| |
| FrameHost* Document::frameHost() const { |
| return m_frame ? m_frame->host() : nullptr; |
| } |
| |
| Settings* Document::settings() const { |
| return m_frame ? m_frame->settings() : nullptr; |
| } |
| |
| Range* Document::createRange() { |
| return Range::create(*this); |
| } |
| |
| NodeIterator* Document::createNodeIterator(Node* root, |
| unsigned whatToShow, |
| NodeFilter* filter) { |
| DCHECK(root); |
| return NodeIterator::create(root, whatToShow, filter); |
| } |
| |
| TreeWalker* Document::createTreeWalker(Node* root, |
| unsigned whatToShow, |
| NodeFilter* filter) { |
| DCHECK(root); |
| return TreeWalker::create(root, whatToShow, filter); |
| } |
| |
| bool Document::needsLayoutTreeUpdate() const { |
| if (!isActive() || !view()) |
| return false; |
| if (needsFullLayoutTreeUpdate()) |
| return true; |
| if (childNeedsStyleRecalc()) |
| return true; |
| if (childNeedsStyleInvalidation()) |
| return true; |
| if (layoutViewItem().wasNotifiedOfSubtreeChange()) |
| return true; |
| return false; |
| } |
| |
| bool Document::needsFullLayoutTreeUpdate() const { |
| if (!isActive() || !view()) |
| return false; |
| if (m_styleEngine->needsActiveStyleUpdate()) |
| return true; |
| if (!m_useElementsNeedingUpdate.isEmpty()) |
| return true; |
| if (needsStyleRecalc()) |
| return true; |
| if (needsStyleInvalidation()) |
| return true; |
| // FIXME: The childNeedsDistributionRecalc bit means either self or children, |
| // we should fix that. |
| if (childNeedsDistributionRecalc()) |
| return true; |
| if (DocumentAnimations::needsAnimationTimingUpdate(*this)) |
| return true; |
| return false; |
| } |
| |
| bool Document::shouldScheduleLayoutTreeUpdate() const { |
| if (!isActive()) |
| return false; |
| if (inStyleRecalc()) |
| return false; |
| // InPreLayout will recalc style itself. There's no reason to schedule another |
| // recalc. |
| if (m_lifecycle.state() == DocumentLifecycle::InPreLayout) |
| return false; |
| if (!shouldScheduleLayout()) |
| return false; |
| return true; |
| } |
| |
| void Document::scheduleLayoutTreeUpdate() { |
| DCHECK(!hasPendingVisualUpdate()); |
| DCHECK(shouldScheduleLayoutTreeUpdate()); |
| DCHECK(needsLayoutTreeUpdate()); |
| |
| if (!view()->canThrottleRendering()) |
| page()->animator().scheduleVisualUpdate(frame()); |
| m_lifecycle.ensureStateAtMost(DocumentLifecycle::VisualUpdatePending); |
| |
| TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), |
| "ScheduleStyleRecalculation", TRACE_EVENT_SCOPE_THREAD, |
| "data", InspectorRecalculateStylesEvent::data(frame())); |
| InspectorInstrumentation::didScheduleStyleRecalculation(this); |
| |
| ++m_styleVersion; |
| } |
| |
| bool Document::hasPendingForcedStyleRecalc() const { |
| return hasPendingVisualUpdate() && !inStyleRecalc() && |
| getStyleChangeType() >= SubtreeStyleChange; |
| } |
| |
| void Document::updateStyleInvalidationIfNeeded() { |
| DCHECK(isActive()); |
| ScriptForbiddenScope forbidScript; |
| |
| if (!childNeedsStyleInvalidation()) |
| return; |
| TRACE_EVENT0("blink", "Document::updateStyleInvalidationIfNeeded"); |
| styleEngine().styleInvalidator().invalidate(*this); |
| } |
| |
| void Document::setupFontBuilder(ComputedStyle& documentStyle) { |
| FontBuilder fontBuilder(*this); |
| CSSFontSelector* selector = styleEngine().fontSelector(); |
| fontBuilder.createFontForDocument(selector, documentStyle); |
| } |
| |
| void Document::inheritHtmlAndBodyElementStyles(StyleRecalcChange change) { |
| DCHECK(inStyleRecalc()); |
| DCHECK(documentElement()); |
| |
| bool didRecalcDocumentElement = false; |
| RefPtr<ComputedStyle> documentElementStyle = |
| documentElement()->mutableComputedStyle(); |
| if (change == Force) |
| documentElement()->clearAnimationStyleChange(); |
| if (!documentElementStyle || documentElement()->needsStyleRecalc() || |
| change == Force) { |
| documentElementStyle = |
| ensureStyleResolver().styleForElement(documentElement()); |
| didRecalcDocumentElement = true; |
| } |
| |
| WritingMode rootWritingMode = documentElementStyle->getWritingMode(); |
| TextDirection rootDirection = documentElementStyle->direction(); |
| |
| HTMLElement* body = this->body(); |
| RefPtr<ComputedStyle> bodyStyle; |
| |
| if (body) { |
| bodyStyle = body->mutableComputedStyle(); |
| if (didRecalcDocumentElement) |
| body->clearAnimationStyleChange(); |
| if (!bodyStyle || body->needsStyleRecalc() || didRecalcDocumentElement) |
| bodyStyle = ensureStyleResolver().styleForElement( |
| body, documentElementStyle.get()); |
| rootWritingMode = bodyStyle->getWritingMode(); |
| rootDirection = bodyStyle->direction(); |
| } |
| |
| const ComputedStyle* backgroundStyle = documentElementStyle.get(); |
| // http://www.w3.org/TR/css3-background/#body-background |
| // <html> root element with no background steals background from its first |
| // <body> child. |
| // Also see LayoutBoxModelObject::backgroundStolenForBeingBody() |
| if (isHTMLHtmlElement(documentElement()) && isHTMLBodyElement(body) && |
| !backgroundStyle->hasBackground()) |
| backgroundStyle = bodyStyle.get(); |
| Color backgroundColor = |
| backgroundStyle->visitedDependentColor(CSSPropertyBackgroundColor); |
| FillLayer backgroundLayers = backgroundStyle->backgroundLayers(); |
| for (auto currentLayer = &backgroundLayers; currentLayer; |
| currentLayer = currentLayer->next()) { |
| // http://www.w3.org/TR/css3-background/#root-background |
| // The root element background always have painting area of the whole |
| // canvas. |
| currentLayer->setClip(BorderFillBox); |
| |
| // The root element doesn't scroll. It always propagates its layout overflow |
| // to the viewport. Positioning background against either box is equivalent |
| // to positioning against the scrolled box of the viewport. |
| if (currentLayer->attachment() == ScrollBackgroundAttachment) |
| currentLayer->setAttachment(LocalBackgroundAttachment); |
| } |
| EImageRendering imageRendering = backgroundStyle->imageRendering(); |
| |
| const ComputedStyle* overflowStyle = nullptr; |
| if (Element* element = viewportDefiningElement(documentElementStyle.get())) { |
| if (element == body) { |
| overflowStyle = bodyStyle.get(); |
| } else { |
| DCHECK_EQ(element, documentElement()); |
| overflowStyle = documentElementStyle.get(); |
| |
| // The body element has its own scrolling box, independent from the |
| // viewport. This is a bit of a weird edge case in the CSS spec that we |
| // might want to try to eliminate some day (eg. for ScrollTopLeftInterop - |
| // see http://crbug.com/157855). |
| if (bodyStyle && !bodyStyle->isOverflowVisible()) |
| UseCounter::count(*this, UseCounter::BodyScrollsInAdditionToViewport); |
| } |
| } |
| |
| // Resolved rem units are stored in the matched properties cache so we need to |
| // make sure to invalidate the cache if the documentElement needed to reattach |
| // or the font size changed and then trigger a full document recalc. We also |
| // need to clear it here since the call to styleForElement on the body above |
| // can cache bad values for rem units if the documentElement's style was |
| // dirty. We could keep track of which elements depend on rem units like we do |
| // for viewport styles, but we assume root font size changes are rare and just |
| // invalidate the cache for now. |
| if (styleEngine().usesRemUnits() && |
| (documentElement()->needsAttach() || |
| !documentElement()->computedStyle() || |
| documentElement()->computedStyle()->fontSize() != |
| documentElementStyle->fontSize())) { |
| ensureStyleResolver().invalidateMatchedPropertiesCache(); |
| documentElement()->setNeedsStyleRecalc( |
| SubtreeStyleChange, |
| StyleChangeReasonForTracing::create(StyleChangeReason::FontSizeChange)); |
| } |
| |
| EOverflowAnchor overflowAnchor = EOverflowAnchor::Auto; |
| EOverflow overflowX = EOverflow::Auto; |
| EOverflow overflowY = EOverflow::Auto; |
| float columnGap = 0; |
| if (overflowStyle) { |
| overflowAnchor = overflowStyle->overflowAnchor(); |
| overflowX = overflowStyle->overflowX(); |
| overflowY = overflowStyle->overflowY(); |
| // Visible overflow on the viewport is meaningless, and the spec says to |
| // treat it as 'auto': |
| if (overflowX == EOverflow::Visible) |
| overflowX = EOverflow::Auto; |
| if (overflowY == EOverflow::Visible) |
| overflowY = EOverflow::Auto; |
| if (overflowAnchor == EOverflowAnchor::Visible) |
| overflowAnchor = EOverflowAnchor::Auto; |
| // Column-gap is (ab)used by the current paged overflow implementation (in |
| // lack of other ways to specify gaps between pages), so we have to |
| // propagate it too. |
| columnGap = overflowStyle->columnGap(); |
| } |
| |
| ScrollSnapType snapType = overflowStyle->getScrollSnapType(); |
| const LengthPoint& snapDestination = overflowStyle->scrollSnapDestination(); |
| |
| RefPtr<ComputedStyle> documentStyle = layoutViewItem().mutableStyle(); |
| if (documentStyle->getWritingMode() != rootWritingMode || |
| documentStyle->direction() != rootDirection || |
| documentStyle->visitedDependentColor(CSSPropertyBackgroundColor) != |
| backgroundColor || |
| documentStyle->backgroundLayers() != backgroundLayers || |
| documentStyle->imageRendering() != imageRendering || |
| documentStyle->overflowAnchor() != overflowAnchor || |
| documentStyle->overflowX() != overflowX || |
| documentStyle->overflowY() != overflowY || |
| documentStyle->columnGap() != columnGap || |
| documentStyle->getScrollSnapType() != snapType || |
| documentStyle->scrollSnapDestination() != snapDestination) { |
| RefPtr<ComputedStyle> newStyle = ComputedStyle::clone(*documentStyle); |
| newStyle->setWritingMode(rootWritingMode); |
| newStyle->setDirection(rootDirection); |
| newStyle->setBackgroundColor(backgroundColor); |
| newStyle->accessBackgroundLayers() = backgroundLayers; |
| newStyle->setImageRendering(imageRendering); |
| newStyle->setOverflowAnchor(overflowAnchor); |
| newStyle->setOverflowX(overflowX); |
| newStyle->setOverflowY(overflowY); |
| newStyle->setColumnGap(columnGap); |
| newStyle->setScrollSnapType(snapType); |
| newStyle->setScrollSnapDestination(snapDestination); |
| layoutViewItem().setStyle(newStyle); |
| setupFontBuilder(*newStyle); |
| } |
| |
| if (body) { |
| if (const ComputedStyle* style = body->computedStyle()) { |
| if (style->direction() != rootDirection || |
| style->getWritingMode() != rootWritingMode) |
| body->setNeedsStyleRecalc(SubtreeStyleChange, |
| StyleChangeReasonForTracing::create( |
| StyleChangeReason::WritingModeChange)); |
| } |
| } |
| |
| if (const ComputedStyle* style = documentElement()->computedStyle()) { |
| if (style->direction() != rootDirection || |
| style->getWritingMode() != rootWritingMode) |
| documentElement()->setNeedsStyleRecalc( |
| SubtreeStyleChange, StyleChangeReasonForTracing::create( |
| StyleChangeReason::WritingModeChange)); |
| } |
| } |
| |
| #if DCHECK_IS_ON() |
| static void assertLayoutTreeUpdated(Node& root) { |
| for (Node& node : NodeTraversal::inclusiveDescendantsOf(root)) { |
| // We leave some nodes with dirty bits in the tree because they don't |
| // matter like Comment and ProcessingInstruction nodes. |
| // TODO(esprehn): Don't even mark those nodes as needing recalcs in the |
| // first place. |
| if (!node.isElementNode() && !node.isTextNode() && !node.isShadowRoot() && |
| !node.isDocumentNode()) |
| continue; |
| DCHECK(!node.needsStyleRecalc()); |
| DCHECK(!node.childNeedsStyleRecalc()); |
| DCHECK(!node.needsReattachLayoutTree()); |
| DCHECK(!node.childNeedsReattachLayoutTree()); |
| DCHECK(!node.childNeedsDistributionRecalc()); |
| DCHECK(!node.needsStyleInvalidation()); |
| DCHECK(!node.childNeedsStyleInvalidation()); |
| for (ShadowRoot* shadowRoot = node.youngestShadowRoot(); shadowRoot; |
| shadowRoot = shadowRoot->olderShadowRoot()) |
| assertLayoutTreeUpdated(*shadowRoot); |
| } |
| } |
| #endif |
| |
| void Document::updateStyleAndLayoutTree() { |
| DCHECK(isMainThread()); |
| |
| ScriptForbiddenScope forbidScript; |
| // We should forbid script execution for plugins here because update while |
| // layout is changing, HTMLPlugin element can be reattached and plugin can be |
| // destroyed. Plugin can execute scripts on destroy. It produces crash without |
| // PluginScriptForbiddenScope: crbug.com/550427. |
| PluginScriptForbiddenScope pluginForbidScript; |
| |
| if (!view() || !isActive()) |
| return; |
| |
| if (view()->shouldThrottleRendering()) |
| return; |
| |
| if (!needsLayoutTreeUpdate()) { |
| if (lifecycle().state() < DocumentLifecycle::StyleClean) { |
| // needsLayoutTreeUpdate may change to false without any actual layout |
| // tree update. For example, needsAnimationTimingUpdate may change to |
| // false when time elapses. Advance lifecycle to StyleClean because style |
| // is actually clean now. |
| lifecycle().advanceTo(DocumentLifecycle::InStyleRecalc); |
| lifecycle().advanceTo(DocumentLifecycle::StyleClean); |
| } |
| return; |
| } |
| |
| if (inStyleRecalc()) |
| return; |
| |
| // Entering here from inside layout, paint etc. would be catastrophic since |
| // recalcStyle can tear down the layout tree or (unfortunately) run |
| // script. Kill the whole layoutObject if someone managed to get into here in |
| // states not allowing tree mutations. |
| RELEASE_ASSERT(lifecycle().stateAllowsTreeMutations()); |
| |
| TRACE_EVENT_BEGIN1("blink,devtools.timeline", "UpdateLayoutTree", "beginData", |
| InspectorRecalculateStylesEvent::data(frame())); |
| |
| unsigned startElementCount = styleEngine().styleForElementCount(); |
| |
| InspectorInstrumentation::willRecalculateStyle(this); |
| PerformanceMonitor::willRecalculateStyle(this); |
| |
| DocumentAnimations::updateAnimationTimingIfNeeded(*this); |
| evaluateMediaQueryListIfNeeded(); |
| updateUseShadowTreesIfNeeded(); |
| updateDistribution(); |
| updateActiveStyle(); |
| updateStyleInvalidationIfNeeded(); |
| |
| // FIXME: We should update style on our ancestor chain before proceeding |
| // however doing so currently causes several tests to crash, as |
| // LocalFrame::setDocument calls Document::attach before setting the |
| // LocalDOMWindow on the LocalFrame, or the SecurityOrigin on the |
| // document. The attach, in turn resolves style (here) and then when we |
| // resolve style on the parent chain, we may end up 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. |
| |
| updateStyle(); |
| |
| notifyLayoutTreeOfSubtreeChanges(); |
| |
| // 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 (hoverNode() && !hoverNode()->layoutObject() && frame()) |
| frame()->eventHandler().dispatchFakeMouseMoveEventSoon(); |
| |
| if (m_focusedElement && !m_focusedElement->isFocusable()) |
| clearFocusedElementSoon(); |
| layoutViewItem().clearHitTestCache(); |
| |
| DCHECK(!DocumentAnimations::needsAnimationTimingUpdate(*this)); |
| |
| unsigned elementCount = |
| styleEngine().styleForElementCount() - startElementCount; |
| |
| TRACE_EVENT_END1("blink,devtools.timeline", "UpdateLayoutTree", |
| "elementCount", elementCount); |
| |
| #if DCHECK_IS_ON() |
| assertLayoutTreeUpdated(*this); |
| #endif |
| InspectorInstrumentation::didRecalculateStyle(this); |
| PerformanceMonitor::didRecalculateStyle(this); |
| } |
| |
| void Document::updateActiveStyle() { |
| DCHECK(isActive()); |
| DCHECK(isMainThread()); |
| TRACE_EVENT0("blink", "Document::updateActiveStyle"); |
| styleEngine().updateActiveStyle(); |
| } |
| |
| void Document::updateStyle() { |
| DCHECK(!view()->shouldThrottleRendering()); |
| TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle"); |
| double startTime = monotonicallyIncreasingTime(); |
| |
| unsigned initialElementCount = styleEngine().styleForElementCount(); |
| |
| HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates; |
| m_lifecycle.advanceTo(DocumentLifecycle::InStyleRecalc); |
| |
| StyleRecalcChange change = NoChange; |
| if (getStyleChangeType() >= SubtreeStyleChange) |
| change = Force; |
| |
| NthIndexCache nthIndexCache(*this); |
| |
| // FIXME: Cannot access the ensureStyleResolver() before calling |
| // styleForDocument below because apparently the StyleResolver's constructor |
| // has side effects. We should fix it. See printing/setPrinting.html, |
| // printing/width-overflow.html though they only fail on mac when accessing |
| // the resolver by what appears to be a viewport size difference. |
| |
| if (change == Force) { |
| m_hasNodesWithPlaceholderStyle = false; |
| RefPtr<ComputedStyle> documentStyle = |
| StyleResolver::styleForDocument(*this); |
| StyleRecalcChange localChange = ComputedStyle::stylePropagationDiff( |
| documentStyle.get(), layoutViewItem().style()); |
| if (localChange != NoChange) |
| layoutViewItem().setStyle(std::move(documentStyle)); |
| } |
| |
| clearNeedsStyleRecalc(); |
| clearNeedsReattachLayoutTree(); |
| |
| StyleResolver& resolver = ensureStyleResolver(); |
| |
| bool shouldRecordStats; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink,blink_style", &shouldRecordStats); |
| styleEngine().setStatsEnabled(shouldRecordStats); |
| |
| if (Element* documentElement = this->documentElement()) { |
| inheritHtmlAndBodyElementStyles(change); |
| if (documentElement->shouldCallRecalcStyle(change)) |
| documentElement->recalcStyle(change); |
| } |
| |
| view()->recalcOverflowAfterStyleChange(); |
| |
| // Only retain the HashMap for the duration of StyleRecalc and |
| // LayoutTreeConstruction. |
| m_styleReattachDataMap.clear(); |
| clearChildNeedsStyleRecalc(); |
| clearChildNeedsReattachLayoutTree(); |
| |
| resolver.clearStyleSharingList(); |
| |
| DCHECK(!needsStyleRecalc()); |
| DCHECK(!childNeedsStyleRecalc()); |
| DCHECK(!needsReattachLayoutTree()); |
| DCHECK(!childNeedsReattachLayoutTree()); |
| DCHECK(inStyleRecalc()); |
| DCHECK_EQ(styleResolver(), &resolver); |
| DCHECK(m_styleReattachDataMap.isEmpty()); |
| m_lifecycle.advanceTo(DocumentLifecycle::StyleClean); |
| if (shouldRecordStats) { |
| TRACE_EVENT_END2("blink,blink_style", "Document::updateStyle", |
| "resolverAccessCount", |
| styleEngine().styleForElementCount() - initialElementCount, |
| "counters", styleEngine().stats()->toTracedValue()); |
| } else { |
| TRACE_EVENT_END1( |
| "blink,blink_style", "Document::updateStyle", "resolverAccessCount", |
| styleEngine().styleForElementCount() - initialElementCount); |
| } |
| |
| double updateDurationSeconds = monotonicallyIncreasingTime() - startTime; |
| DEFINE_STATIC_LOCAL(CustomCountHistogram, updateHistogram, |
| ("Style.UpdateTime", 0, 10000000, 50)); |
| updateHistogram.count(updateDurationSeconds * 1000 * 1000); |
| CSSTiming::from(*this).recordUpdateDuration(updateDurationSeconds); |
| } |
| |
| void Document::notifyLayoutTreeOfSubtreeChanges() { |
| if (!layoutViewItem().wasNotifiedOfSubtreeChange()) |
| return; |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::InLayoutSubtreeChange); |
| |
| layoutViewItem().handleSubtreeModifications(); |
| DCHECK(!layoutViewItem().wasNotifiedOfSubtreeChange()); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::LayoutSubtreeChangeClean); |
| } |
| |
| bool Document::needsLayoutTreeUpdateForNode(const Node& node) const { |
| if (!node.canParticipateInFlatTree()) |
| return false; |
| if (!needsLayoutTreeUpdate()) |
| return false; |
| if (!node.isConnected()) |
| return false; |
| |
| if (needsFullLayoutTreeUpdate() || node.needsStyleRecalc() || |
| node.needsStyleInvalidation()) |
| return true; |
| for (const ContainerNode* ancestor = LayoutTreeBuilderTraversal::parent(node); |
| ancestor; ancestor = LayoutTreeBuilderTraversal::parent(*ancestor)) { |
| if (ancestor->needsStyleRecalc() || ancestor->needsStyleInvalidation() || |
| ancestor->needsAdjacentStyleRecalc()) |
| return true; |
| } |
| return false; |
| } |
| |
| void Document::updateStyleAndLayoutTreeForNode(const Node* node) { |
| DCHECK(node); |
| if (!needsLayoutTreeUpdateForNode(*node)) |
| return; |
| updateStyleAndLayoutTree(); |
| } |
| |
| void Document::updateStyleAndLayoutIgnorePendingStylesheetsForNode(Node* node) { |
| DCHECK(node); |
| if (!node->inActiveDocument()) |
| return; |
| updateStyleAndLayoutIgnorePendingStylesheets(); |
| } |
| |
| void Document::updateStyleAndLayout() { |
| DCHECK(isMainThread()); |
| |
| ScriptForbiddenScope forbidScript; |
| |
| FrameView* frameView = view(); |
| if (frameView && frameView->isInPerformLayout()) { |
| // View layout should not be re-entrant. |
| NOTREACHED(); |
| return; |
| } |
| |
| if (HTMLFrameOwnerElement* owner = localOwner()) |
| owner->document().updateStyleAndLayout(); |
| |
| updateStyleAndLayoutTree(); |
| |
| if (!isActive()) |
| return; |
| |
| if (frameView->needsLayout()) |
| frameView->layout(); |
| |
| if (lifecycle().state() < DocumentLifecycle::LayoutClean) |
| lifecycle().advanceTo(DocumentLifecycle::LayoutClean); |
| |
| if (FrameView* frameView = view()) |
| frameView->performScrollAnchoringAdjustments(); |
| } |
| |
| void Document::layoutUpdated() { |
| // Plugins can run script inside layout which can detach the page. |
| // TODO(esprehn): Can this still happen now that all plugins are out of |
| // process? |
| if (frame() && frame()->page()) |
| frame()->page()->chromeClient().layoutUpdated(frame()); |
| |
| markers().invalidateRectsForAllMarkers(); |
| |
| // The layout system may perform layouts with pending stylesheets. When |
| // recording first layout time, we ignore these layouts, since painting is |
| // suppressed for them. We're interested in tracking the time of the |
| // first real or 'paintable' layout. |
| // TODO(esprehn): This doesn't really make sense, why not track the first |
| // beginFrame? This will catch the first layout in a page that does lots |
| // of layout thrashing even though that layout might not be followed by |
| // a paint for many seconds. |
| if (isRenderingReady() && body() && |
| !styleEngine().hasPendingScriptBlockingSheets()) { |
| if (!m_documentTiming.firstLayout()) |
| m_documentTiming.markFirstLayout(); |
| } |
| |
| m_rootScrollerController->didUpdateLayout(); |
| } |
| |
| void Document::clearFocusedElementSoon() { |
| if (!m_clearFocusedElementTimer.isActive()) |
| m_clearFocusedElementTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void Document::clearFocusedElementTimerFired(TimerBase*) { |
| updateStyleAndLayoutTree(); |
| |
| if (m_focusedElement && !m_focusedElement->isFocusable()) |
| m_focusedElement->blur(); |
| } |
| |
| // FIXME: This is a bad idea and needs to be removed eventually. |
| // Other browsers load stylesheets before they continue parsing the web page. |
| // Since we don't, we can run JavaScript code that needs answers before the |
| // stylesheets are loaded. Doing a layout ignoring the pending stylesheets |
| // lets us get reasonable answers. The long term solution to this problem is |
| // to instead suspend JavaScript execution. |
| void Document::updateStyleAndLayoutTreeIgnorePendingStylesheets() { |
| StyleEngine::IgnoringPendingStylesheet ignoring(styleEngine()); |
| |
| if (styleEngine().hasPendingScriptBlockingSheets()) { |
| // FIXME: We are willing to attempt to suppress painting with outdated style |
| // info only once. Our assumption is that it would be dangerous to try to |
| // stop it a second time, after page content has already been loaded and |
| // displayed with accurate style information. (Our suppression involves |
| // blanking the whole page at the 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(); |
| if (bodyElement && !bodyElement->layoutObject() && |
| m_pendingSheetLayout == NoLayoutWithPendingSheets) { |
| m_pendingSheetLayout = DidLayoutWithPendingSheets; |
| styleEngine().markAllTreeScopesDirty(); |
| } |
| 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 |
| // but here we need up-to-date style immediately. |
| setNeedsStyleRecalc(SubtreeStyleChange, |
| StyleChangeReasonForTracing::create( |
| StyleChangeReason::CleanupPlaceholderStyles)); |
| } |
| } |
| updateStyleAndLayoutTree(); |
| } |
| |
| void Document::updateStyleAndLayoutIgnorePendingStylesheets( |
| Document::RunPostLayoutTasks runPostLayoutTasks) { |
| updateStyleAndLayoutTreeIgnorePendingStylesheets(); |
| updateStyleAndLayout(); |
| |
| if (runPostLayoutTasks == RunPostLayoutTasksSynchronously && view()) |
| view()->flushAnyPendingPostLayoutTasks(); |
| } |
| |
| PassRefPtr<ComputedStyle> Document::styleForElementIgnoringPendingStylesheets( |
| Element* element) { |
| DCHECK_EQ(element->document(), this); |
| StyleEngine::IgnoringPendingStylesheet ignoring(styleEngine()); |
| if (!element->canParticipateInFlatTree()) |
| return ensureStyleResolver().styleForElement(element, nullptr); |
| ContainerNode* parent = LayoutTreeBuilderTraversal::parent(*element); |
| return ensureStyleResolver().styleForElement( |
| element, parent ? parent->ensureComputedStyle() : nullptr); |
| } |
| |
| PassRefPtr<ComputedStyle> Document::styleForPage(int pageIndex) { |
| updateDistribution(); |
| return ensureStyleResolver().styleForPage(pageIndex); |
| } |
| |
| bool Document::isPageBoxVisible(int pageIndex) { |
| return styleForPage(pageIndex)->visibility() != |
| EVisibility::kHidden; // display property doesn't apply to @page. |
| } |
| |
| void Document::pageSizeAndMarginsInPixels(int pageIndex, |
| DoubleSize& pageSize, |
| int& marginTop, |
| int& marginRight, |
| int& marginBottom, |
| int& marginLeft) { |
| RefPtr<ComputedStyle> style = styleForPage(pageIndex); |
| |
| double width = pageSize.width(); |
| double height = pageSize.height(); |
| switch (style->getPageSizeType()) { |
| case PAGE_SIZE_AUTO: |
| break; |
| case PAGE_SIZE_AUTO_LANDSCAPE: |
| if (width < height) |
| std::swap(width, height); |
| break; |
| case PAGE_SIZE_AUTO_PORTRAIT: |
| if (width > height) |
| std::swap(width, height); |
| break; |
| case PAGE_SIZE_RESOLVED: { |
| FloatSize size = style->pageSize(); |
| width = size.width(); |
| height = size.height(); |
| break; |
| } |
| default: |
| NOTREACHED(); |
| } |
| pageSize = DoubleSize(width, height); |
| |
| // 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); |
| 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) { |
| m_isViewSource = isViewSource; |
| if (!m_isViewSource) |
| return; |
| |
| setSecurityOrigin(SecurityOrigin::createUnique()); |
| didUpdateSecurityOrigin(); |
| } |
| |
| void Document::scheduleUseShadowTreeUpdate(SVGUseElement& element) { |
| m_useElementsNeedingUpdate.add(&element); |
| scheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Document::unscheduleUseShadowTreeUpdate(SVGUseElement& element) { |
| m_useElementsNeedingUpdate.remove(&element); |
| } |
| |
| void Document::updateUseShadowTreesIfNeeded() { |
| ScriptForbiddenScope forbidScript; |
| |
| if (m_useElementsNeedingUpdate.isEmpty()) |
| return; |
| |
| HeapHashSet<Member<SVGUseElement>> elements; |
| m_useElementsNeedingUpdate.swap(elements); |
| for (SVGUseElement* element : elements) |
| element->buildPendingResource(); |
| } |
| |
| StyleResolver* Document::styleResolver() const { |
| return m_styleEngine->resolver(); |
| } |
| |
| StyleResolver& Document::ensureStyleResolver() const { |
| return m_styleEngine->ensureResolver(); |
| } |
| |
| void Document::initialize() { |
| DCHECK_EQ(m_lifecycle.state(), DocumentLifecycle::Inactive); |
| DCHECK(!m_axObjectCache || this != &axObjectCacheOwner()); |
| |
| m_layoutView = new LayoutView(this); |
| setLayoutObject(m_layoutView); |
| |
| m_layoutView->setIsInWindow(true); |
| m_layoutView->setStyle(StyleResolver::styleForDocument(*this)); |
| m_layoutView->compositor()->setNeedsCompositingUpdate( |
| CompositingUpdateAfterCompositingInputChange); |
| |
| ContainerNode::attachLayoutTree(); |
| |
| // The TextAutosizer can't update layout view info while the Document is |
| // detached, so update now in case anything changed. |
| if (TextAutosizer* autosizer = textAutosizer()) |
| autosizer->updatePageInfo(); |
| |
| m_frame->documentAttached(); |
| m_lifecycle.advanceTo(DocumentLifecycle::StyleClean); |
| |
| if (view()) |
| view()->didAttachDocument(); |
| } |
| |
| void Document::shutdown() { |
| TRACE_EVENT0("blink", "Document::shutdown"); |
| RELEASE_ASSERT(!m_frame || m_frame->tree().childCount() == 0); |
| if (!isActive()) |
| return; |
| |
| // Frame navigation can cause a new Document to be attached. Don't allow that, |
| // since that will cause a situation where LocalFrame still has a Document |
| // attached after this finishes! Normally, it shouldn't actually be possible |
| // to trigger navigation here. However, plugins (see below) can cause lots of |
| // crazy things to happen, since plugin detach involves nested message loops. |
| FrameNavigationDisabler navigationDisabler(*m_frame); |
| // Defer widget updates to avoid plugins trying to run script inside |
| // ScriptForbiddenScope, which will crash the renderer after |
| // https://crrev.com/200984 |
| HTMLFrameOwnerElement::UpdateSuspendScope suspendWidgetHierarchyUpdates; |
| // Don't allow script to run in the middle of detachLayoutTree() because a |
| // detaching Document is not in a consistent state. |
| ScriptForbiddenScope forbidScript; |
| |
| view()->dispose(); |
| |
| // If the widget of the document's frame owner doesn't match view() then |
| // FrameView::dispose() didn't clear the owner's widget. If we don't clear it |
| // here, it may be clobbered later in LocalFrame::createView(). See also |
| // https://crbug.com/673170 and the comment in FrameView::dispose(). |
| HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner(); |
| if (ownerElement) |
| ownerElement->setWidget(nullptr); |
| |
| m_markers->prepareForDestruction(); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::Stopping); |
| |
| if (page()) |
| page()->documentDetached(this); |
| InspectorInstrumentation::documentDetached(this); |
| |
| if (m_frame->loader().client()->sharedWorkerRepositoryClient()) |
| m_frame->loader() |
| .client() |
| ->sharedWorkerRepositoryClient() |
| ->documentDetached(this); |
| |
| // FIXME: consider using SuspendableObject. |
| if (m_scriptedAnimationController) |
| m_scriptedAnimationController->clearDocumentPointer(); |
| m_scriptedAnimationController.clear(); |
| |
| m_scriptedIdleTaskController.clear(); |
| |
| if (svgExtensions()) |
| accessSVGExtensions().pauseAnimations(); |
| |
| // FIXME: This shouldn't be needed once LocalDOMWindow becomes |
| // ExecutionContext. |
| if (m_domWindow) |
| m_domWindow->clearEventQueue(); |
| |
| if (m_layoutView) |
| m_layoutView->setIsInWindow(false); |
| |
| if (registrationContext()) |
| registrationContext()->documentWasDetached(); |
| |
| m_hoverNode = nullptr; |
| m_activeHoverElement = nullptr; |
| m_autofocusElement = nullptr; |
| |
| if (m_focusedElement.get()) { |
| Element* oldFocusedElement = m_focusedElement; |
| m_focusedElement = nullptr; |
| if (frameHost()) |
| frameHost()->chromeClient().focusedNodeChanged(oldFocusedElement, |
| nullptr); |
| } |
| m_sequentialFocusNavigationStartingPoint = nullptr; |
| |
| if (this == &axObjectCacheOwner()) |
| clearAXObjectCache(); |
| |
| m_layoutView = nullptr; |
| ContainerNode::detachLayoutTree(); |
| |
| if (this != &axObjectCacheOwner()) { |
| if (AXObjectCache* cache = existingAXObjectCache()) { |
| // Documents that are not a root document use the AXObjectCache in |
| // their root document. Node::removedFrom is called after the |
| // document has been detached so it can't find the root document. |
| // We do the removals here instead. |
| for (Node& node : NodeTraversal::descendantsOf(*this)) { |
| cache->remove(&node); |
| } |
| } |
| } |
| |
| styleEngine().didDetach(); |
| |
| frameHost()->eventHandlerRegistry().documentDetached(*this); |
| |
| // Since |Document| class has multiple |LifecycleNotifier| as base class, |
| // we need to have |static_cast<SynchronousMutationNotifier>| here. |
| static_cast<SynchronousMutationNotifier*>(this)->notifyContextDestroyed(); |
| m_frame->selection().documentDetached(*this); |
| |
| // If this Document is associated with a live DocumentLoader, the |
| // DocumentLoader will take care of clearing the FetchContext. Deferring |
| // to the DocumentLoader when possible also prevents prematurely clearing |
| // the context in the case where multiple Documents end up associated with |
| // a single DocumentLoader (e.g., navigating to a javascript: url). |
| if (!loader()) |
| m_fetcher->clearContext(); |
| // If this document is the master for an HTMLImportsController, sever that |
| // relationship. This ensures that we don't leave import loads in flight, |
| // thinking they should have access to a valid frame when they don't. |
| if (m_importsController) { |
| m_importsController->dispose(); |
| clearImportsController(); |
| } |
| |
| m_timers.setTimerTaskRunner( |
| Platform::current()->currentThread()->scheduler()->timerTaskRunner()); |
| |
| if (m_mediaQueryMatcher) |
| m_mediaQueryMatcher->documentDetached(); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::Stopped); |
| |
| // TODO(haraken): Call contextDestroyed() before we start any disruptive |
| // operations. |
| // TODO(haraken): Currently we call notifyContextDestroyed() only in |
| // Document::detachLayoutTree(), which means that we don't call |
| // notifyContextDestroyed() for a document that doesn't get detached. |
| // If such a document has any observer, the observer won't get |
| // a contextDestroyed() notification. This can happen for a document |
| // created by DOMImplementation::createDocument(). |
| ExecutionContext::notifyContextDestroyed(); |
| |
| // This is required, as our LocalFrame might delete itself as soon as it |
| // detaches us. However, this violates Node::detachLayoutTree() semantics, as |
| // it's never possible to re-attach. Eventually Document::detachLayoutTree() |
| // should be renamed, or this setting of the frame to 0 could be made |
| // explicit in each of the callers of Document::detachLayoutTree(). |
| m_frame = nullptr; |
| } |
| |
| void Document::removeAllEventListeners() { |
| ContainerNode::removeAllEventListeners(); |
| |
| if (LocalDOMWindow* domWindow = this->domWindow()) |
| domWindow->removeAllEventListeners(); |
| } |
| |
| Document& Document::axObjectCacheOwner() const { |
| // Every document has its own axObjectCache if accessibility is enabled, |
| // except for page popups, which share the axObjectCache of their owner. |
| Document* doc = const_cast<Document*>(this); |
| if (doc->frame() && doc->frame()->pagePopupOwner()) { |
| DCHECK(!doc->m_axObjectCache); |
| return doc->frame()->pagePopupOwner()->document().axObjectCacheOwner(); |
| } |
| return *doc; |
| } |
| |
| void Document::clearAXObjectCache() { |
| DCHECK_EQ(&axObjectCacheOwner(), this); |
| // Clear the cache member variable before calling delete because attempts |
| // are made to access it during destruction. |
| if (m_axObjectCache) |
| m_axObjectCache->dispose(); |
| m_axObjectCache.clear(); |
| } |
| |
| AXObjectCache* Document::existingAXObjectCache() const { |
| // If the layoutObject is gone then we are in the process of destruction. |
| // This method will be called before m_frame = nullptr. |
| if (!axObjectCacheOwner().layoutView()) |
| return 0; |
| |
| return axObjectCacheOwner().m_axObjectCache.get(); |
| } |
| |
| AXObjectCache* Document::axObjectCache() const { |
| Settings* settings = this->settings(); |
| if (!settings || !settings->getAccessibilityEnabled()) |
| return 0; |
| |
| // The only document that actually has a AXObjectCache is the top-level |
| // document. This is because we need to be able to get from any |
| // WebCoreAXObject to any other WebCoreAXObject on the same page. Using a |
| // single cache allows lookups across nested webareas (i.e. multiple |
| // documents). |
| Document& cacheOwner = this->axObjectCacheOwner(); |
| |
| // If the document has already been detached, do not make a new axObjectCache. |
| if (!cacheOwner.layoutView()) |
| return 0; |
| |
| DCHECK(&cacheOwner == this || !m_axObjectCache); |
| if (!cacheOwner.m_axObjectCache) |
| cacheOwner.m_axObjectCache = AXObjectCache::create(cacheOwner); |
| return cacheOwner.m_axObjectCache.get(); |
| } |
| |
| CanvasFontCache* Document::canvasFontCache() { |
| if (!m_canvasFontCache) |
| m_canvasFontCache = CanvasFontCache::create(*this); |
| |
| return m_canvasFontCache.get(); |
| } |
| |
| DocumentParser* Document::createParser() { |
| if (isHTMLDocument()) |
| return HTMLDocumentParser::create(toHTMLDocument(*this), |
| m_parserSyncPolicy); |
| // FIXME: this should probably pass the frame instead |
| return XMLDocumentParser::create(*this, view()); |
| } |
| |
| bool Document::isFrameSet() const { |
| if (!isHTMLDocument()) |
| return false; |
| return isHTMLFrameSetElement(body()); |
| } |
| |
| ScriptableDocumentParser* Document::scriptableDocumentParser() const { |
| return parser() ? parser()->asScriptableDocumentParser() : nullptr; |
| } |
| |
| void Document::open(Document* enteredDocument, ExceptionState& exceptionState) { |
| if (importLoader()) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Imported document doesn't support open()."); |
| return; |
| } |
| |
| if (!isHTMLDocument()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "Only HTML documents support open()."); |
| return; |
| } |
| |
| if (m_throwOnDynamicMarkupInsertionCount) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Custom Element constructor should not use open()."); |
| return; |
| } |
| |
| if (enteredDocument) { |
| if (!getSecurityOrigin()->canAccess(enteredDocument->getSecurityOrigin())) { |
| exceptionState.throwSecurityError( |
| "Can only call open() on same-origin documents."); |
| return; |
| } |
| setSecurityOrigin(enteredDocument->getSecurityOrigin()); |
| setURL(enteredDocument->url()); |
| m_cookieURL = enteredDocument->cookieURL(); |
| } |
| |
| open(); |
| } |
| |
| void Document::open() { |
| DCHECK(!importLoader()); |
| |
| if (m_frame) { |
| if (ScriptableDocumentParser* parser = scriptableDocumentParser()) { |
| if (parser->isParsing()) { |
| // FIXME: HTML5 doesn't tell us to check this, it might not be correct. |
| if (parser->isExecutingScript()) |
| return; |
| |
| if (!parser->wasCreatedByScript() && parser->hasInsertionPoint()) |
| return; |
| } |
| } |
| |
| // PlzNavigate: We should abort ongoing navigations handled by the client. |
| if (m_frame->loader().hasProvisionalNavigation()) |
| m_frame->loader().stopAllLoaders(); |
| } |
| |
| removeAllEventListenersRecursively(); |
| implicitOpen(ForceSynchronousParsing); |
| if (ScriptableDocumentParser* parser = scriptableDocumentParser()) |
| parser->setWasCreatedByScript(true); |
| |
| if (m_frame) |
| m_frame->loader().didExplicitOpen(); |
| if (m_loadEventProgress != LoadEventInProgress && |
| pageDismissalEventBeingDispatched() == NoDismissal) |
| m_loadEventProgress = LoadEventNotRun; |
| } |
| |
| void Document::detachParser() { |
| if (!m_parser) |
| return; |
| m_parser->detach(); |
| m_parser.clear(); |
| DocumentParserTiming::from(*this).markParserDetached(); |
| } |
| |
| void Document::cancelParsing() { |
| detachParser(); |
| setParsingState(FinishedParsing); |
| setReadyState(Complete); |
| } |
| |
| DocumentParser* Document::implicitOpen( |
| ParserSynchronizationPolicy parserSyncPolicy) { |
| detachParser(); |
| |
| removeChildren(); |
| DCHECK(!m_focusedElement); |
| |
| setCompatibilityMode(NoQuirksMode); |
| |
| if (!threadedParsingEnabledForTesting()) { |
| parserSyncPolicy = ForceSynchronousParsing; |
| } else if (parserSyncPolicy == AllowAsynchronousParsing && isPrefetchOnly()) { |
| // Prefetch must be synchronous. |
| parserSyncPolicy = ForceSynchronousParsing; |
| } |
| |
| m_parserSyncPolicy = parserSyncPolicy; |
| m_parser = createParser(); |
| DocumentParserTiming::from(*this).markParserStart(); |
| setParsingState(Parsing); |
| setReadyState(Loading); |
| |
| return m_parser; |
| } |
| |
| HTMLElement* Document::body() const { |
| if (!documentElement() || !isHTMLHtmlElement(documentElement())) |
| return 0; |
| |
| for (HTMLElement* child = |
| Traversal<HTMLElement>::firstChild(*documentElement()); |
| child; child = Traversal<HTMLElement>::nextSibling(*child)) { |
| if (isHTMLFrameSetElement(*child) || isHTMLBodyElement(*child)) |
| return child; |
| } |
| |
| return 0; |
| } |
| |
| HTMLBodyElement* Document::firstBodyElement() const { |
| if (!documentElement()) |
| return 0; |
| |
| for (HTMLElement* child = |
| Traversal<HTMLElement>::firstChild(*documentElement()); |
| child; child = Traversal<HTMLElement>::nextSibling(*child)) { |
| if (isHTMLBodyElement(*child)) |
| return toHTMLBodyElement(child); |
| } |
| |
| return 0; |
| } |
| |
| void Document::setBody(HTMLElement* prpNewBody, |
| ExceptionState& exceptionState) { |
| HTMLElement* newBody = prpNewBody; |
| |
| if (!newBody) { |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| ExceptionMessages::argumentNullOrIncorrectType(1, "HTMLElement")); |
| return; |
| } |
| if (!documentElement()) { |
| exceptionState.throwDOMException(HierarchyRequestError, |
| "No document element exists."); |
| return; |
| } |
| |
| if (!isHTMLBodyElement(*newBody) && !isHTMLFrameSetElement(*newBody)) { |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| "The new body element is of type '" + newBody->tagName() + |
| "'. It must be either a 'BODY' or 'FRAMESET' element."); |
| return; |
| } |
| |
| HTMLElement* oldBody = body(); |
| if (oldBody == newBody) |
| return; |
| |
| if (oldBody) |
| documentElement()->replaceChild(newBody, oldBody, exceptionState); |
| else |
| documentElement()->appendChild(newBody, exceptionState); |
| } |
| |
| void Document::willInsertBody() { |
| if (frame()) |
| frame()->loader().client()->dispatchWillInsertBody(); |
| // If we get to the <body> try to resume commits since we should have content |
| // to paint now. |
| // TODO(esprehn): Is this really optimal? We might start producing frames |
| // for very little content, should we wait for some heuristic like |
| // isVisuallyNonEmpty() ? |
| beginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| HTMLHeadElement* Document::head() const { |
| Node* de = documentElement(); |
| if (!de) |
| return 0; |
| |
| return Traversal<HTMLHeadElement>::firstChild(*de); |
| } |
| |
| Element* Document::viewportDefiningElement( |
| const ComputedStyle* rootStyle) const { |
| // If a BODY element sets non-visible overflow, it is to be propagated to the |
| // viewport, as long as the following conditions are all met: |
| // (1) The root element is HTML. |
| // (2) It is the primary BODY element (we only assert for this, expecting |
| // callers to behave). |
| // (3) The root element has visible overflow. |
| // Otherwise it's the root element's properties that are to be propagated. |
| Element* rootElement = documentElement(); |
| Element* bodyElement = body(); |
| if (!rootElement) |
| return 0; |
| if (!rootStyle) { |
| rootStyle = rootElement->computedStyle(); |
| if (!rootStyle) |
| return 0; |
| } |
| if (bodyElement && rootStyle->isOverflowVisible() && |
| isHTMLHtmlElement(*rootElement)) |
| return bodyElement; |
| return rootElement; |
| } |
| |
| void Document::close(ExceptionState& exceptionState) { |
| // FIXME: We should follow the specification more closely: |
| // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-close |
| |
| if (importLoader()) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Imported document doesn't support close()."); |
| return; |
| } |
| |
| if (!isHTMLDocument()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "Only HTML documents support close()."); |
| return; |
| } |
| |
| if (m_throwOnDynamicMarkupInsertionCount) { |
| exceptionState.throwDOMException( |
| InvalidStateError, |
| "Custom Element constructor should not use close()."); |
| return; |
| } |
| |
| close(); |
| } |
| |
| void Document::close() { |
| if (!scriptableDocumentParser() || |
| !scriptableDocumentParser()->wasCreatedByScript() || |
| !scriptableDocumentParser()->isParsing()) |
| return; |
| |
| if (DocumentParser* parser = m_parser) |
| parser->finish(); |
| |
| if (!m_frame) { |
| // Because we have no frame, we don't know if all loading has completed, |
| // so we just call implicitClose() immediately. FIXME: This might fire |
| // the load event prematurely |
| // <http://bugs.webkit.org/show_bug.cgi?id=14568>. |
| implicitClose(); |
| return; |
| } |
| |
| m_frame->loader().checkCompleted(); |
| } |
| |
| void Document::implicitClose() { |
| DCHECK(!inStyleRecalc()); |
| if (processingLoadEvent() || !m_parser) |
| return; |
| if (frame() && frame()->navigationScheduler().locationChangePending()) { |
| suppressLoadEvent(); |
| return; |
| } |
| |
| m_loadEventProgress = LoadEventInProgress; |
| |
| ScriptableDocumentParser* parser = scriptableDocumentParser(); |
| m_wellFormed = parser && parser->wellFormed(); |
| |
| // We have to clear the parser, in case someone document.write()s from the |
| // onLoad event handler, as in Radar 3206524. |
| detachParser(); |
| |
| if (frame() && frame()->script().canExecuteScripts(NotAboutToExecuteScript)) { |
| ImageLoader::dispatchPendingLoadEvents(); |
| ImageLoader::dispatchPendingErrorEvents(); |
| } |
| |
| // JS running below could remove the frame or destroy the LayoutView so we |
| // call those two functions repeatedly and don't save them on the stack. |
| |
| // 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 (svgExtensions()) |
| accessSVGExtensions().dispatchSVGLoadEventToOutermostSVGElements(); |
| |
| if (this->domWindow()) |
| this->domWindow()->documentWasClosed(); |
| |
| if (frame()) { |
| frame()->loader().client()->dispatchDidHandleOnloadEvents(); |
| loader()->applicationCacheHost()->stopDeferringEvents(); |
| } |
| |
| if (!frame()) { |
| m_loadEventProgress = LoadEventCompleted; |
| return; |
| } |
| |
| // Make sure both the initial layout and reflow happen after the onload |
| // fires. This will improve onload scores, and other browsers do it. |
| // If they wanna cheat, we can too. -dwh |
| |
| if (frame()->navigationScheduler().locationChangePending() && |
| elapsedTime() < cLayoutScheduleThreshold) { |
| // 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_loadEventProgress = LoadEventCompleted; |
| return; |
| } |
| |
| // We used to force a synchronous display and flush here. This really isn't |
| // necessary and can in fact be actively harmful if pages are loading at a |
| // rate of > 60fps |
| // (if your platform is syncing flushes and limiting them to 60fps). |
| if (!localOwner() || (localOwner()->layoutObject() && |
| !localOwner()->layoutObject()->needsLayout())) { |
| updateStyleAndLayoutTree(); |
| |
| // Always do a layout after loading if needed. |
| if (view() && !layoutViewItem().isNull() && |
| (!layoutViewItem().firstChild() || layoutViewItem().needsLayout())) |
| view()->layout(); |
| } |
| |
| m_loadEventProgress = LoadEventCompleted; |
| |
| if (frame() && !layoutViewItem().isNull() && |
| settings()->getAccessibilityEnabled()) { |
| if (AXObjectCache* cache = axObjectCache()) { |
| if (this == &axObjectCacheOwner()) |
| cache->handleLoadComplete(this); |
| else |
| cache->handleLayoutComplete(this); |
| } |
| } |
| |
| if (svgExtensions()) |
| accessSVGExtensions().startAnimations(); |
| } |
| |
| bool Document::dispatchBeforeUnloadEvent(ChromeClient& chromeClient, |
| bool isReload, |
| bool& didAllowNavigation) { |
| if (!m_domWindow) |
| return true; |
| |
| if (!body()) |
| return true; |
| |
| if (processingBeforeUnload()) |
| return false; |
| |
| BeforeUnloadEvent* beforeUnloadEvent = BeforeUnloadEvent::create(); |
| m_loadEventProgress = BeforeUnloadEventInProgress; |
| m_domWindow->dispatchEvent(beforeUnloadEvent, this); |
| m_loadEventProgress = BeforeUnloadEventCompleted; |
| if (!beforeUnloadEvent->defaultPrevented()) |
| defaultEventHandler(beforeUnloadEvent); |
| if (!frame() || beforeUnloadEvent->returnValue().isNull()) |
| return true; |
| |
| if (didAllowNavigation) { |
| addConsoleMessage(ConsoleMessage::create( |
| JSMessageSource, ErrorMessageLevel, |
| "Blocked attempt to show multiple 'beforeunload' confirmation panels " |
| "for a single navigation.")); |
| return true; |
| } |
| |
| String text = beforeUnloadEvent->returnValue(); |
| if (chromeClient.openBeforeUnloadConfirmPanel(text, m_frame, isReload)) { |
| didAllowNavigation = true; |
| return true; |
| } |
| return false; |
| } |
| |
| void Document::dispatchUnloadEvents() { |
| PluginScriptForbiddenScope forbidPluginDestructorScripting; |
| if (m_parser) |
| m_parser->stopParsing(); |
| |
| if (m_loadEventProgress == LoadEventNotRun) |
| return; |
| |
| if (m_loadEventProgress <= UnloadEventInProgress) { |
| Element* currentFocusedElement = focusedElement(); |
| if (isHTMLInputElement(currentFocusedElement)) |
| toHTMLInputElement(*currentFocusedElement).endEditing(); |
| if (m_loadEventProgress < PageHideInProgress) { |
| m_loadEventProgress = PageHideInProgress; |
| if (LocalDOMWindow* window = domWindow()) |
| window->dispatchEvent( |
| PageTransitionEvent::create(EventTypeNames::pagehide, false), this); |
| if (!m_frame) |
| return; |
| |
| PageVisibilityState visibilityState = pageVisibilityState(); |
| m_loadEventProgress = UnloadVisibilityChangeInProgress; |
| if (visibilityState != PageVisibilityStateHidden && |
| RuntimeEnabledFeatures::visibilityChangeOnUnloadEnabled()) { |
| // Dispatch visibilitychange event, but don't bother doing |
| // other notifications as we're about to be unloaded. |
| dispatchEvent(Event::createBubble(EventTypeNames::visibilitychange)); |
| dispatchEvent( |
| Event::createBubble(EventTypeNames::webkitvisibilitychange)); |
| } |
| if (!m_frame) |
| return; |
| |
| DocumentLoader* documentLoader = |
| m_frame->loader().provisionalDocumentLoader(); |
| m_loadEventProgress = UnloadEventInProgress; |
| Event* unloadEvent(Event::create(EventTypeNames::unload)); |
| if (documentLoader && !documentLoader->timing().unloadEventStart() && |
| !documentLoader->timing().unloadEventEnd()) { |
| DocumentLoadTiming& timing = documentLoader->timing(); |
| DCHECK(timing.navigationStart()); |
| timing.markUnloadEventStart(); |
| m_frame->domWindow()->dispatchEvent(unloadEvent, this); |
| timing.markUnloadEventEnd(); |
| } else { |
| m_frame->domWindow()->dispatchEvent(unloadEvent, m_frame->document()); |
| } |
| } |
| m_loadEventProgress = UnloadEventHandled; |
| } |
| |
| if (!m_frame) |
| return; |
| |
| // Don't remove event listeners from a transitional empty document (see |
| // https://bugs.webkit.org/show_bug.cgi?id=28716 for more information). |
| bool keepEventListeners = |
| m_frame->loader().provisionalDocumentLoader() && |
| m_frame->shouldReuseDefaultView( |
| m_frame->loader().provisionalDocumentLoader()->url()); |
| if (!keepEventListeners) |
| removeAllEventListenersRecursively(); |
| } |
| |
| Document::PageDismissalType Document::pageDismissalEventBeingDispatched() |
| const { |
| switch (m_loadEventProgress) { |
| case BeforeUnloadEventInProgress: |
| return BeforeUnloadDismissal; |
| case PageHideInProgress: |
| return PageHideDismissal; |
| case UnloadVisibilityChangeInProgress: |
| return UnloadVisibilityChangeDismissal; |
| case UnloadEventInProgress: |
| return UnloadDismissal; |
| |
| case LoadEventNotRun: |
| case LoadEventInProgress: |
| case LoadEventCompleted: |
| case BeforeUnloadEventCompleted: |
| case UnloadEventHandled: |
| return NoDismissal; |
| } |
| NOTREACHED(); |
| return NoDismissal; |
| } |
| |
| void Document::setParsingState(ParsingState parsingState) { |
| m_parsingState = parsingState; |
| |
| if (parsing() && !m_elementDataCache) |
| m_elementDataCache = ElementDataCache::create(); |
| } |
| |
| bool Document::shouldScheduleLayout() const { |
| // This function will only be called when FrameView thinks a layout is needed. |
| // This enforces a couple extra rules. |
| // |
| // (a) Only schedule a layout once the stylesheets are loaded. |
| // (b) Only schedule layout once we have a body element. |
| if (!isActive()) |
| return false; |
| |
| if (isRenderingReady() && body()) |
| return true; |
| |
| if (documentElement() && !isHTMLHtmlElement(*documentElement())) |
| return true; |
| |
| return false; |
| } |
| |
| int Document::elapsedTime() const { |
| return static_cast<int>((currentTime() - m_startTime) * 1000); |
| } |
| |
| void Document::write(const SegmentedString& text, |
| Document* enteredDocument, |
| ExceptionState& exceptionState) { |
| if (importLoader()) { |
| exceptionState.throwDOMException( |
| InvalidStateError, "Imported document doesn't support write()."); |
| return; |
| } |
| |
| if (!isHTMLDocument()) { |
| exceptionState.throwDOMException(InvalidStateError, |
| "Only HTML documents support write()."); |
| return; |
| } |
| |
| if (m_throwOnDynamicMarkupInsertionCount) { |
| exceptionState.throwDOMException( |
| InvalidStateError, |
| "Custom Element constructor should not use write()."); |
| return; |
| } |
| |
| if (enteredDocument && |
| !getSecurityOrigin()->canAccess(enteredDocument->getSecurityOrigin())) { |
| exceptionState.throwSecurityError( |
| "Can only call write() on same-origin documents."); |
| return; |
| } |
| |
| NestingLevelIncrementer nestingLevelIncrementer(m_writeRecursionDepth); |
| |
| m_writeRecursionIsTooDeep = |
| (m_writeRecursionDepth > 1) && m_writeRecursionIsTooDeep; |
| m_writeRecursionIsTooDeep = |
| (m_writeRecursionDepth > cMaxWriteRecursionDepth) || |
| m_writeRecursionIsTooDeep; |
| |
| if (m_writeRecursionIsTooDeep) |
| return; |
| |
| bool hasInsertionPoint = m_parser && m_parser->hasInsertionPoint(); |
| |
| if (!hasInsertionPoint && m_ignoreDestructiveWriteCount) { |
| addConsoleMessage( |
| ConsoleMessage::create(JSMessageSource, WarningMessageLevel, |
| ExceptionMessages::failedToExecute( |
| "write", "Document", |
| "It isn't possible to write into a document " |
| "from an asynchronously-loaded external " |
| "script unless it is explicitly opened."))); |
| return; |
| } |
| |
| if (!hasInsertionPoint) |
| open(enteredDocument, ASSERT_NO_EXCEPTION); |
| |
| DCHECK(m_parser); |
| InspectorInstrumentation::NativeBreakpoint nativeBreakpoint( |
| this, "document.write", true); |
| m_parser->insert(text); |
| } |
| |
| void Document::write(const String& text, |
| Document* enteredDocument, |
| ExceptionState& exceptionState) { |
| write(SegmentedString(text), enteredDocument, exceptionState); |
| } |
| |
| void Document::writeln(const String& text, |
| Document* enteredDocument, |
| ExceptionState& exceptionState) { |
| write(text, enteredDocument, exceptionState); |
| if (exceptionState.hadException()) |
| return; |
| write("\n", enteredDocument); |
| } |
| |
| void Document::write(LocalDOMWindow* callingWindow, |
| const Vector<String>& text, |
| ExceptionState& exceptionState) { |
| DCHECK(callingWindow); |
| StringBuilder builder; |
| for (const String& string : text) |
| builder.append(string); |
| write(builder.toString(), callingWindow->document(), exceptionState); |
| } |
| |
| void Document::writeln(LocalDOMWindow* callingWindow, |
| const Vector<String>& text, |
| ExceptionState& exceptionState) { |
| DCHECK(callingWindow); |
| StringBuilder builder; |
| for (const String& string : text) |
| builder.append(string); |
| writeln(builder.toString(), callingWindow->document(), exceptionState); |
| } |
| |
| const KURL& Document::virtualURL() const { |
| return m_url; |
| } |
| |
| KURL Document::virtualCompleteURL(const String& url) const { |
| return completeURL(url); |
| } |
| |
| DOMTimerCoordinator* Document::timers() { |
| return &m_timers; |
| } |
| |
| EventTarget* Document::errorEventTarget() { |
| return domWindow(); |
| } |
| |
| void Document::exceptionThrown(ErrorEvent* event) { |
| MainThreadDebugger::instance()->exceptionThrown(this, event); |
| } |
| |
| void Document::setURL(const KURL& url) { |
| const KURL& newURL = url.isEmpty() ? blankURL() : url; |
| if (newURL == m_url) |
| return; |
| |
| m_url = newURL; |
| m_accessEntryFromURL = nullptr; |
| updateBaseURL(); |
| contextFeatures().urlDidChange(this); |
| } |
| |
| KURL Document::validBaseElementURL() const { |
| if (m_baseElementURL.isValid()) |
| return m_baseElementURL; |
| |
| return KURL(); |
| } |
| |
| void Document::updateBaseURL() { |
| KURL oldBaseURL = m_baseURL; |
| // DOM 3 Core: When the Document supports the feature "HTML" [DOM Level 2 |
| // HTML], the base URI is computed using first the value of the href attribute |
| // of the HTML BASE element if any, and the value of the documentURI attribute |
| // from the Document interface otherwise (which we store, preparsed, in |
| // m_url). |
| if (!m_baseElementURL.isEmpty()) |
| m_baseURL = m_baseElementURL; |
| else if (!m_baseURLOverride.isEmpty()) |
| m_baseURL = m_baseURLOverride; |
| else |
| m_baseURL = m_url; |
| |
| selectorQueryCache().invalidate(); |
| |
| if (!m_baseURL.isValid()) |
| m_baseURL = KURL(); |
| |
| if (m_elemSheet) { |
| // Element sheet is silly. It never contains anything. |
| DCHECK(!m_elemSheet->contents()->ruleCount()); |
| m_elemSheet = CSSStyleSheet::createInline(*this, m_baseURL); |
| } |
| |
| 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. |
| for (HTMLAnchorElement& anchor : |
| Traversal<HTMLAnchorElement>::startsAfter(*this)) |
| anchor.invalidateCachedVisitedLinkHash(); |
| } |
| } |
| |
| void Document::setBaseURLOverride(const KURL& url) { |
| m_baseURLOverride = url; |
| updateBaseURL(); |
| } |
| |
| void Document::processBaseElement() { |
| UseCounter::count(*this, UseCounter::BaseElement); |
| |
| // Find the first href attribute in a base element and the first target |
| // attribute in a base element. |
| const AtomicString* href = 0; |
| const AtomicString* target = 0; |
| for (HTMLBaseElement* base = Traversal<HTMLBaseElement>::firstWithin(*this); |
| base && (!href || !target); |
| base = Traversal<HTMLBaseElement>::next(*base)) { |
| if (!href) { |
| const AtomicString& value = base->fastGetAttribute(hrefAttr); |
| if (!value.isNull()) |
| href = &value; |
| } |
| if (!target) { |
| const AtomicString& value = base->fastGetAttribute(targetAttr); |
| if (!value.isNull()) |
| target = &value; |
| } |
| if (contentSecurityPolicy()->isActive()) { |
| UseCounter::count(*this, |
| UseCounter::ContentSecurityPolicyWithBaseElement); |
| } |
| } |
| |
| // FIXME: Since this doesn't share code with completeURL it may not handle |
| // encodings correctly. |
| KURL baseElementURL; |
| if (href) { |
| String strippedHref = stripLeadingAndTrailingHTMLSpaces(*href); |
| if (!strippedHref.isEmpty()) |
| baseElementURL = KURL(url(), strippedHref); |
| } |
| |
| if (!baseElementURL.isEmpty()) { |
| if (baseElementURL.protocolIsData()) |
| UseCounter::count(*this, UseCounter::BaseWithDataHref); |
| if (!this->getSecurityOrigin()->canRequest(baseElementURL)) |
| UseCounter::count(*this, UseCounter::BaseWithCrossOriginHref); |
| } |
| |
| if (m_baseElementURL != baseElementURL && |
| contentSecurityPolicy()->allowBaseURI(baseElementURL)) { |
| m_baseElementURL = baseElementURL; |
| updateBaseURL(); |
| } |
| |
| if (target) { |
| if (target->contains('\n') || target->contains('\r')) |
| UseCounter::count(*this, UseCounter::BaseWithNewlinesInTarget); |
| if (target->contains('<')) |
| UseCounter::count(*this, UseCounter::BaseWithOpenBracketInTarget); |
| m_baseTarget = *target; |
| } else { |
| m_baseTarget = nullAtom; |
| } |
| } |
| |
| String Document::userAgent() const { |
| return frame() ? frame()->loader().userAgent() : String(); |
| } |
| |
| void Document::disableEval(const String& errorMessage) { |
| if (!frame()) |
| return; |
| |
| frame()->script().disableEval(errorMessage); |
| } |
| |
| void Document::didLoadAllImports() { |
| if (!haveScriptBlockingStylesheetsLoaded()) |
| return; |
| if (!importLoader()) |
| styleResolverMayHaveChanged(); |
| didLoadAllScriptBlockingResources(); |
| } |
| |
| void Document::didRemoveAllPendingStylesheet() { |
| styleResolverMayHaveChanged(); |
| |
| // Only imports on master documents can trigger rendering. |
| if (HTMLImportLoader* import = importLoader()) |
| import->didRemoveAllPendingStylesheet(); |
| if (!haveImportsLoaded()) |
| return; |
| didLoadAllScriptBlockingResources(); |
| } |
| |
| void Document::didLoadAllScriptBlockingResources() { |
| // Use wrapWeakPersistent because the task should not keep this Document alive |
| // just for executing scripts. |
| m_executeScriptsWaitingForResourcesTaskHandle = |
| TaskRunnerHelper::get(TaskType::Networking, this) |
| ->postCancellableTask( |
| BLINK_FROM_HERE, |
| WTF::bind(&Document::executeScriptsWaitingForResources, |
| wrapWeakPersistent(this))); |
| |
| if (isHTMLDocument() && body()) { |
| // For HTML if we have no more stylesheets to load and we're past the body |
| // tag, we should have something to paint so resume. |
| beginLifecycleUpdatesIfRenderingReady(); |
| } else if (!isHTMLDocument() && documentElement()) { |
| // For non-HTML there is no body so resume as soon as the sheets are loaded. |
| beginLifecycleUpdatesIfRenderingReady(); |
| } |
| |
| if (m_gotoAnchorNeededAfterStylesheetsLoad && view()) |
| view()->processUrlFragment(m_url); |
| } |
| |
| void Document::executeScriptsWaitingForResources() { |
| if (!isScriptExecutionReady()) |
| return; |
| if (ScriptableDocumentParser* parser = scriptableDocumentParser()) |
| parser->executeScriptsWaitingForResources(); |
| } |
| |
| CSSStyleSheet& Document::elementSheet() { |
| if (!m_elemSheet) |
| m_elemSheet = CSSStyleSheet::createInline(*this, m_baseURL); |
| return *m_elemSheet; |
| } |
| |
| void Document::maybeHandleHttpRefresh(const String& content, |
| HttpRefreshType httpRefreshType) { |
| if (m_isViewSource || !m_frame) |
| return; |
| |
| double delay; |
| String refreshURL; |
| if (!parseHTTPRefresh(content, httpRefreshType == HttpRefreshFromMetaTag |
| ? isHTMLSpace<UChar> |
| : nullptr, |
| delay, refreshURL)) |
| return; |
| if (refreshURL.isEmpty()) |
| refreshURL = url().getString(); |
| else |
| refreshURL = completeURL(refreshURL).getString(); |
| |
| if (protocolIsJavaScript(refreshURL)) { |
| String message = |
| "Refused to refresh " + m_url.elidedString() + " to a javascript: URL"; |
| addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, |
| ErrorMessageLevel, message)); |
| return; |
| } |
| |
| if (httpRefreshType == HttpRefreshFromMetaTag && |
| isSandboxed(SandboxAutomaticFeatures)) { |
| String message = |
| "Refused to execute the redirect specified via '<meta " |
| "http-equiv='refresh' content='...'>'. The document is sandboxed, and " |
| "the 'allow-scripts' keyword is not set."; |
| addConsoleMessage(ConsoleMessage::create(SecurityMessageSource, |
| ErrorMessageLevel, message)); |
| return; |
| } |
| m_frame->navigationScheduler().scheduleRedirect(delay, refreshURL); |
| } |
| |
| bool Document::shouldMergeWithLegacyDescription( |
| ViewportDescription::Type origin) const { |
| return settings() && settings()->getViewportMetaMergeContentQuirk() && |
| m_legacyViewportDescription.isMetaViewportType() && |
| m_legacyViewportDescription.type == origin; |
| } |
| |
| void Document::setViewportDescription( |
| const ViewportDescription& viewportDescription) { |
| if (viewportDescription.isLegacyViewportType()) { |
| if (viewportDescription == m_legacyViewportDescription) |
| return; |
| m_legacyViewportDescription = viewportDescription; |
| } else { |
| if (viewportDescription == m_viewportDescription) |
| return; |
| m_viewportDescription = viewportDescription; |
| |
| // The UA-defined min-width is considered specifically by Android WebView |
| // quirks mode. |
| if (!viewportDescription.isSpecifiedByAuthor()) |
| m_viewportDefaultMinWidth = viewportDescription.minWidth; |
| } |
| |
| updateViewportDescription(); |
| } |
| |
| ViewportDescription Document::viewportDescription() const { |
| ViewportDescription appliedViewportDescription = m_viewportDescription; |
| bool viewportMetaEnabled = settings() && settings()->getViewportMetaEnabled(); |
| if (m_legacyViewportDescription.type != |
| ViewportDescription::UserAgentStyleSheet && |
| viewportMetaEnabled) |
| appliedViewportDescription = m_legacyViewportDescription; |
| if (shouldOverrideLegacyDescription(m_viewportDescription.type)) |
| appliedViewportDescription = m_viewportDescription; |
| |
| return appliedViewportDescription; |
| } |
| |
| void Document::updateViewportDescription() { |
| if (frame() && frame()->isMainFrame()) { |
| frameHost()->chromeClient().dispatchViewportPropertiesDidChange( |
| viewportDescription()); |
| } |
| } |
| |
| String Document::outgoingReferrer() const { |
| if (getSecurityOrigin()->isUnique()) { |
| // Return |no-referrer|. |
| return String(); |
| } |
| |
| // See http://www.whatwg.org/specs/web-apps/current-work/#fetching-resources |
| // for why we walk the parent chain for srcdoc documents. |
| const Document* referrerDocument = this; |
| if (LocalFrame* frame = m_frame) { |
| while (frame->document()->isSrcdocDocument()) { |
| // Srcdoc documents must be local within the containing frame. |
| frame = toLocalFrame(frame->tree().parent()); |
| // Srcdoc documents cannot be top-level documents, by definition, |
| // because they need to be contained in iframes with the srcdoc. |
| DCHECK(frame); |
| } |
| referrerDocument = frame->document(); |
| } |
| return referrerDocument->m_url.strippedForUseAsReferrer(); |
| } |
| |
| ReferrerPolicy Document::getReferrerPolicy() const { |
| ReferrerPolicy policy = ExecutionContext::getReferrerPolicy(); |
| // For srcdoc documents without their own policy, walk up the frame |
| // tree to find the document that is either not a srcdoc or doesn't |
| // have its own policy. This algorithm is defined in |
| // https://html.spec.whatwg.org/multipage/browsers.html#set-up-a-browsing-context-environment-settings-object. |
| if (!m_frame || policy != ReferrerPolicyDefault || !isSrcdocDocument()) { |
| return policy; |
| } |
| LocalFrame* frame = toLocalFrame(m_frame->tree().parent()); |
| DCHECK(frame); |
| return frame->document()->getReferrerPolicy(); |
| } |
| |
| MouseEventWithHitTestResults Document::performMouseEventHitTest( |
| const HitTestRequest& request, |
| const LayoutPoint& documentPoint, |
| const PlatformMouseEvent& event) { |
| DCHECK(layoutViewItem().isNull() || layoutViewItem().isLayoutView()); |
| |
| // LayoutView::hitTest causes a layout, and we don't want to hit that until |
| // the first layout because until then, there is nothing shown on the screen - |
| // the user can't have intentionally clicked on something belonging to this |
| // page. Furthermore, mousemove events before the first layout should not |
| // lead to a premature layout() happening, which could show a flash of white. |
| // See also the similar code in EventHandler::hitTestResultAtPoint. |
| if (layoutViewItem().isNull() || !view() || !view()->didFirstLayout()) |
| return MouseEventWithHitTestResults(event, |
| HitTestResult(request, LayoutPoint())); |
| |
| HitTestResult result(request, documentPoint); |
| layoutViewItem().hitTest(result); |
| |
| if (!request.readOnly()) |
| updateHoverActiveState(request, result.innerElement(), result.scrollbar()); |
| |
| if (isHTMLCanvasElement(result.innerNode())) { |
| PlatformMouseEvent eventWithRegion = event; |
| HitTestCanvasResult* hitTestCanvasResult = |
| toHTMLCanvasElement(result.innerNode()) |
| ->getControlAndIdIfHitRegionExists(result.pointInInnerNodeFrame()); |
| if (hitTestCanvasResult->getControl()) { |
| result.setInnerNode(hitTestCanvasResult->getControl()); |
| } |
| eventWithRegion.setRegion(hitTestCanvasResult->getId()); |
| return MouseEventWithHitTestResults(eventWithRegion, result); |
| } |
| |
| return MouseEventWithHitTestResults(event, result); |
| } |
| |
| // DOM Section 1.1.1 |
| bool Document::childTypeAllowed(NodeType type) const { |
| switch (type) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| return true; |
| case kDocumentTypeNode: |
| case kElementNode: |
| // Documents may contain no more than one of each of these. |
| // (One Element and one DocumentType.) |
| for (Node& c : NodeTraversal::childrenOf(*this)) { |
| if (c.getNodeType() == type) |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool Document::canAcceptChild(const Node& newChild, |
| const Node* oldChild, |
| ExceptionState& exceptionState) const { |
| if (oldChild && oldChild->getNodeType() == newChild.getNodeType()) |
| 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& child : NodeTraversal::childrenOf(*this)) { |
| if (oldChild && *oldChild == child) |
| continue; |
| |
| switch (child.getNodeType()) { |
| case kDocumentTypeNode: |
| numDoctypes++; |
| break; |
| case kElementNode: |
| numElements++; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| // Then, see how many doctypes and elements might be added by the new child. |
| if (newChild.isDocumentFragment()) { |
| for (Node& child : |
| NodeTraversal::childrenOf(toDocumentFragment(newChild))) { |
| switch (child.getNodeType()) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| "Nodes of type '" + newChild.nodeName() + |
| "' may not be inserted inside nodes of type '#document'."); |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| break; |
| case kDocumentTypeNode: |
| numDoctypes++; |
| break; |
| case kElementNode: |
| numElements++; |
| break; |
| } |
| } |
| } else { |
| switch (newChild.getNodeType()) { |
| case kAttributeNode: |
| case kCdataSectionNode: |
| case kDocumentFragmentNode: |
| case kDocumentNode: |
| case kTextNode: |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| "Nodes of type '" + newChild.nodeName() + |
| "' may not be inserted inside nodes of type '#document'."); |
| return false; |
| case kCommentNode: |
| case kProcessingInstructionNode: |
| return true; |
| case kDocumentTypeNode: |
| numDoctypes++; |
| break; |
| case kElementNode: |
| numElements++; |
| break; |
| } |
| } |
| |
| if (numElements > 1 || numDoctypes > 1) { |
| exceptionState.throwDOMException( |
| HierarchyRequestError, |
| String::format("Only one %s on document allowed.", |
| numElements > 1 ? "element" : "doctype")); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| Node* Document::cloneNode(bool deep) { |
| Document* clone = cloneDocumentWithoutChildren(); |
| clone->cloneDataFromDocument(*this); |
| if (deep) |
| cloneChildNodes(clone); |
| return clone; |
| } |
| |
| Document* Document::cloneDocumentWithoutChildren() { |
| DocumentInit init(url()); |
| if (isXMLDocument()) { |
| if (isXHTMLDocument()) |
| return XMLDocument::createXHTML( |
| init.withRegistrationContext(registrationContext())); |
| return XMLDocument::create(init); |
| } |
| return create(init); |
| } |
| |
| void Document::cloneDataFromDocument(const Document& other) { |
| setCompatibilityMode(other.getCompatibilityMode()); |
| setEncodingData(other.m_encodingData); |
| setContextFeatures(other.contextFeatures()); |
| setSecurityOrigin(other.getSecurityOrigin()->isolatedCopy()); |
| setMimeType(other.contentType()); |
| } |
| |
| bool Document::isSecureContextImpl( |
| const SecureContextCheck privilegeContextCheck) const { |
| // There may be exceptions for the secure context check defined for certain |
| // schemes. The exceptions are applied only to the special scheme and to |
| // sandboxed URLs from those origins, but *not* to any children. |
| // |
| // For example: |
| // <iframe src="http://host"> |
| // <iframe src="scheme-has-exception://host"></iframe> |
| // <iframe sandbox src="scheme-has-exception://host"></iframe> |
| // </iframe> |
| // both inner iframes pass this check, assuming that the scheme |
| // "scheme-has-exception:" is granted an exception. |
| // |
| // However, |
| // <iframe src="http://host"> |
| // <iframe sandbox src="http://host"></iframe> |
| // </iframe> |
| // would fail the check (that is, sandbox does not grant an exception itself). |
| // |
| // Additionally, with |
| // <iframe src="scheme-has-exception://host"> |
| // <iframe src="http://host"></iframe> |
| // <iframe sandbox src="http://host"></iframe> |
| // </iframe> |
| // both inner iframes would fail the check, even though the outermost iframe |
| // passes. |
| // |
| // In all cases, a frame must be potentially trustworthy in addition to |
| // having an exception listed in order for the exception to be granted. |
| if (!getSecurityOrigin()->isPotentiallyTrustworthy()) |
| return false; |
| |
| if (SchemeRegistry::schemeShouldBypassSecureContextCheck( |
| getSecurityOrigin()->protocol())) |
| return true; |
| |
| if (privilegeContextCheck == StandardSecureContextCheck) { |
| if (!m_frame) |
| return true; |
| Frame* parent = m_frame->tree().parent(); |
| while (parent) { |
| if (!parent->securityContext() |
| ->getSecurityOrigin() |
| ->isPotentiallyTrustworthy()) |
| return false; |
| parent = parent->tree().parent(); |
| } |
| } |
| return true; |
| } |
| |
| StyleSheetList& Document::styleSheets() { |
| if (!m_styleSheetList) |
| m_styleSheetList = StyleSheetList::create(this); |
| return *m_styleSheetList; |
| } |
| |
| String Document::preferredStylesheetSet() const { |
| return m_styleEngine->preferredStylesheetSetName(); |
| } |
| |
| String Document::selectedStylesheetSet() const { |
| return m_styleEngine->selectedStylesheetSetName(); |
| } |
| |
| void Document::setSelectedStylesheetSet(const String& aString) { |
| styleEngine().setSelectedStylesheetSetName(aString); |
| } |
| |
| void Document::evaluateMediaQueryListIfNeeded() { |
| if (!m_evaluateMediaQueriesOnStyleRecalc) |
| return; |
| evaluateMediaQueryList(); |
| m_evaluateMediaQueriesOnStyleRecalc = false; |
| } |
| |
| void Document::evaluateMediaQueryList() { |
| if (m_mediaQueryMatcher) |
| m_mediaQueryMatcher->mediaFeaturesChanged(); |
| } |
| |
| void Document::setResizedForViewportUnits() { |
| if (m_mediaQueryMatcher) |
| m_mediaQueryMatcher->viewportChanged(); |
| if (!hasViewportUnits()) |
| return; |
| ensureStyleResolver().setResizedForViewportUnits(); |
| setNeedsStyleRecalcForViewportUnits(); |
| } |
| |
| void Document::clearResizedForViewportUnits() { |
| ensureStyleResolver().clearResizedForViewportUnits(); |
| } |
| |
| void Document::styleResolverMayHaveChanged() { |
| if (hasNodesWithPlaceholderStyle()) { |
| setNeedsStyleRecalc(SubtreeStyleChange, |
| StyleChangeReasonForTracing::create( |
| StyleChangeReason::CleanupPlaceholderStyles)); |
| } |
| |
| if (didLayoutWithPendingStylesheets() && |
| !styleEngine().hasPendingScriptBlockingSheets()) { |
| // We need to manually repaint because we avoid doing all repaints in layout |
| // or style recalc while sheets are still loading to avoid FOUC. |
| m_pendingSheetLayout = IgnoreLayoutWithPendingSheets; |
| |
| DCHECK(!layoutViewItem().isNull() || importsController()); |
| if (!layoutViewItem().isNull()) |
| layoutViewItem().invalidatePaintForViewAndCompositedLayers(); |
| } |
| } |
| |
| void Document::setHoverNode(Node* newHoverNode) { |
| m_hoverNode = newHoverNode; |
| } |
| |
| void Document::setActiveHoverElement(Element* newActiveElement) { |
| if (!newActiveElement) { |
| m_activeHoverElement.clear(); |
| return; |
| } |
| |
| m_activeHoverElement = newActiveElement; |
| } |
| |
| void Document::removeFocusedElementOfSubtree(Node* node, |
| bool amongChildrenOnly) { |
| if (!m_focusedElement) |
| return; |
| |
| // We can't be focused if we're not in the document. |
| if (!node->isConnected()) |
| return; |
| bool contains = |
| node->isShadowIncludingInclusiveAncestorOf(m_focusedElement.get()); |
| if (contains && (m_focusedElement != node || !amongChildrenOnly)) |
| clearFocusedElement(); |
| } |
| |
| void Document::hoveredNodeDetached(Element& element) { |
| if (!m_hoverNode) |
| return; |
| |
| m_hoverNode->updateDistribution(); |
| if (element != m_hoverNode && |
| (!m_hoverNode->isTextNode() || |
| element != FlatTreeTraversal::parent(*m_hoverNode))) |
| return; |
| |
| m_hoverNode = FlatTreeTraversal::parent(element); |
| while (m_hoverNode && !m_hoverNode->layoutObject()) |
| m_hoverNode = FlatTreeTraversal::parent(*m_hoverNode); |
| |
| // If the mouse cursor is not visible, do not clear existing |
| // hover effects on the ancestors of |element| and do not invoke |
| // new hover effects on any other element. |
| if (!page()->isCursorVisible()) |
| return; |
| |
| if (frame()) |
| frame()->eventHandler().scheduleHoverStateUpdate(); |
| } |
| |
| void Document::activeChainNodeDetached(Element& element) { |
| if (!m_activeHoverElement) |
| return; |
| |
| if (element != m_activeHoverElement) |
| return; |
| |
| Node* activeNode = FlatTreeTraversal::parent(element); |
| while (activeNode && activeNode->isElementNode() && |
| !activeNode->layoutObject()) |
| activeNode = FlatTreeTraversal::parent(*activeNode); |
| |
| m_activeHoverElement = activeNode && activeNode->isElementNode() |
| ? toElement(activeNode) |
| : nullptr; |
| } |
| |
| const Vector<AnnotatedRegionValue>& Document::annotatedRegions() const { |
| return m_annotatedRegions; |
| } |
| |
| void Document::setAnnotatedRegions( |
| const Vector<AnnotatedRegionValue>& regions) { |
| m_annotatedRegions = regions; |
| setAnnotatedRegionsDirty(false); |
| } |
| |
| bool Document::setFocusedElement(Element* prpNewFocusedElement, |
| const FocusParams& params) { |
| DCHECK(!m_lifecycle.inDetach()); |
| |
| m_clearFocusedElementTimer.stop(); |
| |
| Element* newFocusedElement = prpNewFocusedElement; |
| |
| // Make sure newFocusedNode is actually in this document |
| if (newFocusedElement && (newFocusedElement->document() != this)) |
| return true; |
| |
| if (NodeChildRemovalTracker::isBeingRemoved(newFocusedElement)) |
| return true; |
| |
| if (m_focusedElement == newFocusedElement) |
| return true; |
| |
| bool focusChangeBlocked = false; |
| Element* oldFocusedElement = m_focusedElement; |
| m_focusedElement = nullptr; |
| |
| // Remove focus from the existing focus node (if any) |
| if (oldFocusedElement) { |
| oldFocusedElement->setFocused(false); |
| |
| // Dispatch the blur event and let the node do any other blur related |
| // activities (important for text fields) |
| // If page lost focus, blur event will have already been dispatched |
| if (page() && (page()->focusController().isFocused())) { |
| oldFocusedElement->dispatchBlurEvent(newFocusedElement, params.type, |
| params.sourceCapabilities); |
| |
| if (m_focusedElement) { |
| // handler shifted focus |
| focusChangeBlocked = true; |
| newFocusedElement = nullptr; |
| } |
| |
| // 'focusout' is a DOM level 3 name for the bubbling blur event. |
| oldFocusedElement->dispatchFocusOutEvent(EventTypeNames::focusout, |
| newFocusedElement, |
| params.sourceCapabilities); |
| // 'DOMFocusOut' is a DOM level 2 name for compatibility. |
| // 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(EventTypeNames::DOMFocusOut, |
| newFocusedElement, |
| params.sourceCapabilities); |
| |
| if (m_focusedElement) { |
| // handler shifted focus |
| focusChangeBlocked = true; |
| newFocusedElement = nullptr; |
| } |
| } |
| |
| if (view()) { |
| Widget* oldWidget = widgetForElement(*oldFocusedElement); |
| if (oldWidget) |
| oldWidget->setFocused(false, params.type); |
| else |
| view()->setFocused(false, params.type); |
| } |
| } |
| |
| if (newFocusedElement) |
| updateStyleAndLayoutTreeForNode(newFocusedElement); |
| if (newFocusedElement && newFocusedElement->isFocusable()) { |
| if (isRootEditableElement(*newFocusedElement) && |
| !acceptsEditingFocus(*newFocusedElement)) { |
| // delegate blocks focus change |
| focusChangeBlocked = true; |
| goto SetFocusedElementDone; |
| } |
| // Set focus on the new node |
| m_focusedElement = newFocusedElement; |
| setSequentialFocusNavigationStartingPoint(m_focusedElement.get()); |
| |
| m_focusedElement->setFocused(true); |
| // Element::setFocused for frames can dispatch events. |
| if (m_focusedElement != newFocusedElement) { |
| focusChangeBlocked = true; |
| goto SetFocusedElementDone; |
| } |
| cancelFocusAppearanceUpdate(); |
| m_focusedElement->updateFocusAppearance(params.selectionBehavior); |
| |
| // Dispatch the focus event and let the node do any other focus related |
| // activities (important for text fields) |
| // If page lost focus, event will be dispatched on page focus, don't |
| // duplicate |
| if (page() && (page()->focusController().isFocused())) { |
| m_focusedElement->dispatchFocusEvent(oldFocusedElement, params.type, |
| params.sourceCapabilities); |
| |
| if (m_focusedElement != newFocusedElement) { |
| // handler shifted focus |
| focusChangeBlocked = true; |
| goto SetFocusedElementDone; |
| } |
| // DOM level 3 bubbling focus event. |
| m_focusedElement->dispatchFocusInEvent(EventTypeNames::focusin, |
| oldFocusedElement, params.type, |
| params.sourceCapabilities); |
| |
| if (m_focusedElement != newFocusedElement) { |
| // handler shifted focus |
| focusChangeBlocked = true; |
| goto SetFocusedElementDone; |
| } |
| |
| // For DOM level 2 compatibility. |
| // 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(EventTypeNames::DOMFocusIn, |
| oldFocusedElement, params.type, |
| params.sourceCapabilities); |
| |
| if (m_focusedElement != newFocusedElement) { |
| // handler shifted focus |
| focusChangeBlocked = true; |
| goto SetFocusedElementDone; |
| } |
| } |
| |
| if (isRootEditableElement(*m_focusedElement)) |
| frame()->spellChecker().didBeginEditing(m_focusedElement.get()); |
| |
| // eww, I suck. set the qt focus correctly |
| // ### find a better place in the code for this |
| if (view()) { |
| Widget* focusWidget = widgetForElement(*m_focusedElement); |
| if (focusWidget) { |
| // Make sure a widget has the right size before giving it focus. |
| // Otherwise, we are testing edge cases of the Widget code. |
| // Specifically, in WebCore this does not work well for text fields. |
| updateStyleAndLayout(); |
| // Re-get the widget in case updating the layout changed things. |
| focusWidget = widgetForElement(*m_focusedElement); |
| } |
| if (focusWidget) |
| focusWidget->setFocused(true, params.type); |
| else |
| view()->setFocused(true, params.type); |
| } |
| } |
| |
| if (!focusChangeBlocked && m_focusedElement) { |
| // Create the AXObject cache in a focus change because Chromium relies on |
| // it. |
| if (AXObjectCache* cache = axObjectCache()) |
| cache->handleFocusedUIElementChanged(oldFocusedElement, |
| newFocusedElement); |
| } |
| |
| if (!focusChangeBlocked && frameHost()) |
| frameHost()->chromeClient().focusedNodeChanged(oldFocusedElement, |
| m_focusedElement.get()); |
| |
| SetFocusedElementDone: |
| updateStyleAndLayoutTree(); |
| if (LocalFrame* frame = this->frame()) |
| frame->selection().didChangeFocus(); |
| return !focusChangeBlocked; |
| } |
| |
| void Document::clearFocusedElement() { |
| setFocusedElement(nullptr, FocusParams(SelectionBehaviorOnFocus::None, |
| WebFocusTypeNone, nullptr)); |
| } |
| |
| void Document::setSequentialFocusNavigationStartingPoint(Node* node) { |
| if (!m_frame) |
| return; |
| if (!node) { |
| m_sequentialFocusNavigationStartingPoint = nullptr; |
| return; |
| } |
| DCHECK_EQ(node->document(), this); |
| if (!m_sequentialFocusNavigationStartingPoint) |
| m_sequentialFocusNavigationStartingPoint = Range::create(*this); |
| m_sequentialFocusNavigationStartingPoint->selectNodeContents( |
| node, ASSERT_NO_EXCEPTION); |
| } |
| |
| Element* Document::sequentialFocusNavigationStartingPoint( |
| WebFocusType type) const { |
| if (m_focusedElement) |
| return m_focusedElement.get(); |
| if (!m_sequentialFocusNavigationStartingPoint) |
| return nullptr; |
| if (!m_sequentialFocusNavigationStartingPoint->collapsed()) { |
| Node* node = m_sequentialFocusNavigationStartingPoint->startContainer(); |
| DCHECK_EQ(node, m_sequentialFocusNavigationStartingPoint->endContainer()); |
| if (node->isElementNode()) |
| return toElement(node); |
| if (Element* neighborElement = type == WebFocusTypeForward |
| ? ElementTraversal::previous(*node) |
| : ElementTraversal::next(*node)) |
| return neighborElement; |
| return node->parentOrShadowHostElement(); |
| } |
| |
| // Range::selectNodeContents didn't select contents because the element had |
| // no children. |
| if (m_sequentialFocusNavigationStartingPoint->startContainer() |
| ->isElementNode() && |
| !m_sequentialFocusNavigationStartingPoint->startContainer() |
| ->hasChildren() && |
| m_sequentialFocusNavigationStartingPoint->startOffset() == 0) |
| return toElement( |
| m_sequentialFocusNavigationStartingPoint->startContainer()); |
| |
| // A node selected by Range::selectNodeContents was removed from the |
| // document tree. |
| if (Node* nextNode = m_sequentialFocusNavigationStartingPoint->firstNode()) { |
| if (type == WebFocusTypeForward) |
| return ElementTraversal::previous(*nextNode); |
| if (nextNode->isElementNode()) |
| return toElement(nextNode); |
| return ElementTraversal::next(*nextNode); |
| } |
| return nullptr; |
| } |
| |
| void Document::setCSSTarget(Element* newTarget) { |
| if (m_cssTarget) |
| m_cssTarget->pseudoStateChanged(CSSSelector::PseudoTarget); |
| m_cssTarget = newTarget; |
| if (m_cssTarget) |
| m_cssTarget->pseudoStateChanged(CSSSelector::PseudoTarget); |
| } |
| |
| static void liveNodeListBaseWriteBarrier(void* parent, |
| const LiveNodeListBase* list) { |
| if (isHTMLCollectionType(list->type())) { |
| ScriptWrappableVisitor::writeBarrier( |
| parent, static_cast<const HTMLCollection*>(list)); |
| } else { |
| ScriptWrappableVisitor::writeBarrier( |
| parent, static_cast<const LiveNodeList*>(list)); |
| } |
| } |
| |
| void Document::registerNodeList(const LiveNodeListBase* list) { |
| DCHECK(!m_nodeLists[list->invalidationType()].contains(list)); |
| m_nodeLists[list->invalidationType()].add(list); |
| liveNodeListBaseWriteBarrier(this, list); |
| if (list->isRootedAtTreeScope()) |
| m_listsInvalidatedAtDocument.add(list); |
| } |
| |
| void Document::unregisterNodeList(const LiveNodeListBase* list) { |
| DCHECK(m_nodeLists[list->invalidationType()].contains(list)); |
| m_nodeLists[list->invalidationType()].remove(list); |
| if (list->isRootedAtTreeScope()) { |
| DCHECK(m_listsInvalidatedAtDocument.contains(list)); |
| m_listsInvalidatedAtDocument.remove(list); |
| } |
| } |
| |
| void Document::registerNodeListWithIdNameCache(const LiveNodeListBase* list) { |
| DCHECK(!m_nodeLists[InvalidateOnIdNameAttrChange].contains(list)); |
| m_nodeLists[InvalidateOnIdNameAttrChange].add(list); |
| liveNodeListBaseWriteBarrier(this, list); |
| } |
| |
| void Document::unregisterNodeListWithIdNameCache(const LiveNodeListBase* list) { |
| DCHECK(m_nodeLists[InvalidateOnIdNameAttrChange].contains(list)); |
| m_nodeLists[InvalidateOnIdNameAttrChange].remove(list); |
| } |
| |
| void Document::attachNodeIterator(NodeIterator* ni) { |
| m_nodeIterators.add(ni); |
| } |
| |
| void Document::detachNodeIterator(NodeIterator* ni) { |
| // The node iterator can be detached without having been attached if its root |
| // node didn't have a document when the iterator was created, but has it now. |
| m_nodeIterators.remove(ni); |
| } |
| |
| void Document::moveNodeIteratorsToNewDocument(Node& node, |
| Document& newDocument) { |
| HeapHashSet<WeakMember<NodeIterator>> nodeIteratorsList = m_nodeIterators; |
| for (NodeIterator* ni : nodeIteratorsList) { |
| if (ni->root() == node) { |
| detachNodeIterator(ni); |
| newDocument.attachNodeIterator(ni); |
| } |
| } |
| } |
| |
| void Document::didMoveTreeToNewDocument(const Node& root) { |
| DCHECK_NE(root.document(), this); |
| if (m_ranges.isEmpty()) |
| return; |
| |
| AttachedRangeSet ranges = m_ranges; |
| for (Range* range : ranges) |
| range->updateOwnerDocumentIfNeeded(); |
| } |
| |
| void Document::nodeChildrenWillBeRemoved(ContainerNode& container) { |
| EventDispatchForbiddenScope assertNoEventDispatch; |
| for (Range* range : m_ranges) |
| range->nodeChildrenWillBeRemoved(container); |
| |
| for (NodeIterator* ni : m_nodeIterators) { |
| for (Node& n : NodeTraversal::childrenOf(container)) |
| ni->nodeWillBeRemoved(n); |
| } |
| |
| notifyNodeChildrenWillBeRemoved(container); |
| if (LocalFrame* frame = this->frame()) |
| frame->selection().nodeChildrenWillBeRemoved(container); |
| |
| if (containsV1ShadowTree()) { |
| for (Node& n : NodeTraversal::childrenOf(container)) |
| n.checkSlotChangeBeforeRemoved(); |
| } |
| } |
| |
| void Document::nodeWillBeRemoved(Node& n) { |
| for (NodeIterator* ni : m_nodeIterators) |
| ni->nodeWillBeRemoved(n); |
| |
| for (Range* range : m_ranges) |
| range->nodeWillBeRemoved(n); |
| |
| notifyNodeWillBeRemoved(n); |
| |
| if (LocalFrame* frame = this->frame()) { |
| frame->selection().nodeWillBeRemoved(n); |
| } |
| |
| if (containsV1ShadowTree()) |
| n.checkSlotChangeBeforeRemoved(); |
| |
| if (n.inActiveDocument() && n.isElementNode()) |
| styleEngine().elementWillBeRemoved(toElement(n)); |
| } |
| |
| void Document::dataWillChange(const CharacterData& characterData) { |
| if (LocalFrame* frame = this->frame()) |
| frame->selection().dataWillChange(characterData); |
| } |
| |
| void Document::didInsertText(Node* text, unsigned offset, unsigned length) { |
| for (Range* range : m_ranges) |
| range->didInsertText(text, offset, length); |
| |
| m_markers->shiftMarkers(text, offset, length); |
| } |
| |
| void Document::didRemoveText(Node* text, unsigned offset, unsigned length) { |
| for (Range* range : m_ranges) |
| range->didRemoveText(text, offset, length); |
| |
| m_markers->removeMarkers(text, offset, length); |
| m_markers->shiftMarkers(text, offset + length, 0 - length); |
| } |
| |
| void Document::didMergeTextNodes(Text& oldNode, unsigned offset) { |
| if (!m_ranges.isEmpty()) { |
| NodeWithIndex oldNodeWithIndex(oldNode); |
| for (Range* range : m_ranges) |
| range->didMergeTextNodes(oldNodeWithIndex, offset); |
| } |
| |
| notifyMergeTextNodes(oldNode, offset); |
| if (m_frame) |
| m_frame->selection().didMergeTextNodes(oldNode, offset); |
| |
| // FIXME: This should update markers for spelling and grammar checking. |
| } |
| |
| void Document::didSplitTextNode(const Text& oldNode) { |
| for (Range* range : m_ranges) |
| range->didSplitTextNode(oldNode); |
| |
| notifySplitTextNode(oldNode); |
| if (m_frame) |
| m_frame->selection().didSplitTextNode(oldNode); |
| |
| // FIXME: This should update markers for spelling and grammar checking. |
| } |
| |
| void Document::setWindowAttributeEventListener(const AtomicString& eventType, |
| EventListener* listener) { |
| LocalDOMWindow* domWindow = this->domWindow(); |
| if (!domWindow) |
| return; |
| domWindow->setAttributeEventListener(eventType, listener); |
| } |
| |
| EventListener* Document::getWindowAttributeEventListener( |
| const AtomicString& eventType) { |
| LocalDOMWindow* domWindow = this->domWindow(); |
| if (!domWindow) |
| return 0; |
| return domWindow->getAttributeEventListener(eventType); |
| } |
| |
| EventQueue* Document::getEventQueue() const { |
| if (!m_domWindow) |
| return 0; |
| return m_domWindow->getEventQueue(); |
| } |
| |
| void Document::enqueueAnimationFrameTask(std::unique_ptr<WTF::Closure> task) { |
| ensureScriptedAnimationController().enqueueTask(std::move(task)); |
| } |
| |
| void Document::enqueueAnimationFrameEvent(Event* event) { |
| ensureScriptedAnimationController().enqueueEvent(event); |
| } |
| |
| void Document::enqueueUniqueAnimationFrameEvent(Event* event) { |
| ensureScriptedAnimationController().enqueuePerFrameEvent(event); |
| } |
| |
| void Document::enqueueScrollEventForNode(Node* target) { |
| // Per the W3C CSSOM View Module only scroll events fired at the document |
| // should bubble. |
| Event* scrollEvent = target->isDocumentNode() |
| ? Event::createBubble(EventTypeNames::scroll) |
| : Event::create(EventTypeNames::scroll); |
| scrollEvent->setTarget(target); |
| ensureScriptedAnimationController().enqueuePerFrameEvent(scrollEvent); |
| } |
| |
| void Document::enqueueResizeEvent() { |
| Event* event = Event::create(EventTypeNames::resize); |
| event->setTarget(domWindow()); |
| ensureScriptedAnimationController().enqueuePerFrameEvent(event); |
| } |
| |
| void Document::enqueueMediaQueryChangeListeners( |
| HeapVector<Member<MediaQueryListListener>>& listeners) { |
| ensureScriptedAnimationController().enqueueMediaQueryChangeListeners( |
| listeners); |
| } |
| |
| void Document::enqueueVisualViewportScrollEvent() { |
| VisualViewportScrollEvent* event = VisualViewportScrollEvent::create(); |
| event->setTarget(domWindow()->visualViewport()); |
| ensureScriptedAnimationController().enqueuePerFrameEvent(event); |
| } |
| |
| void Document::enqueueVisualViewportResizeEvent() { |
| VisualViewportResizeEvent* event = VisualViewportResizeEvent::create(); |
| event->setTarget(domWindow()->visualViewport()); |
| ensureScriptedAnimationController().enqueuePerFrameEvent(event); |
| } |
| |
| void Document::dispatchEventsForPrinting() { |
| if (!m_scriptedAnimationController) |
| return; |
| m_scriptedAnimationController->dispatchEventsAndCallbacksForPrinting(); |
| } |
| |
| Document::EventFactorySet& Document::eventFactories() { |
| DEFINE_STATIC_LOCAL(EventFactorySet, s_eventFactory, ()); |
| return s_eventFactory; |
| } |
| |
| const OriginAccessEntry& Document::accessEntryFromURL() { |
| if (!m_accessEntryFromURL) { |
| m_accessEntryFromURL = WTF::wrapUnique( |
| new OriginAccessEntry(url().protocol(), url().host(), |
| OriginAccessEntry::AllowRegisterableDomains)); |
| } |
| return *m_accessEntryFromURL; |
| } |
| |
| void Document::sendSensitiveInputVisibility() { |
| if (m_sensitiveInputVisibilityTask.isActive()) |
| return; |
| |
| m_sensitiveInputVisibilityTask = |
| TaskRunnerHelper::get(TaskType::UnspecedLoading, this) |
| ->postCancellableTask( |
| BLINK_FROM_HERE, |
| WTF::bind(&Document::sendSensitiveInputVisibilityInternal, |
| wrapWeakPersistent(this))); |
| } |
| |
| void Document::sendSensitiveInputVisibilityInternal() { |
| if (!frame()) |
| return; |
| |
| mojom::blink::SensitiveInputVisibilityServicePtr sensitiveInputServicePtr; |
| frame()->interfaceProvider()->getInterface( |
| mojo::MakeRequest(&sensitiveInputServicePtr)); |
| if (m_passwordCount > 0) { |
| sensitiveInputServicePtr->PasswordFieldVisibleInInsecureContext(); |
| return; |
| } |
| sensitiveInputServicePtr->AllPasswordFieldsInInsecureContextInvisible(); |
| } |
| |
| void Document::runExecutionContextTask( |
| std::unique_ptr<ExecutionContextTask> task, |
| bool isInstrumented) { |
| InspectorInstrumentation::AsyncTask asyncTask(this, task.get(), |
| isInstrumented); |
| task->performTask(this); |
| } |
| |
| void Document::registerEventFactory( |
| std::unique_ptr<EventFactoryBase> eventFactory) { |
| DCHECK(!eventFactories().contains(eventFactory.get())); |
| eventFactories().add(std::move(eventFactory)); |
| } |
| |
| Event* Document::createEvent(ExecutionContext* executionContext, |
| const String& eventType, |
| ExceptionState& exceptionState) { |
| Event* event = nullptr; |
| for (const auto& factory : eventFactories()) { |
| event = factory->create(executionContext, eventType); |
| if (event) |
| return event; |
| } |
| exceptionState.throwDOMException( |
| NotSupportedError, |
| "The provided event type ('" + eventType + "') is invalid."); |
| return nullptr; |
| } |
| |
| void Document::addMutationEventListenerTypeIfEnabled( |
| ListenerType listenerType) { |
| if (ContextFeatures::mutationEventsEnabled(this)) |
| addListenerType(listenerType); |
| } |
| |
| void Document::addListenerTypeIfNeeded(const AtomicString& eventType) { |
| if (eventType == EventTypeNames::DOMSubtreeModified) { |
| UseCounter::count(*this, UseCounter::DOMSubtreeModifiedEvent); |
| addMutationEventListenerTypeIfEnabled(DOMSUBTREEMODIFIED_LISTENER); |
| } else if (eventType == EventTypeNames::DOMNodeInserted) { |
| UseCounter::count(*this, UseCounter::DOMNodeInsertedEvent); |
| addMutationEventListenerTypeIfEnabled(DOMNODEINSERTED_LISTENER); |
| } else if (eventType == EventTypeNames::DOMNodeRemoved) { |
| UseCounter::count(*this, UseCounter::DOMNodeRemovedEvent); |
| addMutationEventListenerTypeIfEnabled(DOMNODEREMOVED_LISTENER); |
| } else if (eventType == EventTypeNames::DOMNodeRemovedFromDocument) { |
| UseCounter::count(*this, UseCounter::DOMNodeRemovedFromDocumentEvent); |
| addMutationEventListenerTypeIfEnabled(DOMNODEREMOVEDFROMDOCUMENT_LISTENER); |
| } else if (eventType == EventTypeNames::DOMNodeInsertedIntoDocument) { |
| UseCounter::count(*this, UseCounter::DOMNodeInsertedIntoDocumentEvent); |
| addMutationEventListenerTypeIfEnabled(DOMNODEINSERTEDINTODOCUMENT_LISTENER); |
| } else if (eventType == EventTypeNames::DOMCharacterDataModified) { |
| UseCounter::count(*this, UseCounter::DOMCharacterDataModifiedEvent); |
| addMutationEventListenerTypeIfEnabled(DOMCHARACTERDATAMODIFIED_LISTENER); |
| } else if (eventType == EventTypeNames::webkitAnimationStart || |
| eventType == EventTypeNames::animationstart) { |
| addListenerType(ANIMATIONSTART_LISTENER); |
| } else if (eventType == EventTypeNames::webkitAnimationEnd || |
| eventType == EventTypeNames::animationend) { |
| addListenerType(ANIMATIONEND_LISTENER); |
| } else if (eventType == EventTypeNames::webkitAnimationIteration || |
| eventType == EventTypeNames::animationiteration) { |
| addListenerType(ANIMATIONITERATION_LISTENER); |
| if (view()) { |
| // Need to re-evaluate time-to-effect-change for any running animations. |
| view()->scheduleAnimation(); |
| } |
| } else if (eventType == EventTypeNames::webkitTransitionEnd || |
| eventType == EventTypeNames::transitionend) { |
| addListenerType(TRANSITIONEND_LISTENER); |
| } else if (eventType == EventTypeNames::scroll) { |
| addListenerType(SCROLL_LISTENER); |
| } |
| } |
| |
| HTMLFrameOwnerElement* Document::localOwner() const { |
| if (!frame()) |
| return 0; |
| // FIXME: This probably breaks the attempts to layout after a load is finished |
| // in implicitClose(), and probably tons of other things... |
| return frame()->deprecatedLocalOwner(); |
| } |
| |
| void Document::willChangeFrameOwnerProperties(int marginWidth, |
| int marginHeight, |
| ScrollbarMode scrollingMode) { |
| if (!body()) |
| return; |
| |
| DCHECK(frame() && frame()->owner()); |
| FrameOwner* owner = frame()->owner(); |
| |
| if (marginWidth != owner->marginWidth()) |
| body()->setIntegralAttribute(marginwidthAttr, marginWidth); |
| if (marginHeight != owner->marginHeight()) |
| body()->setIntegralAttribute(marginheightAttr, marginHeight); |
| if (scrollingMode != owner->scrollingMode() && view()) |
| view()->setNeedsLayout(); |
| } |
| |
| bool Document::isInInvisibleSubframe() const { |
| if (!localOwner()) |
| return false; // this is a local root element |
| |
| // TODO(bokan): This looks like it doesn't work in OOPIF. |
| DCHECK(frame()); |
| return frame()->ownerLayoutItem().isNull(); |
| } |
| |
| String Document::cookie(ExceptionState& exceptionState) const { |
| if (settings() && !settings()->getCookieEnabled()) |
| return String(); |
| |
| // FIXME: The HTML5 DOM spec states that this attribute can raise an |
| // InvalidStateError exception on getting if the Document has no |
| // browsing context. |
| |
| if (!getSecurityOrigin()->canAccessCookies()) { |
| if (isSandboxed(SandboxOrigin)) |
| exceptionState.throwSecurityError( |
| "The document is sandboxed and lacks the 'allow-same-origin' flag."); |
| else if (url().protocolIs("data")) |
| exceptionState.throwSecurityError( |
| "Cookies are disabled inside 'data:' URLs."); |
| else |
| exceptionState.throwSecurityError("Access is denied for this document."); |
| return String(); |
| } |
| |
| // Suborigins are cookie-averse and thus should always return the empty |
| // string, unless the 'unsafe-cookies' option is provided. |
| if (getSecurityOrigin()->hasSuborigin() && |
| !getSecurityOrigin()->suborigin()->policyContains( |
| Suborigin::SuboriginPolicyOptions::UnsafeCookies)) |
| return String(); |
| |
| KURL cookieURL = this->cookieURL(); |
| if (cookieURL.isEmpty()) |
| return String(); |
| |
| return cookies(this, cookieURL); |
| } |
| |
| void Document::setCookie(const String& value, ExceptionState& exceptionState) { |
| if (settings() && !settings()->getCookieEnabled()) |
| return; |
| |
| // FIXME: The HTML5 DOM spec states that this attribute can raise an |
| // InvalidStateError exception on setting if the Document has no |
| // browsing context. |
| |
| if (!getSecurityOrigin()->canAccessCookies()) { |
| if (isSandboxed(SandboxOrigin)) |
| exceptionState.throwSecurityError( |
| "The document is sandboxed and lacks the 'allow-same-origin' flag."); |
| else if (url().protocolIs("data")) |
| exceptionState.throwSecurityError( |
| "Cookies are disabled inside 'data:' URLs."); |
| else |
| exceptionState.throwSecurityError("Access is denied for this document."); |
| return; |
| } |
| |
| // Suborigins are cookie-averse and thus setting should be a no-op, unless |
| // the 'unsafe-cookies' option is provided. |
| if (getSecurityOrigin()->hasSuborigin() && |
| !getSecurityOrigin()->suborigin()->policyContains( |
| Suborigin::SuboriginPolicyOptions::UnsafeCookies)) |
| return; |
| |
| KURL cookieURL = this->cookieURL(); |
| if (cookieURL.isEmpty()) |
| return; |
| |
| setCookies(this, cookieURL, value); |
| } |
| |
| const AtomicString& Document::referrer() const { |
| if (loader()) |
| return loader()->getRequest().httpReferrer(); |
| return nullAtom; |
| } |
| |
| String Document::domain() const { |
| return getSecurityOrigin()->domain(); |
| } |
| |
| void Document::setDomain(const String& rawDomain, |
| ExceptionState& exceptionState) { |
| UseCounter::count(*this, UseCounter::DocumentSetDomain); |
| |
| if (isSandboxed(SandboxDocumentDomain)) { |
| exceptionState.throwSecurityError( |
| "Assignment is forbidden for sandboxed iframes."); |
| return; |
| } |
| |
| if (SchemeRegistry::isDomainRelaxationForbiddenForURLScheme( |
| getSecurityOrigin()->protocol())) { |
| exceptionState.throwSecurityError("Assignment is forbidden for the '" + |
| getSecurityOrigin()->protocol() + |
| "' scheme."); |
| return; |
| } |
| |
| bool success = false; |
| String newDomain = SecurityOrigin::canonicalizeHost(rawDomain, &success); |
| if (!success) { |
| exceptionState.throwSecurityError("'" + rawDomain + |
| "' could not be parsed properly."); |
| return; |
| } |
| |
| if (newDomain.isEmpty()) { |
| exceptionState.throwSecurityError("'" + newDomain + |
| "' is an empty domain."); |
| return; |
| } |
| |
| OriginAccessEntry accessEntry(getSecurityOrigin()->protocol(), newDomain, |
| OriginAccessEntry::AllowSubdomains); |
| OriginAccessEntry::MatchResult result = |
| accessEntry.matchesOrigin(*getSecurityOrigin()); |
| if (result == OriginAccessEntry::DoesNotMatchOrigin) { |
| exceptionState.throwSecurityError( |
| "'" + newDomain + "' is not a suffix of '" + domain() + "'."); |
| return; |
| } |
| |
| if (result == OriginAccessEntry::MatchesOriginButIsPublicSuffix) { |
| exceptionState.throwSecurityError("'" + newDomain + |
| "' is a top-level domain."); |
| return; |
| } |
| |
| getSecurityOrigin()->setDomainFromDOM(newDomain); |
| if (m_frame) |
| m_frame->script().updateSecurityOrigin(getSecurityOrigin()); |
| } |
| |
| // http://www.whatwg.org/specs/web-apps/current-work/#dom-document-lastmodified |
| String Document::lastModified() const { |
| DateComponents date; |
| bool foundDate = false; |
| if (m_frame) { |
| if (DocumentLoader* documentLoader = loader()) { |
| const AtomicString& httpLastModified = |
| documentLoader->response().httpHeaderField(HTTPNames::Last_Modified); |
| if (!httpLastModified.isEmpty()) { |
| date.setMillisecondsSinceEpochForDateTime( |
| convertToLocalTime(parseDate(httpLastModified))); |
| foundDate = true; |
| } |
| } |
| } |
| // FIXME: If this document came from the file system, the HTML5 |
| // specificiation tells us to read the last modification date from the file |
| // system. |
| if (!foundDate) |
| date.setMillisecondsSinceEpochForDateTime( |
| convertToLocalTime(currentTimeMS())); |
| return String::format("%02d/%02d/%04d %02d:%02d:%02d", date.month() + 1, |
| date.monthDay(), date.fullYear(), date.hour(), |
| date.minute(), date.second()); |
| } |
| |
| const KURL Document::firstPartyForCookies() const { |
| // TODO(mkwst): This doesn't properly handle HTML Import documents. |
| |
| // If this is an imported document, grab its master document's first-party: |
| if (importsController() && importsController()->master() && |
| importsController()->master() != this) |
| return importsController()->master()->firstPartyForCookies(); |
| |
| if (!frame()) |
| return SecurityOrigin::urlWithUniqueSecurityOrigin(); |
| |
| // TODO(mkwst): This doesn't correctly handle sandboxed documents; we want to |
| // look at their URL, but we can't because we don't know what it is. |
| Frame* top = frame()->tree().top(); |
| KURL topDocumentURL = |
| top->isLocalFrame() |
| ? toLocalFrame(top)->document()->url() |
| : KURL(KURL(), |
| top->securityContext()->getSecurityOrigin()->toString()); |
| if (SchemeRegistry::shouldTreatURLSchemeAsFirstPartyWhenTopLevel( |
| topDocumentURL.protocol())) |
| return topDocumentURL; |
| |
| // We're intentionally using the URL of each document rather than the |
| // document's SecurityOrigin. Sandboxing a document into a unique origin |
| // shouldn't effect first-/third-party status for cookies and site data. |
| const OriginAccessEntry& accessEntry = |
| top->isLocalFrame() |
| ? toLocalFrame(top)->document()->accessEntryFromURL() |
| : OriginAccessEntry(topDocumentURL.protocol(), topDocumentURL.host(), |
| OriginAccessEntry::AllowRegisterableDomains); |
| const Frame* currentFrame = frame(); |
| while (currentFrame) { |
| // Skip over srcdoc documents, as they are always same-origin with their |
| // closest non-srcdoc parent. |
| while (currentFrame->isLocalFrame() && |
| toLocalFrame(currentFrame)->document()->isSrcdocDocument()) |
| currentFrame = currentFrame->tree().parent(); |
| DCHECK(currentFrame); |
| |
| // We use 'matchesDomain' here, as it turns out that some folks embed HTTPS |
| // login forms |
| // into HTTP pages; we should allow this kind of upgrade. |
| if (accessEntry.matchesDomain( |
| *currentFrame->securityContext()->getSecurityOrigin()) == |
| OriginAccessEntry::DoesNotMatchOrigin) |
| return SecurityOrigin::urlWithUniqueSecurityOrigin(); |
| |
| currentFrame = currentFrame->tree().parent(); |
| } |
| |
| return topDocumentURL; |
| } |
| |
| static bool isValidNameNonASCII(const LChar* characters, unsigned length) { |
| if (!isValidNameStart(characters[0])) |
| return false; |
| |
| for (unsigned i = 1; i < length; ++i) { |
| if (!isValidNamePart(characters[i])) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool isValidNameNonASCII(const UChar* characters, unsigned length) { |
| for (unsigned i = 0; i < length;) { |
| bool first = i == 0; |
| UChar32 c; |
| U16_NEXT(characters, i, length, c); // Increments i. |
| if (first ? !isValidNameStart(c) : !isValidNamePart(c)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| template <typename CharType> |
| static inline bool isValidNameASCII(const CharType* characters, |
| unsigned length) { |
| CharType c = characters[0]; |
| if (!(isASCIIAlpha(c) || c == ':' || c == '_')) |
| return false; |
| |
| for (unsigned i = 1; i < length; ++i) { |
| c = characters[i]; |
| if (!(isASCIIAlphanumeric(c) || c == ':' || c == '_' || c == '-' || |
| c == '.')) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Document::isValidName(const String& name) { |
| unsigned length = name.length(); |
| if (!length) |
| return false; |
| |
| if (name.is8Bit()) { |
| const LChar* characters = name.characters8(); |
| |
| if (isValidNameASCII(characters, length)) |
| return true; |
| |
| return isValidNameNonASCII(characters, length); |
| } |
| |
| const UChar* characters = name.characters16(); |
| |
| if (isValidNameASCII(characters, length)) |
| return true; |
| |
| return isValidNameNonASCII(characters, length); |
| } |
| |
| enum QualifiedNameStatus { |
| QNValid, |
| QNMultipleColons, |
| QNInvalidStartChar, |
| QNInvalidChar, |
| QNEmptyPrefix, |
| QNEmptyLocalName |
| }; |
| |
| struct ParseQualifiedNameResult { |
| QualifiedNameStatus status; |
| UChar32 character; |
| ParseQualifiedNameResult() {} |
| explicit ParseQualifiedNameResult(QualifiedNameStatus status) |
| : status(status) {} |
| ParseQualifiedNameResult(QualifiedNameStatus status, UChar32 character) |
| : status(status), character(character) {} |
| }; |
| |
| template <typename CharType> |
| static ParseQualifiedNameResult parseQualifiedNameInternal( |
| const AtomicString& qualifiedName, |
| const CharType* characters, |
| unsigned length, |
| AtomicString& prefix, |
| AtomicString& localName) { |
| bool nameStart = true; |
| bool sawColon = false; |
| int colonPos = 0; |
| |
| for (unsigned i = 0; i < length;) { |
| UChar32 c; |
| U16_NEXT(characters, i, length, c) |
| if (c == ':') { |
| if (sawColon) |
| return ParseQualifiedNameResult(QNMultipleColons); |
| nameStart = true; |
| sawColon = true; |
| colonPos = i - 1; |
| } else if (nameStart) { |
| if (!isValidNameStart(c)) |
| return ParseQualifiedNameResult(QNInvalidStartChar, c); |
| nameStart = false; |
| } else { |
| if (!isValidNamePart(c)) |
| return ParseQualifiedNameResult(QNInvalidChar, c); |
| } |
| } |
| |
| if (!sawColon) { |
| prefix = nullAtom; |
| localName = qualifiedName; |
| } else { |
| prefix = AtomicString(characters, colonPos); |
| if (prefix.isEmpty()) |
| return ParseQualifiedNameResult(QNEmptyPrefix); |
| int prefixStart = colonPos + 1; |
| localName = AtomicString(characters + prefixStart, length - prefixStart); |
| } |
| |
| if (localName.isEmpty()) |
| return ParseQualifiedNameResult(QNEmptyLocalName); |
| |
| return ParseQualifiedNameResult(QNValid); |
| } |
| |
| bool Document::parseQualifiedName(const AtomicString& qualifiedName, |
| AtomicString& prefix, |
| AtomicString& localName, |
| ExceptionState& exceptionState) { |
| unsigned length = qualifiedName.length(); |
| |
| if (!length) { |
| exceptionState.throwDOMException(InvalidCharacterError, |
| "The qualified name provided is empty."); |
| return false; |
| } |
| |
| ParseQualifiedNameResult returnValue; |
| if (qualifiedName.is8Bit()) |
| returnValue = parseQualifiedNameInternal( |
| qualifiedName, qualifiedName.characters8(), length, prefix, localName); |
| else |
| returnValue = parseQualifiedNameInternal( |
| qualifiedName, qualifiedName.characters16(), length, prefix, localName); |
| if (returnValue.status == QNValid) |
| return true; |
| |
| StringBuilder message; |
| message.append("The qualified name provided ('"); |
| message.append(qualifiedName); |
| message.append("') "); |
| |
| if (returnValue.status == QNMultipleColons) { |
| message.append("contains multiple colons."); |
| } else if (returnValue.status == QNInvalidStartChar) { |
| message.append("contains the invalid name-start character '"); |
| message.append(returnValue.character); |
| message.append("'."); |
| } else if (returnValue.status == QNInvalidChar) { |
| message.append("contains the invalid character '"); |
| message.append(returnValue.character); |
| message.append("'."); |
| } else if (returnValue.status == QNEmptyPrefix) { |
| message.append("has an empty namespace prefix."); |
| } else { |
| DCHECK_EQ(returnValue.status, QNEmptyLocalName); |
| message.append("has an empty local name."); |
| } |
| |
| if (returnValue.status == QNInvalidStartChar || |
| returnValue.status == QNInvalidChar) |
| exceptionState.throwDOMException(InvalidCharacterError, message.toString()); |
| else |
| exceptionState.throwDOMException(NamespaceError, message.toString()); |
| return false; |
| } |
| |
| void Document::setEncodingData(const DocumentEncodingData& newData) { |
| // It's possible for the encoding of the document to change while we're |
| // decoding data. That can only occur while we're processing the <head> |
| // portion of the document. There isn't much user-visible content in the |
| // <head>, but there is the <title> element. This function detects that |
| // situation and re-decodes the document's title so that the user doesn't see |
| // an incorrectly decoded title in the title bar. |
| if (m_titleElement && encoding() != newData.encoding() && |
| !ElementTraversal::firstWithin(*m_titleElement) && |
| encoding() == Latin1Encoding() && |
| m_titleElement->textContent().containsOnlyLatin1()) { |
| CString originalBytes = m_titleElement->textContent().latin1(); |
| std::unique_ptr<TextCodec> codec = newTextCodec(newData.encoding()); |
| String correctlyDecodedTitle = |
| codec->decode(originalBytes.data(), originalBytes.length(), DataEOF); |
| m_titleElement->setTextContent(correctlyDecodedTitle); |
| } |
| |
| DCHECK(newData.encoding().isValid()); |
| m_encodingData = newData; |
| |
| // FIXME: Should be removed as part of |
| // https://code.google.com/p/chromium/issues/detail?id=319643 |
| bool shouldUseVisualOrdering = m_encodingData.encoding().usesVisualOrdering(); |
| if (shouldUseVisualOrdering != m_visuallyOrdered) { |
| m_visuallyOrdered = shouldUseVisualOrdering; |
| // FIXME: How is possible to not have a layoutObject here? |
| if (!layoutViewItem().isNull()) { |
| layoutViewItem().mutableStyleRef().setRtlOrdering( |
| m_visuallyOrdered ? EOrder::kVisual : EOrder::kLogical); |
| } |
| setNeedsStyleRecalc(SubtreeStyleChange, |
| StyleChangeReasonForTracing::create( |
| StyleChangeReason::VisuallyOrdered)); |
| } |
| } |
| |
| KURL Document::completeURL(const String& url) const { |
| return completeURLWithOverride(url, m_baseURL); |
| } |
| |
| KURL Document::completeURLWithOverride(const String& url, |
| const KURL& baseURLOverride) const { |
| DCHECK(baseURLOverride.isEmpty() || baseURLOverride.isValid()); |
| |
| // Always return a null URL when passed a null string. |
| // FIXME: Should we change the KURL constructor to have this behavior? |
| // See also [CSS]StyleSheet::completeURL(const String&) |
| if (url.isNull()) |
| return KURL(); |
| // This logic is deliberately spread over many statements in an attempt to |
| // track down http://crbug.com/312410. |
| const KURL& baseURL = baseURLForOverride(baseURLOverride); |
| if (!encoding().isValid()) |
| return KURL(baseURL, url); |
| return KURL(baseURL, url, encoding()); |
| } |
| |
| const KURL& Document::baseURLForOverride(const KURL& baseURLOverride) const { |
| // This logic is deliberately spread over many statements in an attempt to |
| // track down http://crbug.com/312410. |
| const KURL* baseURLFromParent = 0; |
| bool shouldUseParentBaseURL = baseURLOverride.isEmpty(); |
| if (!shouldUseParentBaseURL) { |
| const KURL& aboutBlankURL = blankURL(); |
| shouldUseParentBaseURL = (baseURLOverride == aboutBlankURL); |
| } |
| if (shouldUseParentBaseURL) { |
| if (Document* parent = parentDocument()) |
| baseURLFromParent = &parent->baseURL(); |
| } |
| return baseURLFromParent ? *baseURLFromParent : baseURLOverride; |
| } |
| |
| KURL Document::openSearchDescriptionURL() { |
| static const char openSearchMIMEType[] = |
| "application/opensearchdescription+xml"; |
| static const char openSearchRelation[] = "search"; |
| |
| // FIXME: Why do only top-level frames have openSearchDescriptionURLs? |
| if (!frame() || frame()->tree().parent()) |
| return KURL(); |
| |
| // FIXME: Why do we need to wait for load completion? |
| if (!loadEventFinished()) |
| return KURL(); |
| |
| if (!head()) |
| return KURL(); |
| |
| for (HTMLLinkElement* linkElement = |
| Traversal<HTMLLinkElement>::firstChild(*head()); |
| linkElement; |
| linkElement = Traversal<HTMLLinkElement>::nextSibling(*linkElement)) { |
| if (!equalIgnoringCase(linkElement->type(), openSearchMIMEType) || |
| !equalIgnoringCase(linkElement->rel(), openSearchRelation)) |
| continue; |
| if (linkElement->href().isEmpty()) |
| continue; |
| |
| // Count usage; perhaps we can lock this to secure contexts. |
| UseCounter::Feature osdDisposition; |
| RefPtr<SecurityOrigin> target = SecurityOrigin::create(linkElement->href()); |
| if (isSecureContext()) { |
| osdDisposition = target->isPotentiallyTrustworthy() |
| ? UseCounter::OpenSearchSecureOriginSecureTarget |
| : UseCounter::OpenSearchSecureOriginInsecureTarget; |
| } else { |
| osdDisposition = target->isPotentiallyTrustworthy() |
| ? UseCounter::OpenSearchInsecureOriginSecureTarget |
| : UseCounter::OpenSearchInsecureOriginInsecureTarget; |
| } |
| UseCounter::count(*this, osdDisposition); |
| |
| return linkElement->href(); |
| } |
| |
| return KURL(); |
| } |
| |
| void Document::currentScriptForBinding( |
| HTMLScriptElementOrSVGScriptElement& scriptElement) const { |
| if (Element* script = currentScript()) { |
| if (script->isInV1ShadowTree()) |
| return; |
| if (isHTMLScriptElement(script)) |
| scriptElement.setHTMLScriptElement(toHTMLScriptElement(script)); |
| else if (isSVGScriptElement(script)) |
| scriptElement.setSVGScriptElement(toSVGScriptElement(script)); |
| } |
| } |
| |
| void Document::pushCurrentScript(Element* newCurrentScript) { |
| DCHECK(isHTMLScriptElement(newCurrentScript) || |
| isSVGScriptElement(newCurrentScript)); |
| m_currentScriptStack.push_back(newCurrentScript); |
| } |
| |
| void Document::popCurrentScript() { |
| DCHECK(!m_currentScriptStack.isEmpty()); |
| m_currentScriptStack.pop_back(); |
| } |
| |
| void Document::setTransformSource(std::unique_ptr<TransformSource> source) { |
| m_transformSource = std::move(source); |
| } |
| |
| String Document::designMode() const { |
| return inDesignMode() ? "on" : "off"; |
| } |
| |
| void Document::setDesignMode(const String& value) { |
| bool newValue = m_designMode; |
| if (equalIgnoringCase(value, "on")) { |
| newValue = true; |
| UseCounter::count(*this, UseCounter::DocumentDesignModeEnabeld); |
| } else if (equalIgnoringCase(value, "off")) { |
| newValue = false; |
| } |
| if (newValue == m_designMode) |
| return; |
| m_designMode = newValue; |
| setNeedsStyleRecalc(SubtreeStyleChange, StyleChangeReasonForTracing::create( |
| StyleChangeReason::DesignMode)); |
| } |
| |
| Document* Document::parentDocument() const { |
| if (!m_frame) |
| return 0; |
| Frame* parent = m_frame->tree().parent(); |
| if (!parent || !parent->isLocalFrame()) |
| return 0; |
| return toLocalFrame(parent)->document(); |
| } |
| |
| Document& Document::topDocument() const { |
| // FIXME: Not clear what topDocument() should do in the OOPI case--should it |
| // return the topmost available Document, or something else? |
| Document* doc = const_cast<Document*>(this); |
| for (HTMLFrameOwnerElement* element = doc->localOwner(); element; |
| element = doc->localOwner()) |
| doc = &element->document(); |
| |
| DCHECK(doc); |
| return *doc; |
| } |
| |
| Document* Document::contextDocument() { |
| if (m_contextDocument) |
| return m_contextDocument; |
| if (m_frame) |
| return this; |
| return nullptr; |
| } |
| |
| Attr* Document::createAttribute(const AtomicString& name, |
| ExceptionState& exceptionState) { |
| return createAttributeNS(nullAtom, convertLocalName(name), exceptionState, |
| true); |
| } |
| |
| Attr* Document::createAttributeNS(const AtomicString& namespaceURI, |
| const AtomicString& qualifiedName, |
| ExceptionState& exceptionState, |
| bool shouldIgnoreNamespaceChecks) { |
| AtomicString prefix, localName; |
| if (!parseQualifiedName(qualifiedName, prefix, localName, exceptionState)) |
| return nullptr; |
| |
| QualifiedName qName(prefix, localName, namespaceURI); |
| |
| if (!shouldIgnoreNamespaceChecks && !hasValidNamespaceForAttributes(qName)) { |
| exceptionState.throwDOMException( |
| NamespaceError, |
| "The namespace URI provided ('" + namespaceURI + |
| "') is not valid for the qualified name provided ('" + |
| qualifiedName + "')."); |
| return nullptr; |
| } |
| |
| return Attr::create(*this, qName, emptyAtom); |
| } |
| |
| const SVGDocumentExtensions* Document::svgExtensions() { |
| return m_svgExtensions.get(); |
| } |
| |
| SVGDocumentExtensions& Document::accessSVGExtensions() { |
| if (!m_svgExtensions) |
| m_svgExtensions = new SVGDocumentExtensions(this); |
| return *m_svgExtensions; |
| } |
| |
| bool Document::hasSVGRootNode() const { |
| return isSVGSVGElement(documentElement()); |
| } |
| |
| HTMLCollection* Document::images() { |
| return ensureCachedCollection<HTMLCollection>(DocImages); |
| } |
| |
| HTMLCollection* Document::applets() { |
| return ensureCachedCollection<HTMLCollection>(DocApplets); |
| } |
| |
| HTMLCollection* Document::embeds() { |
| return ensureCachedCollection<HTMLCollection>(DocEmbeds); |
| } |
| |
| HTMLCollection* Document::scripts() { |
| return ensureCachedCollection<HTMLCollection>(DocScripts); |
| } |
| |
| HTMLCollection* Document::links() { |
| return ensureCachedCollection<HTMLCollection>(DocLinks); |
| } |
| |
| HTMLCollection* Document::forms() { |
| return ensureCachedCollection<HTMLCollection>(DocForms); |
| } |
| |
| HTMLCollection* Document::anchors() { |
| return ensureCachedCollection<HTMLCollection>(DocAnchors); |
| } |
| |
| HTMLAllCollection* Document::all() { |
| return ensureCachedCollection<HTMLAllCollection>(DocAll); |
| } |
| |
| HTMLCollection* Document::windowNamedItems(const AtomicString& name) { |
| return ensureCachedCollection<WindowNameCollection>(WindowNamedItems, name); |
| } |
| |
| DocumentNameCollection* Document::documentNamedItems(const AtomicString& name) { |
| return ensureCachedCollection<DocumentNameCollection>(DocumentNamedItems, |
| name); |
| } |
| |
| void Document::finishedParsing() { |
| DCHECK(!scriptableDocumentParser() || !m_parser->isParsing()); |
| DCHECK(!scriptableDocumentParser() || m_readyState != Loading); |
| setParsingState(InDOMContentLoaded); |
| DocumentParserTiming::from(*this).markParserStop(); |
| |
| // FIXME: DOMContentLoaded is dispatched synchronously, but this should be |
| // dispatched in a queued task, see https://crbug.com/425790 |
| if (!m_documentTiming.domContentLoadedEventStart()) |
| m_documentTiming.markDomContentLoadedEventStart(); |
| dispatchEvent(Event::createBubble(EventTypeNames::DOMContentLoaded)); |
| if (!m_documentTiming.domContentLoadedEventEnd()) |
| m_documentTiming.markDomContentLoadedEventEnd(); |
| setParsingState(FinishedParsing); |
| |
| // Ensure Custom Element callbacks are drained before DOMContentLoaded. |
| // FIXME: Remove this ad-hoc checkpoint when DOMContentLoaded is dispatched in |
| // a queued task, which will do a checkpoint anyway. https://crbug.com/425790 |
| Microtask::performCheckpoint(V8PerIsolateData::mainThreadIsolate()); |
| |
| if (LocalFrame* frame = this->frame()) { |
| // Don't update the layout tree if we haven't requested the main resource |
| // yet to avoid adding extra latency. Note that the first layout tree update |
| // can be expensive since it triggers the parsing of the default stylesheets |
| // which are compiled-in. |
| const bool mainResourceWasAlreadyRequested = |
| frame->loader().stateMachine()->committedFirstRealDocumentLoad(); |
| |
| // FrameLoader::finishedParsing() might end up calling |
| // Document::implicitClose() if all resource loads are |
| // complete. HTMLObjectElements can start loading their resources from post |
| // attach callbacks triggered by recalcStyle(). This means if we parse out |
| // an <object> tag and then reach the end of the document without updating |
| // styles, we might not have yet started the resource load and might fire |
| // the window load event too early. To avoid this we force the styles to be |
| // up to date before calling FrameLoader::finishedParsing(). See |
| // https://bugs.webkit.org/show_bug.cgi?id=36864 starting around comment 35. |
| if (mainResourceWasAlreadyRequested) |
| updateStyleAndLayoutTree(); |
| |
| beginLifecycleUpdatesIfRenderingReady(); |
| |
| frame->loader().finishedParsing(); |
| |
| TRACE_EVENT_INSTANT1("devtools.timeline", "MarkDOMContent", |
| TRACE_EVENT_SCOPE_THREAD, "data", |
| InspectorMarkLoadEvent::data(frame)); |
| InspectorInstrumentation::domContentLoadedEventFired(frame); |
| } |
| |
| // Schedule dropping of the ElementDataCache. We keep it alive for a while |
| // after parsing finishes so that dynamically inserted content can also |
| // benefit from sharing optimizations. Note that we don't refresh the timer |
| // on cache access since that could lead to huge caches being kept alive |
| // indefinitely by something innocuous like JS setting .innerHTML repeatedly |
| // on a timer. |
| m_elementDataCacheClearTimer.startOneShot(10, BLINK_FROM_HERE); |
| |
| // Parser should have picked up all preloads by now |
| m_fetcher->clearPreloads(ResourceFetcher::ClearSpeculativeMarkupPreloads); |
| |
| if (isPrefetchOnly()) |
| WebPrerenderingSupport::current()->prefetchFinished(); |
| } |
| |
| void Document::elementDataCacheClearTimerFired(TimerBase*) { |
| m_elementDataCache.clear(); |
| } |
| |
| void Document::beginLifecycleUpdatesIfRenderingReady() { |
| if (!isActive()) |
| return; |
| if (!isRenderingReady()) |
| return; |
| view()->beginLifecycleUpdates(); |
| } |
| |
| Vector<IconURL> Document::iconURLs(int iconTypesMask) { |
| IconURL firstFavicon; |
| IconURL firstTouchIcon; |
| IconURL firstTouchPrecomposedIcon; |
| Vector<IconURL> secondaryIcons; |
| |
| using TraversalFunction = HTMLLinkElement* (*)(const Node&); |
| TraversalFunction findNextCandidate = |
| &Traversal<HTMLLinkElement>::nextSibling; |
| |
| HTMLLinkElement* firstElement = nullptr; |
| if (head()) { |
| firstElement = Traversal<HTMLLinkElement>::firstChild(*head()); |
| } else if (isSVGDocument() && isSVGSVGElement(documentElement())) { |
| firstElement = Traversal<HTMLLinkElement>::firstWithin(*documentElement()); |
| findNextCandidate = &Traversal<HTMLLinkElement>::next; |
| } |
| |
| // Start from the first child node so that icons seen later take precedence as |
| // required by the spec. |
| for (HTMLLinkElement* linkElement = firstElement; linkElement; |
| linkElement = findNextCandidate(*linkElement)) { |
| if (!(linkElement->getIconType() & iconTypesMask)) |
| continue; |
| if (linkElement->href().isEmpty()) |
| continue; |
| |
| IconURL newURL(linkElement->href(), linkElement->iconSizes(), |
| linkElement->type(), linkElement->getIconType()); |
| if (linkElement->getIconType() == Favicon) { |
| if (firstFavicon.m_iconType != InvalidIcon) |
| secondaryIcons.push_back(firstFavicon); |
| firstFavicon = newURL; |
| } else if (linkElement->getIconType() == TouchIcon) { |
| if (firstTouchIcon.m_iconType != InvalidIcon) |
| secondaryIcons.push_back(firstTouchIcon); |
| firstTouchIcon = newURL; |
| } else if (linkElement->getIconType() == TouchPrecomposedIcon) { |
| if (firstTouchPrecomposedIcon.m_iconType != InvalidIcon) |
| secondaryIcons.push_back(firstTouchPrecomposedIcon); |
| firstTouchPrecomposedIcon = newURL; |
| } else { |
| NOTREACHED(); |
| } |
| } |
| |
| Vector<IconURL> iconURLs; |
| if (firstFavicon.m_iconType != InvalidIcon) |
| iconURLs.push_back(firstFavicon); |
| else if (m_url.protocolIsInHTTPFamily() && iconTypesMask & Favicon) |
| iconURLs.push_back(IconURL::defaultFavicon(m_url)); |
| |
| if (firstTouchIcon.m_iconType != InvalidIcon) |
| iconURLs.push_back(firstTouchIcon); |
| if (firstTouchPrecomposedIcon.m_iconType != InvalidIcon) |
| iconURLs.push_back(firstTouchPrecomposedIcon); |
| for (int i = secondaryIcons.size() - 1; i >= 0; --i) |
| iconURLs.push_back(secondaryIcons[i]); |
| return iconURLs; |
| } |
| |
| Color Document::themeColor() const { |
| auto rootElement = documentElement(); |
| if (!rootElement) |
| return Color(); |
| for (HTMLMetaElement& metaElement : |
| Traversal<HTMLMetaElement>::descendantsOf(*rootElement)) { |
| Color color = Color::transparent; |
| if (equalIgnoringCase(metaElement.name(), "theme-color") && |
| CSSParser::parseColor( |
| color, metaElement.content().getString().stripWhiteSpace(), true)) |
| return color; |
| } |
| return Color(); |
| } |
| |
| HTMLLinkElement* Document::linkManifest() const { |
| HTMLHeadElement* head = this->head(); |
| if (!head) |
| return 0; |
| |
| // The first link element with a manifest rel must be used. Others are |
| // ignored. |
| for (HTMLLinkElement* linkElement = |
| Traversal<HTMLLinkElement>::firstChild(*head); |
| linkElement; |
| linkElement = Traversal<HTMLLinkElement>::nextSibling(*linkElement)) { |
| if (!linkElement->relAttribute().isManifest()) |
| continue; |
| return linkElement; |
| } |
| |
| return 0; |
| } |
| |
| void Document::initSecurityContext(const DocumentInit& initializer) { |
| DCHECK(!getSecurityOrigin()); |
| |
| if (!initializer.hasSecurityContext()) { |
| // No source for a security context. |
| // This can occur via document.implementation.createDocument(). |
| m_cookieURL = KURL(ParsedURLString, emptyString()); |
| setSecurityOrigin(SecurityOrigin::createUnique()); |
| initContentSecurityPolicy(); |
| // Unique security origins cannot have a suborigin |
| return; |
| } |
| |
| // In the common case, create the security context from the currently |
| // loading URL with a fresh content security policy. |
| enforceSandboxFlags(initializer.getSandboxFlags()); |
| setInsecureRequestPolicy(initializer.getInsecureRequestPolicy()); |
| if (initializer.insecureNavigationsToUpgrade()) { |
| for (auto toUpgrade : *initializer.insecureNavigationsToUpgrade()) |
| addInsecureNavigationUpgrade(toUpgrade); |
| } |
| |
| if (isSandboxed(SandboxOrigin)) { |
| m_cookieURL = m_url; |
| setSecurityOrigin(SecurityOrigin::createUnique()); |
| // If we're supposed to inherit our security origin from our |
| // owner, but we're also sandboxed, the only things we inherit are |
| // the origin's potential trustworthiness and the ability to |
| // load local resources. The latter lets about:blank iframes in |
| // file:// URL documents load images and other resources from |
| // the file system. |
| if (initializer.owner() && |
| initializer.owner()->getSecurityOrigin()->isPotentiallyTrustworthy()) |
| getSecurityOrigin()->setUniqueOriginIsPotentiallyTrustworthy(true); |
| if (initializer.owner() && |
| initializer.owner()->getSecurityOrigin()->canLoadLocalResources()) |
| getSecurityOrigin()->grantLoadLocalResources(); |
| } else if (initializer.owner()) { |
| m_cookieURL = initializer.owner()->cookieURL(); |
| // We alias the SecurityOrigins to match Firefox, see Bug 15313 |
| // https://bugs.webkit.org/show_bug.cgi?id=15313 |
| setSecurityOrigin(initializer.owner()->getSecurityOrigin()); |
| } else { |
| m_cookieURL = m_url; |
| setSecurityOrigin(SecurityOrigin::create(m_url)); |
| } |
| |
| // Set the address space before setting up CSP, as the latter may override |
| // the former via the 'treat-as-public-address' directive (see |
| // https://mikewest.github.io/cors-rfc1918/#csp). |
| if (initializer.isHostedInReservedIPRange()) { |
| setAddressSpace(getSecurityOrigin()->isLocalhost() |
| ? WebAddressSpaceLocal |
| : WebAddressSpacePrivate); |
| } else if (getSecurityOrigin()->isLocal()) { |
| // "Local" security origins (like 'file://...') are treated as having |
| // a local address space. |
| // |
| // TODO(mkwst): It's not entirely clear that this is a good idea. |
| setAddressSpace(WebAddressSpaceLocal); |
| } else { |
| setAddressSpace(WebAddressSpacePublic); |
| } |
| |
| if (importsController()) { |
| // If this document is an HTML import, grab a reference to it's master |
| // document's Content Security Policy. We don't call |
| // 'initContentSecurityPolicy' in this case, as we can't rebind the master |
| // document's policy object: its ExecutionContext needs to remain tied to |
| // the master document. |
| setContentSecurityPolicy( |
| importsController()->master()->contentSecurityPolicy()); |
| } else { |
| initContentSecurityPolicy(); |
| } |
| |
| if (getSecurityOrigin()->hasSuborigin()) |
| enforceSuborigin(*getSecurityOrigin()->suborigin()); |
| |
| if (Settings* settings = initializer.settings()) { |
| if (!settings->getWebSecurityEnabled()) { |
| // Web security is turned off. We should let this document access every |
| // other document. This is used primary by testing harnesses for web |
| // sites. |
| getSecurityOrigin()->grantUniversalAccess(); |
| } else if (getSecurityOrigin()->isLocal()) { |
| if (settings->getAllowUniversalAccessFromFileURLs()) { |
| // Some clients want local URLs to have universal access, but that |
| // setting is dangerous for other clients. |
| getSecurityOrigin()->grantUniversalAccess(); |
| } else if (!settings->getAllowFileAccessFromFileURLs()) { |
| // Some clients do not want local URLs to have access to other local |
| // URLs. |
| getSecurityOrigin()->blockLocalAccessFromLocalOrigin(); |
| } |
| } |
| } |
| |
| if (initializer.shouldTreatURLAsSrcdocDocument()) { |
| m_isSrcdocDocument = true; |
| setBaseURLOverride(initializer.parentBaseURL()); |
| } |
| |
| if (getSecurityOrigin()->isUnique() && |
| SecurityOrigin::create(m_url)->isPotentiallyTrustworthy()) |
| getSecurityOrigin()->setUniqueOriginIsPotentiallyTrustworthy(true); |
| |
| if (getSecurityOrigin()->hasSuborigin()) |
| enforceSuborigin(*getSecurityOrigin()->suborigin()); |
| } |
| |
| void Document::initContentSecurityPolicy(ContentSecurityPolicy* csp) { |
| setContentSecurityPolicy(csp ? csp : ContentSecurityPolicy::create()); |
| |
| // We inherit the parent/opener's CSP for documents with "local" schemes: |
| // 'about', 'blob', 'data', and 'filesystem'. We also inherit CSP for |
| // documents with empty/invalid URLs because we treat those URLs as |
| // 'about:blank' in Blink. |
| // |
| // https://w3c.github.io/webappsec-csp/#initialize-document-csp |
| // |
| // TODO(dcheng): This is similar enough to work we're doing in |
| // 'DocumentLoader::ensureWriter' that it might make sense to combine them. |
| if (m_frame) { |
| Frame* inheritFrom = m_frame->tree().parent() ? m_frame->tree().parent() |
| : m_frame->client()->opener(); |
| if (inheritFrom && m_frame != inheritFrom) { |
| DCHECK(inheritFrom->securityContext() && |
| inheritFrom->securityContext()->contentSecurityPolicy()); |
| ContentSecurityPolicy* policyToInherit = |
| inheritFrom->securityContext()->contentSecurityPolicy(); |
| if (m_url.isEmpty() || m_url.protocolIsAbout() || |
| m_url.protocolIsData() || m_url.protocolIs("blob") || |
| m_url.protocolIs("filesystem")) { |
| contentSecurityPolicy()->copyStateFrom(policyToInherit); |
| } |
| // Plugin documents inherit their parent/opener's 'plugin-types' directive |
| // regardless of URL. |
| if (isPluginDocument()) |
| contentSecurityPolicy()->copyPluginTypesFrom(policyToInherit); |
| } |
| } |
| contentSecurityPolicy()->bindToExecutionContext(this); |
| } |
| |
| bool Document::isSecureTransitionTo(const KURL& url) const { |
| RefPtr<SecurityOrigin> other = SecurityOrigin::create(url); |
| return getSecurityOrigin()->canAccess(other.get()); |
| } |
| |
| bool Document::allowInlineEventHandler(Node* node, |
| EventListener* listener, |
| const String& contextURL, |
| const WTF::OrdinalNumber& contextLine) { |
| Element* element = node && node->isElementNode() ? toElement(node) : nullptr; |
| if (!ContentSecurityPolicy::shouldBypassMainWorld(this) && |
| !contentSecurityPolicy()->allowInlineEventHandler( |
| element, listener->code(), contextURL, contextLine)) |
| return false; |
| |
| // HTML says that inline script needs browsing context to create its execution |
| // environment. |
| // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-handler-attributes |
| // Also, if the listening node came from other document, which happens on |
| // context-less event dispatching, we also need to ask the owner document of |
| // the node. |
| LocalFrame* frame = executingFrame(); |
| if (!frame) |
| return false; |
| if (!frame->script().canExecuteScripts(NotAboutToExecuteScript)) |
| return false; |
| if (node && node->document() != this && |
| !node->document().allowInlineEventHandler(node, listener, contextURL, |
| contextLine)) |
| return false; |
| |
| return true; |
| } |
| |
| bool Document::allowExecutingScripts(Node* node) { |
| // FIXME: Eventually we'd like to evaluate scripts which are inserted into a |
| // viewless document but this'll do for now. |
| // See http://bugs.webkit.org/show_bug.cgi?id=5727 |
| LocalFrame* frame = executingFrame(); |
| if (!frame) |
| return false; |
| if (!node->document().executingFrame()) |
| return false; |
| if (!frame->script().canExecuteScripts(AboutToExecuteScript)) |
| return false; |
| return true; |
| } |
| |
| void Document::enforceSandboxFlags(SandboxFlags mask) { |
| RefPtr<SecurityOrigin> standInOrigin = getSecurityOrigin(); |
| applySandboxFlags(mask); |
| // Send a notification if the origin has been updated. |
| if (standInOrigin && !standInOrigin->isUnique() && |
| getSecurityOrigin()->isUnique()) { |
| getSecurityOrigin()->setUniqueOriginIsPotentiallyTrustworthy( |
| standInOrigin->isPotentiallyTrustworthy()); |
| if (frame()) |
| frame()->loader().client()->didUpdateToUniqueOrigin(); |
| } |
| } |
| |
| void Document::updateSecurityOrigin(PassRefPtr<SecurityOrigin> origin) { |
| setSecurityOrigin(std::move(origin)); |
| didUpdateSecurityOrigin(); |
| } |
| |
| void Document::didUpdateSecurityOrigin() { |
| if (!m_frame) |
| return; |
| m_frame->script().updateSecurityOrigin(getSecurityOrigin()); |
| } |
| |
| bool Document::isContextThread() const { |
| return isMainThread(); |
| } |
| |
| void Document::updateFocusAppearanceSoon( |
| SelectionBehaviorOnFocus selectionbehavioronfocus) { |
| m_updateFocusAppearanceSelectionBahavior = selectionbehavioronfocus; |
| if (!m_updateFocusAppearanceTimer.isActive()) |
| m_updateFocusAppearanceTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void Document::cancelFocusAppearanceUpdate() { |
| m_updateFocusAppearanceTimer.stop(); |
| } |
| |
| void Document::updateFocusAppearanceTimerFired(TimerBase*) { |
| Element* element = focusedElement(); |
| if (!element) |
| return; |
| updateStyleAndLayout(); |
| if (element->isFocusable()) |
| element->updateFocusAppearance(m_updateFocusAppearanceSelectionBahavior); |
| } |
| |
| void Document::attachRange(Range* range) { |
| DCHECK(!m_ranges.contains(range)); |
| m_ranges.add(range); |
| } |
| |
| void Document::detachRange(Range* range) { |
| // We don't ASSERT m_ranges.contains(range) to allow us to call this |
| // unconditionally to fix: https://bugs.webkit.org/show_bug.cgi?id=26044 |
| m_ranges.remove(range); |
| } |
| |
| void Document::initDNSPrefetch() { |
| Settings* settings = this->settings(); |
| |
| m_haveExplicitlyDisabledDNSPrefetch = false; |
| m_isDNSPrefetchEnabled = settings && settings->getDNSPrefetchingEnabled() && |
| getSecurityOrigin()->protocol() == "http"; |
| |
| // Inherit DNS prefetch opt-out from parent frame |
| if (Document* parent = parentDocument()) { |
| if (!parent->isDNSPrefetchEnabled()) |
| m_isDNSPrefetchEnabled = false; |
| } |
| } |
| |
| void Document::parseDNSPrefetchControlHeader(const String& dnsPrefetchControl) { |
| if (equalIgnoringCase(dnsPrefetchControl, "on") && |
| !m_haveExplicitlyDisabledDNSPrefetch) { |
| m_isDNSPrefetchEnabled = true; |
| return; |
| } |
| |
| m_isDNSPrefetchEnabled = false; |
| m_haveExplicitlyDisabledDNSPrefetch = true; |
| } |
| |
| IntersectionObserverController* Document::intersectionObserverController() { |
| return m_intersectionObserverController; |
| } |
| |
| IntersectionObserverController& |
| Document::ensureIntersectionObserverController() { |
| if (!m_intersectionObserverController) |
| m_intersectionObserverController = |
| IntersectionObserverController::create(this); |
| return *m_intersectionObserverController; |
| } |
| |
| ResizeObserverController& Document::ensureResizeObserverController() { |
| if (!m_resizeObserverController) |
| m_resizeObserverController = new ResizeObserverController(); |
| return *m_resizeObserverController; |
| } |
| |
| static void runAddConsoleMessageTask(MessageSource source, |
| MessageLevel level, |
| const String& message, |
| ExecutionContext* context) { |
| context->addConsoleMessage(ConsoleMessage::create(source, level, message)); |
| } |
| |
| void Document::addConsoleMessage(ConsoleMessage* consoleMessage) { |
| if (!isContextThread()) { |
| postTask(TaskType::Unthrottled, BLINK_FROM_HERE, |
| createCrossThreadTask( |
| &runAddConsoleMessageTask, consoleMessage->source(), |
| consoleMessage->level(), consoleMessage->message())); |
| return; |
| } |
| |
| if (!m_frame) |
| return; |
| |
| if (consoleMessage->location()->isUnknown()) { |
| // TODO(dgozman): capture correct location at call places instead. |
| unsigned lineNumber = 0; |
| if (!isInDocumentWrite() && scriptableDocumentParser()) { |
| ScriptableDocumentParser* parser = scriptableDocumentParser(); |
| if (parser->isParsingAtLineNumber()) |
| lineNumber = parser->lineNumber().oneBasedInt(); |
| } |
| consoleMessage = ConsoleMessage::create( |
| consoleMessage->source(), consoleMessage->level(), |
| consoleMessage->message(), |
| SourceLocation::create(url().getString(), lineNumber, 0, nullptr)); |
| } |
| m_frame->console().addMessage(consoleMessage); |
| } |
| |
| void Document::postTask(TaskType taskType, |
| const WebTraceLocation& location, |
| std::unique_ptr<ExecutionContextTask> task, |
| const String& taskNameForInstrumentation) { |
| if (!taskNameForInstrumentation.isEmpty()) { |
| InspectorInstrumentation::asyncTaskScheduled( |
| this, taskNameForInstrumentation, task.get()); |
| } |
| |
| TaskRunnerHelper::get(taskType, this) |
| ->postTask(location, |
| crossThreadBind(&Document::runExecutionContextTask, |
| wrapCrossThreadWeakPersistent(this), |
| WTF::passed(std::move(task)), |
| !taskNameForInstrumentation.isEmpty())); |
| } |
| |
| void Document::tasksWereSuspended() { |
| scriptRunner()->suspend(); |
| |
| if (m_parser) |
| m_parser->suspendScheduledTasks(); |
| if (m_scriptedAnimationController) |
| m_scriptedAnimationController->suspend(); |
| } |
| |
| void Document::tasksWereResumed() { |
| scriptRunner()->resume(); |
| |
| if (m_parser) |
| m_parser->resumeScheduledTasks(); |
| if (m_scriptedAnimationController) |
| m_scriptedAnimationController->resume(); |
| |
| MutationObserver::resumeSuspendedObservers(); |
| if (m_domWindow) |
| DOMWindowPerformance::performance(*m_domWindow)->resumeSuspendedObservers(); |
| } |
| |
| bool Document::tasksNeedSuspension() { |
| Page* page = this->page(); |
| return page && page->suspended(); |
| } |
| |
| void Document::addToTopLayer(Element* element, const Element* before) { |
| if (element->isInTopLayer()) |
| return; |
| |
| DCHECK(!m_topLayerElements.contains(element)); |
| DCHECK(!before || m_topLayerElements.contains(before)); |
| if (before) { |
| size_t beforePosition = m_topLayerElements.find(before); |
| m_topLayerElements.insert(beforePosition, element); |
| } else { |
| m_topLayerElements.push_back(element); |
| } |
| element->setIsInTopLayer(true); |
| } |
| |
| void Document::removeFromTopLayer(Element* element) { |
| if (!element->isInTopLayer()) |
| return; |
| size_t position = m_topLayerElements.find(element); |
| DCHECK_NE(position, kNotFound); |
| m_topLayerElements.remove(position); |
| element->setIsInTopLayer(false); |
| } |
| |
| HTMLDialogElement* Document::activeModalDialog() const { |
| if (m_topLayerElements.isEmpty()) |
| return 0; |
| return toHTMLDialogElement(m_topLayerElements.back().get()); |
| } |
| |
| void Document::exitPointerLock() { |
| if (!page()) |
| return; |
| if (Element* target = page()->pointerLockController().element()) { |
| if (target->document() != this) |
| return; |
| page()->pointerLockController().requestPointerUnlock(); |
| } |
| } |
| |
| Element* Document::pointerLockElement() const { |
| if (!page() || page()->pointerLockController().lockPending()) |
| return 0; |
| if (Element* element = page()->pointerLockController().element()) { |
| if (element->document() == this) |
| return element; |
| } |
| return 0; |
| } |
| |
| void Document::suppressLoadEvent() { |
| if (!loadEventFinished()) |
| m_loadEventProgress = LoadEventCompleted; |
| } |
| |
| void Document::decrementLoadEventDelayCount() { |
| DCHECK(m_loadEventDelayCount); |
| --m_loadEventDelayCount; |
| |
| if (!m_loadEventDelayCount) |
| checkLoadEventSoon(); |
| } |
| |
| void Document::checkLoadEventSoon() { |
| if (frame() && !m_loadEventDelayTimer.isActive()) |
| m_loadEventDelayTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| bool Document::isDelayingLoadEvent() { |
| // Always delay load events until after garbage collection. |
| // This way we don't have to explicitly delay load events via |
| // incrementLoadEventDelayCount and decrementLoadEventDelayCount in |
| // Node destructors. |
| if (ThreadState::current()->sweepForbidden()) { |
| if (!m_loadEventDelayCount) |
| checkLoadEventSoon(); |
| return true; |
| } |
| return m_loadEventDelayCount; |
| } |
| |
| void Document::loadEventDelayTimerFired(TimerBase*) { |
| if (frame()) |
| frame()->loader().checkCompleted(); |
| } |
| |
| void Document::loadPluginsSoon() { |
| // FIXME: Remove this timer once we don't need to compute layout to load |
| // plugins. |
| if (!m_pluginLoadingTimer.isActive()) |
| m_pluginLoadingTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void Document::pluginLoadingTimerFired(TimerBase*) { |
| updateStyleAndLayout(); |
| } |
| |
| ScriptedAnimationController& Document::ensureScriptedAnimationController() { |
| if (!m_scriptedAnimationController) { |
| m_scriptedAnimationController = ScriptedAnimationController::create(this); |
| // We need to make sure that we don't start up the animation controller on a |
| // background tab, for example. |
| if (!page()) |
| m_scriptedAnimationController->suspend(); |
| } |
| return *m_scriptedAnimationController; |
| } |
| |
| int Document::requestAnimationFrame(FrameRequestCallback* callback) { |
| return ensureScriptedAnimationController().registerCallback(callback); |
| } |
| |
| void Document::cancelAnimationFrame(int id) { |
| if (!m_scriptedAnimationController) |
| return; |
| m_scriptedAnimationController->cancelCallback(id); |
| } |
| |
| void Document::serviceScriptedAnimations(double monotonicAnimationStartTime) { |
| if (!m_scriptedAnimationController) |
| return; |
| m_scriptedAnimationController->serviceScriptedAnimations( |
| monotonicAnimationStartTime); |
| } |
| |
| ScriptedIdleTaskController& Document::ensureScriptedIdleTaskController() { |
| if (!m_scriptedIdleTaskController) |
| m_scriptedIdleTaskController = ScriptedIdleTaskController::create(this); |
| return *m_scriptedIdleTaskController; |
| } |
| |
| int Document::requestIdleCallback(IdleRequestCallback* callback, |
| const IdleRequestOptions& options) { |
| return ensureScriptedIdleTaskController().registerCallback(callback, options); |
| } |
| |
| void Document::cancelIdleCallback(int id) { |
| if (!m_scriptedIdleTaskController) |
| return; |
| m_scriptedIdleTaskController->cancelCallback(id); |
| } |
| |
| Touch* Document::createTouch(DOMWindow* window, |
| EventTarget* target, |
| int identifier, |
| double pageX, |
| double pageY, |
| double screenX, |
| double screenY, |
| double radiusX, |
| double radiusY, |
| float rotationAngle, |
| float force) const { |
| // Match behavior from when these types were integers, and avoid surprises |
| // from someone explicitly |
| // passing Infinity/NaN. |
| if (!std::isfinite(pageX)) |
| pageX = 0; |
| if (!std::isfinite(pageY)) |
| pageY = 0; |
| if (!std::isfinite(screenX)) |
| screenX = 0; |
| if (!std::isfinite(screenY)) |
| screenY = 0; |
| if (!std::isfinite(radiusX)) |
| radiusX = 0; |
| if (!std::isfinite(radiusY)) |
| radiusY = 0; |
| if (!std::isfinite(rotationAngle)) |
| rotationAngle = 0; |
| if (!std::isfinite(force)) |
| force = 0; |
| |
| if (radiusX || radiusY || rotationAngle || force) |
| UseCounter::count(*this, |
| UseCounter::DocumentCreateTouchMoreThanSevenArguments); |
| |
| // FIXME: It's not clear from the documentation at |
| // http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html |
| // when this method should throw and nor is it by inspection of iOS behavior. |
| // It would be nice to verify any cases where it throws under iOS and |
| // implement them here. See https://bugs.webkit.org/show_bug.cgi?id=47819 |
| LocalFrame* frame = window && window->isLocalDOMWindow() |
| ? blink::toLocalDOMWindow(window)->frame() |
| : this->frame(); |
| return Touch::create(frame, target, identifier, FloatPoint(screenX, screenY), |
| FloatPoint(pageX, pageY), FloatSize(radiusX, radiusY), |
| rotationAngle, force, String()); |
| } |
| |
| TouchList* Document::createTouchList(HeapVector<Member<Touch>>& touches) const { |
| return TouchList::adopt(touches); |
| } |
| |
| DocumentLoader* Document::loader() const { |
| if (!m_frame) |
| return 0; |
| |
| DocumentLoader* loader = m_frame->loader().documentLoader(); |
| if (!loader) |
| return 0; |
| |
| if (m_frame->document() != this) |
| return 0; |
| |
| return loader; |
| } |
| |
| Node* eventTargetNodeForDocument(Document* doc) { |
| if (!doc) |
| return 0; |
| Node* node = doc->focusedElement(); |
| if (!node && doc->isPluginDocument()) { |
| PluginDocument* pluginDocument = toPluginDocument(doc); |
| node = pluginDocument->pluginNode(); |
| } |
| if (!node && doc->isHTMLDocument()) |
| node = doc->body(); |
| if (!node) |
| node = doc->documentElement(); |
| return node; |
| } |
| |
| void Document::adjustFloatQuadsForScrollAndAbsoluteZoom( |
| Vector<FloatQuad>& quads, |
| LayoutObject& layoutObject) { |
| if (!view()) |
| return; |
| |
| LayoutRect visibleContentRect(view()->visibleContentRect()); |
| for (size_t i = 0; i < quads.size(); ++i) { |
| quads[i].move(-FloatSize(visibleContentRect.x().toFloat(), |
| visibleContentRect.y().toFloat())); |
| adjustFloatQuadForAbsoluteZoom(quads[i], layoutObject); |
| } |
| } |
| |
| void Document::adjustFloatRectForScrollAndAbsoluteZoom( |
| FloatRect& rect, |
| LayoutObject& layoutObject) { |
| if (!view()) |
| return; |
| |
| LayoutRect visibleContentRect(view()->visibleContentRect()); |
| rect.move(-FloatSize(visibleContentRect.x().toFloat(), |
| visibleContentRect.y().toFloat())); |
| adjustFloatRectForAbsoluteZoom(rect, layoutObject); |
| } |
| |
| void Document::setThreadedParsingEnabledForTesting(bool enabled) { |
| s_threadedParsingEnabledForTesting = enabled; |
| } |
| |
| bool Document::threadedParsingEnabledForTesting() { |
| return s_threadedParsingEnabledForTesting; |
| } |
| |
| SnapCoordinator* Document::snapCoordinator() { |
| if (RuntimeEnabledFeatures::cssScrollSnapPointsEnabled() && |
| !m_snapCoordinator) |
| m_snapCoordinator = SnapCoordinator::create(); |
| |
| return m_snapCoordinator.get(); |
| } |
| |
| void Document::setContextFeatures(ContextFeatures& features) { |
| m_contextFeatures = &features; |
| } |
| |
| static LayoutObject* nearestCommonHoverAncestor(LayoutObject* obj1, |
| LayoutObject* obj2) { |
| if (!obj1 || !obj2) |
| return 0; |
| |
| for (LayoutObject* currObj1 = obj1; currObj1; |
| currObj1 = currObj1->hoverAncestor()) { |
| for (LayoutObject* currObj2 = obj2; currObj2; |
| currObj2 = currObj2->hoverAncestor()) { |
| if (currObj1 == currObj2) |
| return currObj1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| void Document::updateHoverActiveState(const HitTestRequest& request, |
| Element* innerElement, |
| Scrollbar* hitScrollbar) { |
| DCHECK(!request.readOnly()); |
| |
| // Only cancel hover state when hitting native scrollbar because custom |
| // scrollbar's style depends on the owner element's hover state. |
| bool hitNativeScrollbar = hitScrollbar && !hitScrollbar->isCustomScrollbar(); |
| |
| if (request.active() && m_frame && !hitNativeScrollbar) |
| m_frame->eventHandler().notifyElementActivated(); |
| |
| Element* innerElementInDocument = hitNativeScrollbar ? nullptr : innerElement; |
| while (innerElementInDocument && innerElementInDocument->document() != this) { |
| innerElementInDocument->document().updateHoverActiveState( |
| request, innerElementInDocument, hitScrollbar); |
| innerElementInDocument = innerElementInDocument->document().localOwner(); |
| } |
| |
| updateDistribution(); |
| Element* oldActiveElement = activeHoverElement(); |
| if (oldActiveElement && (!request.active() || hitNativeScrollbar)) { |
| // The oldActiveElement layoutObject is null, dropped on :active by setting |
| // display: none, for instance. We still need to clear the ActiveChain as |
| // the mouse is released. |
| for (Node* node = oldActiveElement; node; |
| node = FlatTreeTraversal::parent(*node)) { |
| DCHECK(!node->isTextNode()); |
| node->setActive(false); |
| m_userActionElements.setInActiveChain(node, false); |
| } |
| setActiveHoverElement(nullptr); |
| } else { |
| Element* newActiveElement = innerElementInDocument; |
| if (!oldActiveElement && newActiveElement && |
| !newActiveElement->isDisabledFormControl() && request.active() && |
| !request.touchMove()) { |
| // We are setting the :active chain and freezing it. If future moves |
| // happen, they will need to reference this chain. |
| for (Node* node = newActiveElement; node; |
| node = FlatTreeTraversal::parent(*node)) { |
| DCHECK(!node->isTextNode()); |
| m_userActionElements.setInActiveChain(node, true); |
| } |
| setActiveHoverElement(newActiveElement); |
| } |
| } |
| // If the mouse has just been pressed, set :active on the chain. Those (and |
| // only those) nodes should remain :active until the mouse is released. |
| bool allowActiveChanges = !oldActiveElement && activeHoverElement(); |
| |
| // If the mouse is down and if this is a mouse move event, we want to restrict |
| // changes in :hover/:active to only apply to elements that are in the :active |
| // chain that we froze at the time the mouse went down. |
| bool mustBeInActiveChain = request.active() && request.move(); |
| |
| Node* oldHoverNode = hoverNode(); |
| |
| // Check to see if the hovered node has changed. |
| // If it hasn't, we do not need to do anything. |
| Node* newHoverNode = innerElementInDocument; |
| while (newHoverNode && !newHoverNode->layoutObject()) |
| newHoverNode = newHoverNode->parentOrShadowHostNode(); |
| |
| // Update our current hover node. |
| setHoverNode(newHoverNode); |
| |
| // We have two different objects. Fetch their layoutObjects. |
| LayoutObject* oldHoverObj = |
| oldHoverNode ? oldHoverNode->layoutObject() : nullptr; |
| LayoutObject* newHoverObj = |
| newHoverNode ? newHoverNode->layoutObject() : nullptr; |
| |
| // Locate the common ancestor layout object for the two layoutObjects. |
| LayoutObject* ancestor = nearestCommonHoverAncestor(oldHoverObj, newHoverObj); |
| Node* ancestorNode(ancestor ? ancestor->node() : nullptr); |
| |
| HeapVector<Member<Node>, 32> nodesToRemoveFromChain; |
| HeapVector<Member<Node>, 32> nodesToAddToChain; |
| |
| if (oldHoverObj != newHoverObj) { |
| // If the old hovered node is not nil but it's layoutObject 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 (oldHoverNode && !oldHoverObj) { |
| for (Node& node : NodeTraversal::inclusiveAncestorsOf(*oldHoverNode)) { |
| if (!mustBeInActiveChain || |
| (node.isElementNode() && toElement(node).inActiveChain())) |
| nodesToRemoveFromChain.push_back(node); |
| } |
| } |
| |
| // The old hover path only needs to be cleared up to (and not including) the |
| // common ancestor; |
| for (LayoutObject* curr = oldHoverObj; curr && curr != ancestor; |
| curr = curr->hoverAncestor()) { |
| if (curr->node() && !curr->isText() && |
| (!mustBeInActiveChain || curr->node()->inActiveChain())) |
| nodesToRemoveFromChain.push_back(curr->node()); |
| } |
| |
| // TODO(mustaq): The two loops above may push a single node twice into |
| // nodesToRemoveFromChain. There must be a better way. |
| } |
| |
| // Now set the hover state for our new object up to the root. |
| for (LayoutObject* curr = newHoverObj; curr; curr = curr->hoverAncestor()) { |
| if (curr->node() && !curr->isText() && |
| (!mustBeInActiveChain || curr->node()->inActiveChain())) |
| nodesToAddToChain.push_back(curr->node()); |
| } |
| |
| size_t removeCount = nodesToRemoveFromChain.size(); |
| for (size_t i = 0; i < removeCount; ++i) { |
| nodesToRemoveFromChain[i]->setHovered(false); |
| } |
| |
| bool sawCommonAncestor = false; |
| size_t addCount = nodesToAddToChain.size(); |
| for (size_t i = 0; i < addCount; ++i) { |
| // Elements past the common ancestor do not change hover state, but might |
| // change active state. |
| if (ancestorNode && nodesToAddToChain[i] == ancestorNode) |
| sawCommonAncestor = true; |
| if (allowActiveChanges) |
| nodesToAddToChain[i]->setActive(true); |
| if (!sawCommonAncestor || nodesToAddToChain[i] == m_hoverNode) { |
| nodesToAddToChain[i]->setHovered(true); |
| } |
| } |
| } |
| |
| bool Document::haveScriptBlockingStylesheetsLoaded() const { |
| return m_styleEngine->haveScriptBlockingStylesheetsLoaded(); |
| } |
| |
| bool Document::haveRenderBlockingStylesheetsLoaded() const { |
| if (RuntimeEnabledFeatures::cssInBodyDoesNotBlockPaintEnabled()) |
| return m_styleEngine->haveRenderBlockingStylesheetsLoaded(); |
| return m_styleEngine->haveScriptBlockingStylesheetsLoaded(); |
| } |
| |
| Locale& Document::getCachedLocale(const AtomicString& locale) { |
| AtomicString localeKey = locale; |
| if (locale.isEmpty() || |
| !RuntimeEnabledFeatures::langAttributeAwareFormControlUIEnabled()) |
| return Locale::defaultLocale(); |
| LocaleIdentifierToLocaleMap::AddResult result = |
| m_localeCache.add(localeKey, nullptr); |
| if (result.isNewEntry) |
| result.storedValue->value = Locale::create(localeKey); |
| return *(result.storedValue->value); |
| } |
| |
| AnimationClock& Document::animationClock() { |
| DCHECK(page()); |
| return page()->animator().clock(); |
| } |
| |
| Document& Document::ensureTemplateDocument() { |
| if (isTemplateDocument()) |
| return *this; |
| |
| if (m_templateDocument) |
| return *m_templateDocument; |
| |
| if (isHTMLDocument()) { |
| DocumentInit init = DocumentInit::fromContext(contextDocument(), blankURL()) |
| .withNewRegistrationContext(); |
| m_templateDocument = HTMLDocument::create(init); |
| } else { |
| m_templateDocument = Document::create(DocumentInit(blankURL())); |
| } |
| |
| m_templateDocument->m_templateDocumentHost = this; // balanced in dtor. |
| |
| return *m_templateDocument.get(); |
| } |
| |
| void Document::didAssociateFormControl(Element* element) { |
| if (!frame() || !frame()->page() || !loadEventFinished()) |
| return; |
| |
| // We add a slight delay because this could be called rapidly. |
| if (!m_didAssociateFormControlsTimer.isActive()) |
| m_didAssociateFormControlsTimer.startOneShot(0.3, BLINK_FROM_HERE); |
| } |
| |
| void Document::didAssociateFormControlsTimerFired(TimerBase* timer) { |
| DCHECK_EQ(timer, &m_didAssociateFormControlsTimer); |
| if (!frame() || !frame()->page()) |
| return; |
| |
| frame()->page()->chromeClient().didAssociateFormControlsAfterLoad(frame()); |
| } |
| |
| float Document::devicePixelRatio() const { |
| return m_frame ? m_frame->devicePixelRatio() : 1.0; |
| } |
| |
| TextAutosizer* Document::textAutosizer() { |
| if (!m_textAutosizer) |
| m_textAutosizer = TextAutosizer::create(this); |
| return m_textAutosizer.get(); |
| } |
| |
| void Document::setAutofocusElement(Element* element) { |
| if (!element) { |
| m_autofocusElement = nullptr; |
| return; |
| } |
| if (m_hasAutofocused) |
| return; |
| m_hasAutofocused = true; |
| DCHECK(!m_autofocusElement); |
| m_autofocusElement = element; |
| postTask(TaskType::UserInteraction, BLINK_FROM_HERE, |
| createSameThreadTask(&runAutofocusTask)); |
| } |
| |
| Element* Document::activeElement() const { |
| if (Element* element = adjustedFocusedElement()) |
| return element; |
| return body(); |
| } |
| |
| bool Document::hasFocus() const { |
| return page() && page()->focusController().isDocumentFocused(*this); |
| } |
| |
| template <unsigned type> |
| bool shouldInvalidateNodeListCachesForAttr( |
| const HeapHashSet<WeakMember<const LiveNodeListBase>> nodeLists[], |
| const QualifiedName& attrName) { |
| if (!nodeLists[type].isEmpty() && |
| LiveNodeListBase::shouldInvalidateTypeOnAttributeChange( |
| static_cast<NodeListInvalidationType>(type), attrName)) |
| return true; |
| return shouldInvalidateNodeListCachesForAttr<type + 1>(nodeLists, attrName); |
| } |
| |
| template <> |
| bool shouldInvalidateNodeListCachesForAttr<numNodeListInvalidationTypes>( |
| const HeapHashSet<WeakMember<const LiveNodeListBase>>[], |
| const QualifiedName&) { |
| return false; |
| } |
| |
| bool Document::shouldInvalidateNodeListCaches( |
| const QualifiedName* attrName) const { |
| if (attrName) { |
| return shouldInvalidateNodeListCachesForAttr< |
| DoNotInvalidateOnAttributeChanges + 1>(m_nodeLists, *attrName); |
| } |
| |
| for (int type = 0; type < numNodeListInvalidationTypes; ++type) { |
| if (!m_nodeLists[type].isEmpty()) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Document::invalidateNodeListCaches(const QualifiedName* attrName) { |
| for (const LiveNodeListBase* list : m_listsInvalidatedAtDocument) |
| list->invalidateCacheForAttribute(attrName); |
| } |
| |
| void Document::platformColorsChanged() { |
| if (!isActive()) |
| return; |
| |
| styleEngine().platformColorsChanged(); |
| } |
| |
| bool Document::isSecureContext( |
| String& errorMessage, |
| const SecureContextCheck privilegeContextCheck) const { |
| if (!isSecureContext(privilegeContextCheck)) { |
| errorMessage = SecurityOrigin::isPotentiallyTrustworthyErrorMessage(); |
| return false; |
| } |
| return true; |
| } |
| |
| bool Document::isSecureContext( |
| const SecureContextCheck privilegeContextCheck) const { |
| bool isSecure = isSecureContextImpl(privilegeContextCheck); |
| if (getSandboxFlags() != SandboxNone) { |
| UseCounter::count( |
| *this, isSecure |
| ? UseCounter::SecureContextCheckForSandboxedOriginPassed |
| : UseCounter::SecureContextCheckForSandboxedOriginFailed); |
| } |
| UseCounter::count(*this, isSecure ? UseCounter::SecureContextCheckPassed |
| : UseCounter::SecureContextCheckFailed); |
| return isSecure; |
| } |
| |
| void Document::enforceInsecureRequestPolicy(WebInsecureRequestPolicy policy) { |
| // Combine the new policy with the existing policy, as a base policy may be |
| // inherited from a remote parent before this page's policy is set. In other |
| // words, insecure requests should be upgraded or blocked if _either_ the |
| // existing policy or the newly enforced policy triggers upgrades or |
| // blockage. |
| setInsecureRequestPolicy(getInsecureRequestPolicy() | policy); |
| if (frame()) |
| frame()->loader().client()->didEnforceInsecureRequestPolicy( |
| getInsecureRequestPolicy()); |
| } |
| |
| void Document::setShadowCascadeOrder(ShadowCascadeOrder order) { |
| DCHECK_NE(order, ShadowCascadeOrder::ShadowCascadeNone); |
| |
| if (order == m_shadowCascadeOrder) |
| return; |
| |
| if (order == ShadowCascadeOrder::ShadowCascadeV0) { |
| m_mayContainV0Shadow = true; |
| if (m_shadowCascadeOrder == ShadowCascadeOrder::ShadowCascadeV1) |
| UseCounter::count(*this, UseCounter::MixedShadowRootV0AndV1); |
| } |
| |
| // For V0 -> V1 upgrade, we need style recalculation for the whole document. |
| if (m_shadowCascadeOrder == ShadowCascadeOrder::ShadowCascadeV0 && |
| order == ShadowCascadeOrder::ShadowCascadeV1) { |
| this->setNeedsStyleRecalc( |
| SubtreeStyleChange, |
| StyleChangeReasonForTracing::create(StyleChangeReason::Shadow)); |
| UseCounter::count(*this, UseCounter::MixedShadowRootV0AndV1); |
| } |
| |
| if (order > m_shadowCascadeOrder) |
| m_shadowCascadeOrder = order; |
| } |
| |
| LayoutViewItem Document::layoutViewItem() const { |
| return LayoutViewItem(m_layoutView); |
| } |
| |
| PropertyRegistry* Document::propertyRegistry() { |
| // TODO(timloh): When the flag is removed, return a reference instead. |
| if (!m_propertyRegistry && RuntimeEnabledFeatures::cssVariables2Enabled()) |
| m_propertyRegistry = PropertyRegistry::create(); |
| return m_propertyRegistry; |
| } |
| |
| const PropertyRegistry* Document::propertyRegistry() const { |
| return const_cast<Document*>(this)->propertyRegistry(); |
| } |
| |
| void Document::incrementPasswordCount() { |
| ++m_passwordCount; |
| if (isSecureContext() || m_passwordCount != 1) { |
| // The browser process only cares about passwords on pages where the |
| // top-level URL is not secure. Secure contexts must have a top-level |
| // URL that is secure, so there is no need to send notifications for |
| // password fields in secure contexts. |
| // |
| // Also, only send a message on the first visible password field; the |
| // browser process doesn't care about the presence of additional |
| // password fields beyond that. |
| return; |
| } |
| sendSensitiveInputVisibility(); |
| } |
| |
| void Document::decrementPasswordCount() { |
| DCHECK_GT(m_passwordCount, 0u); |
| --m_passwordCount; |
| if (isSecureContext() || m_passwordCount > 0) |
| return; |
| sendSensitiveInputVisibility(); |
| } |
| |
| DEFINE_TRACE(Document) { |
| visitor->trace(m_importsController); |
| visitor->trace(m_docType); |
| visitor->trace(m_implementation); |
| visitor->trace(m_autofocusElement); |
| visitor->trace(m_focusedElement); |
| visitor->trace(m_sequentialFocusNavigationStartingPoint); |
| visitor->trace(m_hoverNode); |
| visitor->trace(m_activeHoverElement); |
| visitor->trace(m_documentElement); |
| visitor->trace(m_rootScrollerController); |
| visitor->trace(m_titleElement); |
| visitor->trace(m_axObjectCache); |
| visitor->trace(m_markers); |
| visitor->trace(m_cssTarget); |
| visitor->trace(m_currentScriptStack); |
| visitor->trace(m_scriptRunner); |
| visitor->trace(m_listsInvalidatedAtDocument); |
| for (int i = 0; i < numNodeListInvalidationTypes; ++i) |
| visitor->trace(m_nodeLists[i]); |
| visitor->trace(m_topLayerElements); |
| visitor->trace(m_elemSheet); |
| visitor->trace(m_nodeIterators); |
| visitor->trace(m_ranges); |
| visitor->trace(m_styleEngine); |
| visitor->trace(m_formController); |
| visitor->trace(m_visitedLinkState); |
| visitor->trace(m_frame); |
| visitor->trace(m_domWindow); |
| visitor->trace(m_fetcher); |
| visitor->trace(m_parser); |
| visitor->trace(m_contextFeatures); |
| visitor->trace(m_styleSheetList); |
| visitor->trace(m_documentTiming); |
| visitor->trace(m_mediaQueryMatcher); |
| visitor->trace(m_scriptedAnimationController); |
| visitor->trace(m_scriptedIdleTaskController); |
| visitor->trace(m_textAutosizer); |
| visitor->trace(m_registrationContext); |
| visitor->trace(m_customElementMicrotaskRunQueue); |
| visitor->trace(m_elementDataCache); |
| visitor->trace(m_useElementsNeedingUpdate); |
| visitor->trace(m_timers); |
| visitor->trace(m_templateDocument); |
| visitor->trace(m_templateDocumentHost); |
| visitor->trace(m_userActionElements); |
| visitor->trace(m_svgExtensions); |
| visitor->trace(m_timeline); |
| visitor->trace(m_compositorPendingAnimations); |
| visitor->trace(m_contextDocument); |
| visitor->trace(m_canvasFontCache); |
| visitor->trace(m_intersectionObserverController); |
| visitor->trace(m_snapCoordinator); |
| visitor->trace(m_resizeObserverController); |
| visitor->trace(m_propertyRegistry); |
| visitor->trace(m_styleReattachDataMap); |
| Supplementable<Document>::trace(visitor); |
| TreeScope::trace(visitor); |
| ContainerNode::trace(visitor); |
| ExecutionContext::trace(visitor); |
| SecurityContext::trace(visitor); |
| SynchronousMutationNotifier::trace(visitor); |
| } |
| |
| void Document::maybeRecordLoadReason(WouldLoadReason reason) { |
| DCHECK(m_wouldLoadReason == Created || reason != Created); |
| DCHECK(frame()); |
| if (m_wouldLoadReason == Created && frame()->isCrossOriginSubframe() && |
| frame()->loader().stateMachine()->committedFirstRealDocumentLoad()) { |
| recordLoadReasonToHistogram(reason); |
| } |
| m_wouldLoadReason = reason; |
| } |
| |
| DEFINE_TRACE_WRAPPERS(Document) { |
| visitor->traceWrappers(m_importsController); |
| visitor->traceWrappers(m_implementation); |
| visitor->traceWrappers(m_styleSheetList); |
| visitor->traceWrappers(m_styleEngine); |
| for (int i = 0; i < numNodeListInvalidationTypes; ++i) { |
| for (auto list : m_nodeLists[i]) { |
| if (isHTMLCollectionType(list->type())) { |
| visitor->traceWrappersWithManualWriteBarrier( |
| static_cast<const HTMLCollection*>(list.get())); |
| } else { |
| visitor->traceWrappersWithManualWriteBarrier( |
| static_cast<const LiveNodeList*>(list.get())); |
| } |
| } |
| } |
| // Cannot trace in Supplementable<Document> as it is part of platform/ and |
| // thus cannot refer to ScriptWrappableVisitor. |
| visitor->traceWrappers( |
| static_cast<FontFaceSet*>(Supplementable<Document>::m_supplements.get( |
| FontFaceSet::supplementName()))); |
| ContainerNode::traceWrappers(visitor); |
| } |
| |
| template class CORE_TEMPLATE_EXPORT Supplement<Document>; |
| |
| } // namespace blink |
| |
| #ifndef NDEBUG |
| using namespace blink; |
| |
| static WeakDocumentSet& liveDocumentSet() { |
| DEFINE_STATIC_LOCAL(WeakDocumentSet, set, ()); |
| return set; |
| } |
| |
| void showLiveDocumentInstances() { |
| WeakDocumentSet& set = liveDocumentSet(); |
| fprintf(stderr, "There are %u documents currently alive:\n", set.size()); |
| for (Document* document : set) |
| fprintf(stderr, "- Document %p URL: %s\n", document, |
| document->url().getString().utf8().data()); |
| } |
| #endif |