| /* |
| * 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/ExceptionStatePlaceholder.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/V0CustomElementConstructorBuilder.h" |
| #include "bindings/core/v8/V8DOMWrapper.h" |
| #include "bindings/core/v8/V8PerIsolateData.h" |
| #include "bindings/core/v8/WindowProxy.h" |
| #include "core/HTMLElementFactory.h" |
| #include "core/HTMLNames.h" |
| #include "core/SVGElementFactory.h" |
| #include "core/SVGNames.h" |
| #include "core/XMLNSNames.h" |
| #include "core/XMLNames.h" |
| #include "core/animation/AnimationTimeline.h" |
| #include "core/animation/CompositorPendingAnimations.h" |
| #include "core/animation/DocumentAnimations.h" |
| #include "core/css/CSSFontSelector.h" |
| #include "core/css/CSSStyleDeclaration.h" |
| #include "core/css/CSSStyleSheet.h" |
| #include "core/css/FontFaceSet.h" |
| #include "core/css/MediaQueryMatcher.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/AddConsoleMessageTask.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/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/MainThreadTaskRunner.h" |
| #include "core/dom/MutationObserver.h" |
| #include "core/dom/NodeChildRemovalTracker.h" |
| #include "core/dom/NodeComputedStyle.h" |
| #include "core/dom/NodeFilter.h" |
| #include "core/dom/NodeIntersectionObserverData.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/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/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/DragCaretController.h" |
| #include "core/editing/Editor.h" |
| #include "core/editing/FrameSelection.h" |
| #include "core/editing/InputMethodController.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/fetch/ResourceFetcher.h" |
| #include "core/frame/DOMTimer.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/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/HTMLStyleElement.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/InstanceCounters.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/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/appcache/ApplicationCacheHost.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/ScrollingCoordinator.h" |
| #include "core/page/scrolling/SnapCoordinator.h" |
| #include "core/page/scrolling/ViewportScrollCallback.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/Language.h" |
| #include "platform/LengthFunctions.h" |
| #include "platform/Logging.h" |
| #include "platform/PluginScriptForbiddenScope.h" |
| #include "platform/RuntimeEnabledFeatures.h" |
| #include "platform/ScriptForbiddenScope.h" |
| #include "platform/TraceEvent.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/network/HTTPParsers.h" |
| #include "platform/scheduler/CancellableTaskFactory.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/Platform.h" |
| #include "public/platform/WebAddressSpace.h" |
| #include "public/platform/WebFrameScheduler.h" |
| #include "public/platform/WebScheduler.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/TemporaryChange.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 bool shouldInheritSecurityOriginFromOwner(const KURL& url) |
| { |
| // http://www.whatwg.org/specs/web-apps/current-work/#origin-0 |
| // |
| // If a Document has the address "about:blank" |
| // The origin of the Document is the origin it was assigned when its browsing context was created. |
| // |
| // Note: We generalize this to all "blank" URLs and invalid URLs because we |
| // treat all of these URLs as about:blank. |
| // |
| return url.isEmpty() || url.protocolIsAbout(); |
| } |
| |
| 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(element.hasEditableStyle()); |
| |
| return element.document().frame() && element.rootEditableElement(); |
| } |
| |
| uint64_t Document::s_globalTreeVersion = 0; |
| |
| static bool s_threadedParsingEnabledForTesting = true; |
| |
| // This class doesn't work with non-Document ExecutionContext. |
| class AutofocusTask final : public ExecutionContextTask { |
| public: |
| static std::unique_ptr<AutofocusTask> create() |
| { |
| return wrapUnique(new AutofocusTask()); |
| } |
| ~AutofocusTask() override { } |
| |
| private: |
| AutofocusTask() { } |
| void performTask(ExecutionContext* context) override |
| { |
| Document* document = toDocument(context); |
| if (Element* element = document->autofocusElement()) { |
| document->setAutofocusElement(0); |
| element->focus(); |
| } |
| } |
| }; |
| |
| 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()) |
| , m_domWindow(m_frame ? m_frame->localDOMWindow() : 0) |
| , m_importsController(initializer.importsController()) |
| , m_contextFeatures(ContextFeatures::defaultSwitch()) |
| , m_wellFormed(false) |
| , m_printing(false) |
| , m_wasPrinting(false) |
| , m_paginatedForScreen(false) |
| , m_compatibilityMode(NoQuirksMode) |
| , m_compatibilityModeLocked(false) |
| , m_executeScriptsWaitingForResourcesTask(CancellableTaskFactory::create(this, &Document::executeScriptsWaitingForResources)) |
| , m_hasAutofocused(false) |
| , m_clearFocusedElementTimer(this, &Document::clearFocusedElementTimerFired) |
| , m_domTreeVersion(++s_globalTreeVersion) |
| , m_styleVersion(0) |
| , m_listenerTypes(0) |
| , m_mutationObserverTypes(0) |
| , 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_markers(new DocumentMarkerController(*this)) |
| , m_updateFocusAppearanceTimer(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_hasAnnotatedRegions(false) |
| , m_annotatedRegionsDirty(false) |
| , m_useSecureKeyboardEntryWhenActive(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(this, &Document::loadEventDelayTimerFired) |
| , m_pluginLoadingTimer(this, &Document::pluginLoadingTimerFired) |
| , m_documentTiming(*this) |
| , m_writeRecursionIsTooDeep(false) |
| , m_writeRecursionDepth(0) |
| , m_taskRunner(MainThreadTaskRunner::create(this)) |
| , m_registrationContext(initializer.registrationContext(this)) |
| , m_elementDataCacheClearTimer(this, &Document::elementDataCacheClearTimerFired) |
| , m_timeline(AnimationTimeline::create(this)) |
| , m_compositorPendingAnimations(new CompositorPendingAnimations()) |
| , m_templateDocumentHost(nullptr) |
| , m_didAssociateFormControlsTimer(this, &Document::didAssociateFormControlsTimerFired) |
| , m_timers(timerTaskRunner()->adoptClone()) |
| , m_hasViewportUnits(false) |
| , m_parserSyncPolicy(AllowAsynchronousParsing) |
| , m_nodeCount(0) |
| { |
| 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); |
| } else if (m_importsController) { |
| m_fetcher = FrameFetchContext::createContextAndFetcher(nullptr, this); |
| } else { |
| m_fetcher = ResourceFetcher::create(nullptr); |
| } |
| |
| ViewportScrollCallback* applyScroll = nullptr; |
| if (isInMainFrame()) { |
| applyScroll = RootScrollerController::createViewportApplyScroll( |
| frameHost()->topControls(), frameHost()->overscrollController()); |
| } |
| m_rootScrollerController = |
| RootScrollerController::create(*this, applyScroll); |
| |
| // 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()->activeDOMObjectsAreSuspended()); |
| |
| #ifndef NDEBUG |
| liveDocumentSet().add(this); |
| #endif |
| } |
| |
| Document::~Document() |
| { |
| DCHECK(!layoutView()); |
| 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 = wrapUnique(new SelectorQueryCache()); |
| return *m_selectorQueryCache; |
| } |
| |
| MediaQueryMatcher& Document::mediaQueryMatcher() |
| { |
| if (!m_mediaQueryMatcher) |
| m_mediaQueryMatcher = MediaQueryMatcher::create(*this); |
| return *m_mediaQueryMatcher; |
| } |
| |
| void Document::mediaQueryAffectingValueChanged() |
| { |
| m_evaluateMediaQueriesOnStyleRecalc = true; |
| styleEngine().clearMediaQueryRuleSetStyleSheets(); |
| 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.", TextCaseInsensitive)) |
| m_isMobileDocument = true; |
| } |
| // Doctype affects the interpretation of the stylesheets. |
| styleEngine().clearResolver(); |
| } |
| |
| 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::isEffectiveRootScroller(const Element& element) const |
| { |
| return m_rootScrollerController->effectiveRootScroller() == element; |
| } |
| |
| bool Document::isInMainFrame() const |
| { |
| return frame() && frame()->isMainFrame(); |
| } |
| |
| AtomicString Document::convertLocalName(const AtomicString& name) |
| { |
| return isHTMLDocument() ? name.lower() : name; |
| } |
| |
| 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()) { |
| if (CustomElement::shouldCreateCustomElement(*this, name)) |
| return CustomElement::createCustomElementSync(*this, name, exceptionState); |
| return HTMLElementFactory::createHTMLElement(convertLocalName(name), *this, 0, CreatedByCreateElement); |
| } |
| |
| return Element::create(QualifiedName(nullAtom, name, nullAtom), this); |
| } |
| |
| Element* Document::createElement(const AtomicString& localName, const AtomicString& typeExtension, ExceptionState& exceptionState) |
| { |
| if (!isValidName(localName)) { |
| exceptionState.throwDOMException(InvalidCharacterError, "The tag name provided ('" + localName + "') is not a valid name."); |
| return nullptr; |
| } |
| |
| Element* element; |
| |
| if (CustomElement::shouldCreateCustomElement(*this, localName)) { |
| element = CustomElement::createCustomElementSync(*this, localName, exceptionState); |
| } else if (V0CustomElement::isValidName(localName) && registrationContext()) { |
| element = registrationContext()->createCustomTagElement(*this, QualifiedName(nullAtom, convertLocalName(localName), xhtmlNamespaceURI)); |
| } else { |
| element = createElement(localName, exceptionState); |
| if (exceptionState.hadException()) |
| return nullptr; |
| } |
| |
| if (!typeExtension.isEmpty()) |
| V0CustomElementRegistrationContext::setIsAttributeAndTypeExtension(element, typeExtension); |
| |
| 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(*this, qName)) |
| return CustomElement::createCustomElementSync(*this, qName, exceptionState); |
| return createElement(qName, CreatedByCreateElement); |
| } |
| |
| Element* Document::createElementNS(const AtomicString& namespaceURI, const AtomicString& qualifiedName, const AtomicString& typeExtension, ExceptionState& exceptionState) |
| { |
| QualifiedName qName(createQualifiedName(namespaceURI, qualifiedName, exceptionState)); |
| if (qName == QualifiedName::null()) |
| return nullptr; |
| |
| Element* element; |
| if (CustomElement::shouldCreateCustomElement(*this, qName)) |
| element = CustomElement::createCustomElementSync(*this, qName, exceptionState); |
| else if (V0CustomElement::isValidName(qName.localName()) && registrationContext()) |
| element = registrationContext()->createCustomTagElement(*this, qName); |
| else |
| element = createElement(qName, CreatedByCreateElement); |
| |
| if (!typeExtension.isEmpty()) |
| V0CustomElementRegistrationContext::setIsAttributeAndTypeExtension(element, typeExtension); |
| |
| 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::setImportsController(HTMLImportsController* controller) |
| { |
| DCHECK(!m_importsController || !controller); |
| m_importsController = controller; |
| if (!m_importsController && !loader()) |
| m_fetcher->clearContext(); |
| } |
| |
| 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() |
| { |
| 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 TEXT_NODE: |
| return createTextNode(importedNode->nodeValue()); |
| case CDATA_SECTION_NODE: |
| return CDATASection::create(*this, importedNode->nodeValue()); |
| case PROCESSING_INSTRUCTION_NODE: |
| return createProcessingInstruction(importedNode->nodeName(), importedNode->nodeValue(), exceptionState); |
| case COMMENT_NODE: |
| return createComment(importedNode->nodeValue()); |
| case DOCUMENT_TYPE_NODE: { |
| DocumentType* doctype = toDocumentType(importedNode); |
| return DocumentType::create(this, doctype->name(), doctype->publicId(), doctype->systemId()); |
| } |
| case ELEMENT_NODE: { |
| Element* oldElement = toElement(importedNode); |
| // FIXME: The following check might be unnecessary. Is it possible that |
| // oldElement has mismatched prefix/namespace? |
| if (!hasValidNamespaceForElements(oldElement->tagQName())) { |
| 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 ATTRIBUTE_NODE: |
| return Attr::create(*this, QualifiedName(nullAtom, AtomicString(toAttr(importedNode)->name()), nullAtom), toAttr(importedNode)->value()); |
| case DOCUMENT_FRAGMENT_NODE: { |
| if (importedNode->isShadowRoot()) { |
| // ShadowRoot nodes should not be explicitly importable. |
| // Either they are imported along with their host node, or created implicitly. |
| 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 DOCUMENT_NODE: |
| exceptionState.throwDOMException(NotSupportedError, "The node provided is a document, which may not be imported."); |
| return nullptr; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return nullptr; |
| } |
| |
| Node* Document::adoptNode(Node* source, ExceptionState& exceptionState) |
| { |
| EventQueueScope scope; |
| |
| switch (source->getNodeType()) { |
| case DOCUMENT_NODE: |
| exceptionState.throwDOMException(NotSupportedError, "The node provided is of type '" + source->nodeName() + "', which may not be adopted."); |
| return nullptr; |
| case ATTRIBUTE_NODE: { |
| 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 |
| if (!qName.prefix().isEmpty() && qName.namespaceURI().isNull()) // createElementNS(null, "html:div") |
| return false; |
| if (qName.prefix() == xmlAtom && qName.namespaceURI() != XMLNames::xmlNamespaceURI) // createElementNS("http://www.example.com", "xml:lang") |
| 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, 0, 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; |
| } |
| |
| ASSERT_NOT_REACHED(); |
| return String(); |
| } |
| |
| void Document::setReadyState(ReadyState 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 (!layoutView()) |
| return 0; |
| |
| return TreeScope::elementFromPoint(x, y); |
| } |
| |
| HeapVector<Member<Element>> Document::elementsFromPoint(int x, int y) const |
| { |
| if (!layoutView()) |
| return HeapVector<Member<Element>>(); |
| return TreeScope::elementsFromPoint(x, y); |
| } |
| |
| Range* Document::caretRangeFromPoint(int x, int y) |
| { |
| if (!layoutView()) |
| return nullptr; |
| |
| HitTestResult result = hitTestInDocument(this, x, y); |
| PositionWithAffinity positionWithAffinity = result.position(); |
| if (positionWithAffinity.position().isNull()) |
| return nullptr; |
| |
| Position rangeCompliantPosition = positionWithAffinity.position().parentAnchoredEquivalent(); |
| return Range::createAdjustedToTreeScope(*this, rangeCompliantPosition); |
| } |
| |
| Element* Document::scrollingElement() |
| { |
| if (RuntimeEnabledFeatures::scrollTopLeftInteropEnabled()) { |
| if (inQuirksMode()) { |
| updateStyleAndLayoutTree(); |
| HTMLBodyElement* body = firstBodyElement(); |
| if (body && body->layoutObject() && body->layoutObject()->hasOverflowClip()) |
| return nullptr; |
| |
| return body; |
| } |
| |
| return documentElement(); |
| } |
| |
| return body(); |
| } |
| |
| /* |
| * 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 <= 0x20 || c == 0x7F || (WTF::Unicode::category(c) & (WTF::Unicode::Separator_Line | WTF::Unicode::Separator_Paragraph))) { |
| 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: |
| // http://dvcs.w3.org/hg/webperf/raw-file/tip/specs/PageVisibility/Overview.html#dom-document-hidden |
| 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(); |
| } |
| |
| 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 DOCUMENT_NODE; |
| } |
| |
| 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() : 0; |
| } |
| |
| Page* Document::page() const |
| { |
| return m_frame ? m_frame->page() : 0; |
| } |
| |
| FrameHost* Document::frameHost() const |
| { |
| return m_frame ? m_frame->host() : 0; |
| } |
| |
| Settings* Document::settings() const |
| { |
| return m_frame ? m_frame->settings() : 0; |
| } |
| |
| 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 (layoutView()->wasNotifiedOfSubtreeChange()) |
| return true; |
| return false; |
| } |
| |
| bool Document::needsFullLayoutTreeUpdate() const |
| { |
| if (!isActive() || !view()) |
| return false; |
| if (!m_useElementsNeedingUpdate.isEmpty()) |
| return true; |
| if (!m_layerUpdateSVGFilterElements.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() |
| { |
| ScriptForbiddenScope forbidScript; |
| |
| if (!isActive()) |
| return; |
| 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)); |
| } |
| |
| EOverflow overflowX = OverflowAuto; |
| EOverflow overflowY = OverflowAuto; |
| float columnGap = 0; |
| if (overflowStyle) { |
| overflowX = overflowStyle->overflowX(); |
| overflowY = overflowStyle->overflowY(); |
| // Visible overflow on the viewport is meaningless, and the spec says to treat it as 'auto': |
| if (overflowX == OverflowVisible) |
| overflowX = OverflowAuto; |
| if (overflowY == OverflowVisible) |
| overflowY = OverflowAuto; |
| // 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 = layoutView()->mutableStyle(); |
| if (documentStyle->getWritingMode() != rootWritingMode |
| || documentStyle->direction() != rootDirection |
| || documentStyle->visitedDependentColor(CSSPropertyBackgroundColor) != backgroundColor |
| || documentStyle->backgroundLayers() != backgroundLayers |
| || documentStyle->imageRendering() != imageRendering |
| || 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->setOverflowX(overflowX); |
| newStyle->setOverflowY(overflowY); |
| newStyle->setColumnGap(columnGap); |
| newStyle->setScrollSnapType(snapType); |
| newStyle->setScrollSnapDestination(snapDestination); |
| layoutView()->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.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())); |
| TRACE_EVENT_SCOPED_SAMPLING_STATE("blink", "UpdateLayoutTree"); |
| |
| unsigned startElementCount = styleEngine().styleForElementCount(); |
| |
| InspectorInstrumentation::StyleRecalc instrumentation(this); |
| |
| DocumentAnimations::updateAnimationTimingIfNeeded(*this); |
| evaluateMediaQueryListIfNeeded(); |
| updateUseShadowTreesIfNeeded(); |
| updateDistribution(); |
| 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(); |
| layoutView()->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 |
| } |
| |
| void Document::updateStyle() |
| { |
| DCHECK(!view()->shouldThrottleRendering()); |
| TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle"); |
| 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(), layoutView()->style()); |
| if (localChange != NoChange) |
| layoutView()->setStyle(documentStyle.release()); |
| } |
| |
| clearNeedsStyleRecalc(); |
| |
| StyleResolver& resolver = ensureStyleResolver(); |
| |
| bool shouldRecordStats; |
| TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink,blink_style", &shouldRecordStats); |
| styleEngine().setStatsEnabled(shouldRecordStats); |
| |
| if (Element* documentElement = this->documentElement()) { |
| inheritHtmlAndBodyElementStyles(change); |
| dirtyElementsForLayerUpdate(); |
| if (documentElement->shouldCallRecalcStyle(change)) |
| documentElement->recalcStyle(change); |
| while (dirtyElementsForLayerUpdate()) |
| documentElement->recalcStyle(NoChange); |
| } |
| |
| view()->recalcOverflowAfterStyleChange(); |
| |
| clearChildNeedsStyleRecalc(); |
| |
| resolver.clearStyleSharingList(); |
| |
| m_wasPrinting = m_printing; |
| |
| DCHECK(!needsStyleRecalc()); |
| DCHECK(!childNeedsStyleRecalc()); |
| DCHECK(inStyleRecalc()); |
| DCHECK_EQ(styleResolver(), &resolver); |
| 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); |
| } |
| } |
| |
| void Document::notifyLayoutTreeOfSubtreeChanges() |
| { |
| if (!layoutView()->wasNotifiedOfSubtreeChange()) |
| return; |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::InLayoutSubtreeChange); |
| |
| layoutView()->handleSubtreeModifications(); |
| DCHECK(!layoutView()->wasNotifiedOfSubtreeChange()); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::LayoutSubtreeChangeClean); |
| } |
| |
| bool Document::needsLayoutTreeUpdateForNode(const Node& node) const |
| { |
| if (!node.canParticipateInFlatTree()) |
| return false; |
| if (!needsLayoutTreeUpdate()) |
| return false; |
| if (!node.inShadowIncludingDocument()) |
| 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. |
| ASSERT_NOT_REACHED(); |
| 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); |
| } |
| |
| 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::setNeedsFocusedElementCheck() |
| { |
| setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::createWithExtraData(StyleChangeReason::PseudoClass, StyleChangeExtraData::Focus)); |
| } |
| |
| void Document::clearFocusedElementSoon() |
| { |
| if (!m_clearFocusedElementTimer.isActive()) |
| m_clearFocusedElementTimer.startOneShot(0, BLINK_FROM_HERE); |
| } |
| |
| void Document::clearFocusedElementTimerFired(Timer<Document>*) |
| { |
| updateStyleAndLayoutTree(); |
| m_clearFocusedElementTimer.stop(); |
| |
| 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().resolverChanged(FullStyleUpdate); |
| } else if (m_hasNodesWithPlaceholderStyle) { |
| // If new nodes have been added or style recalc has been done with style sheets still |
| // pending, some nodes may not have had their real style calculated yet. Normally this |
| // gets cleaned when style sheets arrive 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()); |
| return ensureStyleResolver().styleForElement(element, element->parentNode() ? element->parentNode()->ensureComputedStyle() : 0); |
| } |
| |
| PassRefPtr<ComputedStyle> Document::styleForPage(int pageIndex) |
| { |
| updateDistribution(); |
| return ensureStyleResolver().styleForPage(pageIndex); |
| } |
| |
| bool Document::isPageBoxVisible(int pageIndex) |
| { |
| return styleForPage(pageIndex)->visibility() != HIDDEN; // display property doesn't apply to @page. |
| } |
| |
| void Document::pageSizeAndMarginsInPixels(int pageIndex, IntSize& pageSize, int& marginTop, int& marginRight, int& marginBottom, int& marginLeft) |
| { |
| RefPtr<ComputedStyle> style = styleForPage(pageIndex); |
| |
| int width = pageSize.width(); |
| int 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: |
| ASSERT_NOT_REACHED(); |
| } |
| pageSize = IntSize(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(); |
| } |
| |
| bool Document::dirtyElementsForLayerUpdate() |
| { |
| if (m_layerUpdateSVGFilterElements.isEmpty()) |
| return false; |
| |
| for (Element* element : m_layerUpdateSVGFilterElements) |
| element->setNeedsStyleRecalc(LocalStyleChange, StyleChangeReasonForTracing::create(StyleChangeReason::SVGFilterLayerUpdate)); |
| m_layerUpdateSVGFilterElements.clear(); |
| return true; |
| } |
| |
| void Document::scheduleSVGFilterLayerUpdateHack(Element& element) |
| { |
| if (element.getStyleChangeType() == NeedsReattachStyleChange) |
| return; |
| element.setSVGFilterNeedsLayerUpdate(); |
| m_layerUpdateSVGFilterElements.add(&element); |
| scheduleLayoutTreeUpdateIfNeeded(); |
| } |
| |
| void Document::unscheduleSVGFilterLayerUpdateHack(Element& element) |
| { |
| element.clearSVGFilterNeedsLayerUpdate(); |
| m_layerUpdateSVGFilterElements.remove(&element); |
| } |
| |
| 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::attach(const AttachContext& context) |
| { |
| 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::attach(context); |
| |
| // 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->selection().documentAttached(this); |
| m_lifecycle.advanceTo(DocumentLifecycle::StyleClean); |
| } |
| |
| void Document::detach(const AttachContext& context) |
| { |
| TRACE_EVENT0("blink", "Document::detach"); |
| 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 detach() because a detaching Document is not in a |
| // consistent state. |
| ScriptForbiddenScope forbidScript; |
| view()->dispose(); |
| m_markers->prepareForDestruction(); |
| if (LocalDOMWindow* window = this->domWindow()) |
| window->willDetachDocumentFromFrame(); |
| |
| 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); |
| |
| stopActiveDOMObjects(); |
| |
| // FIXME: consider using ActiveDOMObject. |
| 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::detach(context); |
| |
| 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); |
| |
| m_frame->selection().documentDetached(*this); |
| m_frame->inputMethodController().documentDetached(); |
| |
| // 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(); |
| setImportsController(nullptr); |
| } |
| |
| m_timers.setTimerTaskRunner( |
| Platform::current()->currentThread()->scheduler()->timerTaskRunner()->adoptClone()); |
| |
| // This is required, as our LocalFrame might delete itself as soon as it detaches |
| // us. However, this violates Node::detach() semantics, as it's never |
| // possible to re-attach. Eventually Document::detach() should be renamed, |
| // or this setting of the frame to 0 could be made explicit in each of the |
| // callers of Document::detach(). |
| m_frame = nullptr; |
| |
| if (m_mediaQueryMatcher) |
| m_mediaQueryMatcher->documentDetached(); |
| |
| m_lifecycle.advanceTo(DocumentLifecycle::Stopped); |
| |
| // FIXME: Currently we call notifyContextDestroyed() only in |
| // Document::detach(), 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(); |
| } |
| |
| 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->accessibilityEnabled()) |
| 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() : 0; |
| } |
| |
| 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 (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; |
| } |
| } |
| |
| if (m_frame->loader().provisionalDocumentLoader()) |
| 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; |
| |
| 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; |
| } |
| |
| 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(); |
| |
| HTMLLinkElement::dispatchPendingLoadEvents(); |
| HTMLStyleElement::dispatchPendingLoadEvents(); |
| } |
| |
| // 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() && layoutView() && (!layoutView()->firstChild() || layoutView()->needsLayout())) |
| view()->layout(); |
| } |
| |
| m_loadEventProgress = LoadEventCompleted; |
| |
| if (frame() && layoutView() && settings()->accessibilityEnabled()) { |
| 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->localDOMWindow()->dispatchEvent(unloadEvent, this); |
| timing.markUnloadEventEnd(); |
| } else { |
| m_frame->localDOMWindow()->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; |
| } |
| ASSERT_NOT_REACHED(); |
| 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 (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); |
| 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::logExceptionToConsole(const String& errorMessage, std::unique_ptr<SourceLocation> location) |
| { |
| ConsoleMessage* consoleMessage = ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, errorMessage, std::move(location)); |
| addConsoleMessage(consoleMessage); |
| } |
| |
| 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); |
| } |
| |
| 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() |
| { |
| // 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 (m_baseElementURL != baseElementURL && contentSecurityPolicy()->allowBaseURI(baseElementURL)) { |
| m_baseElementURL = baseElementURL; |
| updateBaseURL(); |
| } |
| |
| m_baseTarget = target ? *target : 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(); |
<