blob: 7a4260bd3cff4a1ce3edeb6c3335dfecbc7db32c [file] [log] [blame]
/*
* 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/ExceptionMessages.h"
#include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/ScriptController.h"
#include "bindings/core/v8/SourceLocation.h"
#include "bindings/core/v8/V0CustomElementConstructorBuilder.h"
#include "bindings/core/v8/V8ElementCreationOptions.h"
#include "bindings/core/v8/WindowProxy.h"
#include "bindings/core/v8/html_script_element_or_svg_script_element.h"
#include "bindings/core/v8/string_or_dictionary.h"
#include "common/net/ip_address_space.mojom-blink.h"
#include "core/animation/DocumentAnimations.h"
#include "core/animation/DocumentTimeline.h"
#include "core/animation/PendingAnimations.h"
#include "core/css/CSSFontSelector.h"
#include "core/css/CSSPropertyValueSet.h"
#include "core/css/CSSStyleDeclaration.h"
#include "core/css/CSSStyleSheet.h"
#include "core/css/CSSTiming.h"
#include "core/css/FontFaceSetDocument.h"
#include "core/css/MediaQueryMatcher.h"
#include "core/css/PropertyRegistry.h"
#include "core/css/SelectorQuery.h"
#include "core/css/StyleChangeReason.h"
#include "core/css/StyleEngine.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/properties/CSSProperty.h"
#include "core/css/resolver/FontBuilder.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/css/resolver/StyleResolverStats.h"
#include "core/dom/AXObjectCache.h"
#include "core/dom/Attr.h"
#include "core/dom/CDATASection.h"
#include "core/dom/Comment.h"
#include "core/dom/ContextFeatures.h"
#include "core/dom/DOMImplementation.h"
#include "core/dom/DocumentFragment.h"
#include "core/dom/DocumentParserTiming.h"
#include "core/dom/DocumentType.h"
#include "core/dom/Element.h"
#include "core/dom/ElementCreationOptions.h"
#include "core/dom/ElementDataCache.h"
#include "core/dom/ElementRegistrationOptions.h"
#include "core/dom/ElementShadow.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/ExceptionCode.h"
#include "core/dom/FlatTreeTraversal.h"
#include "core/dom/LayoutTreeBuilderTraversal.h"
#include "core/dom/LiveNodeList.h"
#include "core/dom/MutationObserver.h"
#include "core/dom/NodeChildRemovalTracker.h"
#include "core/dom/NodeComputedStyle.h"
#include "core/dom/NodeFilter.h"
#include "core/dom/NodeIterator.h"
#include "core/dom/NodeRareData.h"
#include "core/dom/NodeTraversal.h"
#include "core/dom/NodeWithIndex.h"
#include "core/dom/NthIndexCache.h"
#include "core/dom/ProcessingInstruction.h"
#include "core/dom/ScriptedAnimationController.h"
#include "core/dom/ShadowRoot.h"
#include "core/dom/StaticNodeList.h"
#include "core/dom/TransformSource.h"
#include "core/dom/TreeWalker.h"
#include "core/dom/VisitedLinkState.h"
#include "core/dom/WhitespaceAttacher.h"
#include "core/dom/XMLDocument.h"
#include "core/dom/events/Event.h"
#include "core/dom/events/EventListener.h"
#include "core/dom/events/ScopedEventQueue.h"
#include "core/dom/trustedtypes/TrustedHTML.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/markers/DocumentMarkerController.h"
#include "core/editing/serializers/Serialization.h"
#include "core/events/BeforeUnloadEvent.h"
#include "core/events/EventFactory.h"
#include "core/events/HashChangeEvent.h"
#include "core/events/PageTransitionEvent.h"
#include "core/events/VisualViewportResizeEvent.h"
#include "core/events/VisualViewportScrollEvent.h"
#include "core/frame/ContentSettingsClient.h"
#include "core/frame/DOMTimer.h"
#include "core/frame/DOMVisualViewport.h"
#include "core/frame/EventHandlerRegistry.h"
#include "core/frame/FrameConsole.h"
#include "core/frame/History.h"
#include "core/frame/HostsUsingFeatures.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/LocalFrameClient.h"
#include "core/frame/LocalFrameView.h"
#include "core/frame/PerformanceMonitor.h"
#include "core/frame/Settings.h"
#include "core/frame/UseCounter.h"
#include "core/frame/VisualViewport.h"
#include "core/frame/csp/ContentSecurityPolicy.h"
#include "core/html/DocumentAllNameCollection.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/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/HTMLLinkElement.h"
#include "core/html/HTMLMetaElement.h"
#include "core/html/HTMLPlugInElement.h"
#include "core/html/HTMLScriptElement.h"
#include "core/html/HTMLTemplateElement.h"
#include "core/html/HTMLTitleElement.h"
#include "core/html/PluginDocument.h"
#include "core/html/WindowNameCollection.h"
#include "core/html/canvas/CanvasContextCreationAttributes.h"
#include "core/html/canvas/CanvasFontCache.h"
#include "core/html/canvas/CanvasRenderingContext.h"
#include "core/html/canvas/HTMLCanvasElement.h"
#include "core/html/custom/CustomElement.h"
#include "core/html/custom/CustomElementDefinition.h"
#include "core/html/custom/CustomElementDescriptor.h"
#include "core/html/custom/CustomElementRegistry.h"
#include "core/html/custom/V0CustomElementMicrotaskRunQueue.h"
#include "core/html/custom/V0CustomElementRegistrationContext.h"
#include "core/html/forms/FormController.h"
#include "core/html/forms/HTMLInputElement.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/html_element_factory.h"
#include "core/html_element_type_helpers.h"
#include "core/html_names.h"
#include "core/input/EventHandler.h"
#include "core/input/TouchList.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/InspectorTraceEvents.h"
#include "core/inspector/MainThreadDebugger.h"
#include "core/intersection_observer/IntersectionObserverController.h"
#include "core/layout/AdjustForAbsoluteZoom.h"
#include "core/layout/HitTestCanvasResult.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutEmbeddedContent.h"
#include "core/layout/LayoutView.h"
#include "core/layout/TextAutosizer.h"
#include "core/loader/CookieJar.h"
#include "core/loader/DocumentLoader.h"
#include "core/loader/FrameFetchContext.h"
#include "core/loader/FrameLoader.h"
#include "core/loader/IdlenessDetector.h"
#include "core/loader/NavigationScheduler.h"
#include "core/loader/PrerendererClient.h"
#include "core/loader/TextResourceDecoderBuilder.h"
#include "core/loader/appcache/ApplicationCacheHost.h"
#include "core/origin_trials/origin_trials.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/OverscrollController.h"
#include "core/page/scrolling/RootScrollerController.h"
#include "core/page/scrolling/ScrollStateCallback.h"
#include "core/page/scrolling/ScrollingCoordinator.h"
#include "core/page/scrolling/SnapCoordinator.h"
#include "core/page/scrolling/TopDocumentRootScrollerController.h"
#include "core/paint/compositing/PaintLayerCompositor.h"
#include "core/policy/DocumentPolicy.h"
#include "core/probe/CoreProbes.h"
#include "core/resize_observer/ResizeObserverController.h"
#include "core/script/ScriptRunner.h"
#include "core/svg/SVGDocumentExtensions.h"
#include "core/svg/SVGScriptElement.h"
#include "core/svg/SVGTitleElement.h"
#include "core/svg/SVGUseElement.h"
#include "core/svg_element_factory.h"
#include "core/svg_names.h"
#include "core/timing/DOMWindowPerformance.h"
#include "core/timing/Performance.h"
#include "core/workers/SharedWorkerRepositoryClient.h"
#include "core/xml/parser/XMLDocumentParser.h"
#include "core/xml_names.h"
#include "core/xmlns_names.h"
#include "platform/CrossThreadFunctional.h"
#include "platform/DateComponents.h"
#include "platform/EventDispatchForbiddenScope.h"
#include "platform/Histogram.h"
#include "platform/InstanceCounters.h"
#include "platform/Language.h"
#include "platform/LengthFunctions.h"
#include "platform/WebFrameScheduler.h"
#include "platform/bindings/DOMDataStore.h"
#include "platform/bindings/Microtask.h"
#include "platform/bindings/ScriptForbiddenScope.h"
#include "platform/bindings/V8DOMWrapper.h"
#include "platform/bindings/V8PerIsolateData.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/loader/fetch/ResourceFetcher.h"
#include "platform/network/ContentSecurityPolicyParsers.h"
#include "platform/network/HTTPParsers.h"
#include "platform/network/NetworkStateNotifier.h"
#include "platform/plugins/PluginScriptForbiddenScope.h"
#include "platform/runtime_enabled_features.h"
#include "platform/scheduler/child/web_scheduler.h"
#include "platform/scroll/ScrollbarTheme.h"
#include "platform/text/PlatformLocale.h"
#include "platform/weborigin/OriginAccessEntry.h"
#include "platform/weborigin/SchemeRegistry.h"
#include "platform/weborigin/SecurityOrigin.h"
#include "platform/wtf/AutoReset.h"
#include "platform/wtf/DateMath.h"
#include "platform/wtf/Functional.h"
#include "platform/wtf/HashFunctions.h"
#include "platform/wtf/PtrUtil.h"
#include "platform/wtf/StdLibExtras.h"
#include "platform/wtf/Time.h"
#include "platform/wtf/text/CharacterNames.h"
#include "platform/wtf/text/StringBuffer.h"
#include "platform/wtf/text/TextEncodingRegistry.h"
#include "public/platform/InterfaceProvider.h"
#include "public/platform/Platform.h"
#include "public/platform/TaskType.h"
#include "public/platform/WebPrerenderingSupport.h"
#include "public/platform/modules/insecure_input/insecure_input_service.mojom-blink.h"
#include "public/platform/site_engagement.mojom-blink.h"
#include "services/metrics/public/cpp/mojo_ukm_recorder.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/metrics/public/interfaces/ukm_interface.mojom-shared.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/WebKit/common/page/page_visibility_state.mojom-blink.h"
#ifndef NDEBUG
using WeakDocumentSet =
blink::PersistentHeapHashSet<blink::WeakMember<blink::Document>>;
static WeakDocumentSet& liveDocumentSet();
#endif
namespace blink {
using namespace HTMLNames;
class DocumentOutliveTimeReporter : public BlinkGCObserver {
public:
explicit DocumentOutliveTimeReporter(Document* document)
: BlinkGCObserver(ThreadState::Current()), document_(document) {}
~DocumentOutliveTimeReporter() override {
// As not all documents are destroyed before the process dies, this might
// miss some long-lived documents or leaked documents.
UMA_HISTOGRAM_EXACT_LINEAR(
"Document.OutliveTimeAfterShutdown.DestroyedBeforeProcessDies",
GetOutliveTimeCount() + 1, 101);
}
void OnCompleteSweepDone() override {
enum GCCount {
kGCCount5,
kGCCount10,
kGCCountMax,
};
// There are some cases that a document can live after shutting down because
// the document can still be referenced (e.g. a document opened via
// window.open can be referenced by the opener even after shutting down). To
// avoid such cases as much as possible, outlive time count is started after
// all DomWrapper of the document have disappeared.
if (!gc_age_when_document_detached_) {
if (document_->domWindow() &&
DOMWrapperWorld::HasWrapperInAnyWorldInMainThread(
document_->domWindow())) {
return;
}
gc_age_when_document_detached_ = ThreadState::Current()->GcAge();
}
int outlive_time_count = GetOutliveTimeCount();
if (outlive_time_count == 5 || outlive_time_count == 10) {
const char* kUMAString = "Document.OutliveTimeAfterShutdown.GCCount";
if (outlive_time_count == 5)
UMA_HISTOGRAM_ENUMERATION(kUMAString, kGCCount5, kGCCountMax);
else if (outlive_time_count == 10)
UMA_HISTOGRAM_ENUMERATION(kUMAString, kGCCount10, kGCCountMax);
else
NOTREACHED();
}
if (outlive_time_count == 5 || outlive_time_count == 10 ||
outlive_time_count == 20 || outlive_time_count == 50) {
document_->RecordUkmOutliveTimeAfterShutdown(outlive_time_count);
}
}
private:
int GetOutliveTimeCount() const {
if (!gc_age_when_document_detached_)
return 0;
return ThreadState::Current()->GcAge() - gc_age_when_document_detached_;
}
WeakPersistent<Document> document_;
int gc_age_when_document_detached_ = 0;
};
static const unsigned kCMaxWriteRecursionDepth = 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 kCLayoutScheduleThreshold = 250;
// After a document has been committed for this time, it can create a history
// entry even if the user hasn't interacted with the document.
static const int kElapsedTimeForHistoryEntryWithoutUserGestureMS = 5000;
// 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 kNameStartMask =
WTF::Unicode::kLetter_Lowercase | WTF::Unicode::kLetter_Uppercase |
WTF::Unicode::kLetter_Other | WTF::Unicode::kLetter_Titlecase |
WTF::Unicode::kNumber_Letter;
if (!(WTF::Unicode::Category(c) & kNameStartMask))
return false;
// rule (c) above
if (c >= 0xF900 && c < 0xFFFE)
return false;
// rule (d) above
WTF::Unicode::CharDecompositionType decomp_type =
WTF::Unicode::DecompositionType(c);
if (decomp_type == WTF::Unicode::kDecompositionFont ||
decomp_type == WTF::Unicode::kDecompositionCompat)
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 kOtherNamePartMask =
WTF::Unicode::kMark_NonSpacing | WTF::Unicode::kMark_Enclosing |
WTF::Unicode::kMark_SpacingCombining | WTF::Unicode::kLetter_Modifier |
WTF::Unicode::kNumber_DecimalDigit;
if (!(WTF::Unicode::Category(c) & kOtherNamePartMask))
return false;
// rule (c) above
if (c >= 0xF900 && c < 0xFFFE)
return false;
// rule (d) above
WTF::Unicode::CharDecompositionType decomp_type =
WTF::Unicode::DecompositionType(c);
if (decomp_type == WTF::Unicode::kDecompositionFont ||
decomp_type == WTF::Unicode::kDecompositionCompat)
return false;
return true;
}
// Tests whether |name| is something the HTML parser would accept as a
// tag name.
template <typename CharType>
static inline bool IsValidElementNamePerHTMLParser(const CharType* characters,
unsigned length) {
CharType c = characters[0] | 0x20;
if (!('a' <= c && c < 'z'))
return false;
for (unsigned i = 1; i < length; ++i) {
c = characters[i];
if (c == '\t' || c == '\n' || c == '\f' || c == '\r' || c == ' ' ||
c == '/' || c == '>')
return false;
}
return true;
}
static bool IsValidElementNamePerHTMLParser(const String& name) {
unsigned length = name.length();
if (!length)
return false;
if (name.Is8Bit()) {
const LChar* characters = name.Characters8();
return IsValidElementNamePerHTMLParser(characters, length);
}
const UChar* characters = name.Characters16();
return IsValidElementNamePerHTMLParser(characters, length);
}
// Tests whether |name| is a valid name per DOM spec. Also checks
// whether the HTML parser would accept this element name and counts
// cases of mismatches.
static bool IsValidElementName(Document* document, const String& name) {
bool is_valid_dom_name = Document::IsValidName(name);
bool is_valid_html_name = IsValidElementNamePerHTMLParser(name);
if (UNLIKELY(is_valid_html_name != is_valid_dom_name)) {
// This is inaccurate because it will not report activity in
// detached documents. However retrieving the frame from the
// bindings is too slow.
UseCounter::Count(document,
is_valid_dom_name
? WebFeature::kElementNameDOMValidHTMLParserInvalid
: WebFeature::kElementNameDOMInvalidHTMLParserValid);
}
return is_valid_dom_name;
}
static bool AcceptsEditingFocus(const Element& element) {
DCHECK(HasEditableStyle(element));
return element.GetDocument().GetFrame() && RootEditableElement(element);
}
uint64_t Document::global_tree_version_ = 0;
static bool g_threaded_parsing_enabled_for_testing = true;
// This doesn't work with non-Document ExecutionContext.
static void RunAutofocusTask(ExecutionContext* context) {
// Document lifecycle check is done in Element::focus()
if (!context)
return;
Document* document = ToDocument(context);
if (Element* element = document->AutofocusElement()) {
document->SetAutofocusElement(nullptr);
element->focus();
}
}
static void RecordLoadReasonToHistogram(WouldLoadReason reason) {
// TODO(dcheng): Make EnumerationHistogram work with scoped enums.
DEFINE_STATIC_LOCAL(EnumerationHistogram, unseen_frame_histogram,
("Navigation.DeferredDocumentLoading.StatesV4",
static_cast<int>(WouldLoadReason::kCount)));
unseen_frame_histogram.Count(static_cast<int>(reason));
}
class Document::NetworkStateObserver final
: public GarbageCollectedFinalized<Document::NetworkStateObserver>,
public NetworkStateNotifier::NetworkStateObserver,
public ContextLifecycleObserver {
USING_GARBAGE_COLLECTED_MIXIN(Document::NetworkStateObserver);
public:
explicit NetworkStateObserver(Document& document)
: ContextLifecycleObserver(&document) {
online_observer_handle_ = GetNetworkStateNotifier().AddOnLineObserver(
this, GetExecutionContext()->GetTaskRunner(TaskType::kNetworking));
}
void OnLineStateChange(bool on_line) override {
AtomicString event_name =
on_line ? EventTypeNames::online : EventTypeNames::offline;
Document* document = ToDocument(GetExecutionContext());
if (!document->domWindow())
return;
document->domWindow()->DispatchEvent(Event::Create(event_name));
probe::networkStateChanged(document->GetFrame(), on_line);
}
void ContextDestroyed(ExecutionContext* context) override {
UnregisterAsObserver(context);
}
void UnregisterAsObserver(ExecutionContext* context) {
DCHECK(context);
online_observer_handle_ = nullptr;
}
virtual void Trace(blink::Visitor* visitor) {
ContextLifecycleObserver::Trace(visitor);
}
private:
std::unique_ptr<NetworkStateNotifier::NetworkStateObserverHandle>
online_observer_handle_;
};
Document* Document::Create(Document& document) {
Document* new_document = new Document(
DocumentInit::Create().WithContextDocument(&document).WithURL(
BlankURL()));
new_document->SetSecurityOrigin(document.GetMutableSecurityOrigin());
new_document->SetContextFeatures(document.GetContextFeatures());
return new_document;
}
Document::Document(const DocumentInit& initializer,
DocumentClassFlags document_classes)
: ContainerNode(nullptr, kCreateDocument),
TreeScope(*this),
has_nodes_with_placeholder_style_(false),
evaluate_media_queries_on_style_recalc_(false),
pending_sheet_layout_(kNoLayoutWithPendingSheets),
frame_(initializer.GetFrame()),
// TODO(dcheng): Why does this need both a LocalFrame and LocalDOMWindow
// pointer?
dom_window_(frame_ ? frame_->DomWindow() : nullptr),
imports_controller_(initializer.ImportsController()),
context_document_(initializer.ContextDocument()),
context_features_(ContextFeatures::DefaultSwitch()),
well_formed_(false),
printing_(kNotPrinting),
paginated_for_screen_(false),
compatibility_mode_(kNoQuirksMode),
compatibility_mode_locked_(false),
has_autofocused_(false),
clear_focused_element_timer_(GetTaskRunner(TaskType::kUnspecedTimer),
this,
&Document::ClearFocusedElementTimerFired),
dom_tree_version_(++global_tree_version_),
style_version_(0),
listener_types_(0),
mutation_observer_types_(0),
visited_link_state_(VisitedLinkState::Create(*this)),
visually_ordered_(false),
ready_state_(kComplete),
parsing_state_(kFinishedParsing),
goto_anchor_needed_after_stylesheets_load_(false),
contains_validity_style_rules_(false),
contains_plugins_(false),
ignore_destructive_write_count_(0),
throw_on_dynamic_markup_insertion_count_(0),
markers_(new DocumentMarkerController(*this)),
update_focus_appearance_timer_(
GetTaskRunner(TaskType::kUnspecedTimer),
this,
&Document::UpdateFocusAppearanceTimerFired),
css_target_(nullptr),
load_event_progress_(kLoadEventCompleted),
start_time_(CurrentTime()),
script_runner_(ScriptRunner::Create(this)),
xml_version_("1.0"),
xml_standalone_(kStandaloneUnspecified),
has_xml_declaration_(0),
design_mode_(false),
is_running_exec_command_(false),
has_annotated_regions_(false),
annotated_regions_dirty_(false),
document_classes_(document_classes),
is_view_source_(false),
saw_elements_in_known_namespaces_(false),
is_srcdoc_document_(initializer.ShouldTreatURLAsSrcdocDocument()),
is_mobile_document_(false),
layout_view_(nullptr),
has_fullscreen_supplement_(false),
load_event_delay_count_(0),
load_event_delay_timer_(GetTaskRunner(TaskType::kNetworking),
this,
&Document::LoadEventDelayTimerFired),
plugin_loading_timer_(GetTaskRunner(TaskType::kUnspecedLoading),
this,
&Document::PluginLoadingTimerFired),
document_timing_(*this),
write_recursion_is_too_deep_(false),
write_recursion_depth_(0),
registration_context_(initializer.RegistrationContext(this)),
element_data_cache_clear_timer_(
GetTaskRunner(TaskType::kUnspecedTimer),
this,
&Document::ElementDataCacheClearTimerFired),
timeline_(DocumentTimeline::Create(this)),
pending_animations_(new PendingAnimations(*this)),
worklet_animation_controller_(new WorkletAnimationController(this)),
template_document_host_(nullptr),
did_associate_form_controls_timer_(
GetTaskRunner(TaskType::kUnspecedLoading),
this,
&Document::DidAssociateFormControlsTimerFired),
timers_(GetTaskRunner(TaskType::kJavascriptTimer)),
has_viewport_units_(false),
parser_sync_policy_(kAllowAsynchronousParsing),
node_count_(0),
would_load_reason_(WouldLoadReason::kInvalid),
password_count_(0),
logged_field_edit_(false),
engagement_level_(mojom::blink::EngagementLevel::NONE),
secure_context_state_(SecureContextState::kUnknown),
ukm_source_id_(ukm::UkmRecorder::GetNewSourceID()),
needs_to_record_ukm_outlive_time_(false) {
if (frame_) {
DCHECK(frame_->GetPage());
ProvideContextFeaturesToDocumentFrom(*this, *frame_->GetPage());
fetcher_ = frame_->Loader().GetDocumentLoader()->Fetcher();
FrameFetchContext::ProvideDocumentToContext(fetcher_->Context(), this);
// TODO(dcheng): Why does this need to check that DOMWindow is non-null?
CustomElementRegistry* registry =
frame_->DomWindow() ? frame_->DomWindow()->MaybeCustomElements()
: nullptr;
if (registry && registration_context_)
registry->Entangle(registration_context_);
} else if (imports_controller_) {
fetcher_ = FrameFetchContext::CreateFetcherFromDocument(this);
} else {
fetcher_ = ResourceFetcher::Create(nullptr);
}
DCHECK(fetcher_);
root_scroller_controller_ = RootScrollerController::Create(*this);
// We depend on the url getting immediately set in subframes, but we
// also depend on the url NOT getting immediately set in opened windows.
// See fast/dom/early-frame-url.html
// and fast/dom/location-new-window-no-crash.html, respectively.
// FIXME: Can/should we unify this behavior?
if (initializer.ShouldSetURL()) {
SetURL(initializer.Url());
} else {
// Even if this document has no URL, we need to initialize base URL with
// fallback base URL.
UpdateBaseURL();
}
InitSecurityContext(initializer);
if (frame_)
frame_->Client()->DidSetFramePolicyHeaders(GetSandboxFlags(), {});
InitDNSPrefetch();
InstanceCounters::IncrementCounter(InstanceCounters::kDocumentCounter);
lifecycle_.AdvanceTo(DocumentLifecycle::kInactive);
// Since CSSFontSelector requires Document::m_fetcher and StyleEngine owns
// CSSFontSelector, need to initialize m_styleEngine after initializing
// m_fetcher.
style_engine_ = 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()->IsContextPaused());
#ifndef NDEBUG
liveDocumentSet().insert(this);
#endif
}
Document::~Document() {
DCHECK(!GetLayoutView());
DCHECK(!ParentTreeScope());
// If a top document with a cache, verify that it was comprehensively
// cleared during detach.
DCHECK(!ax_object_cache_);
InstanceCounters::DecrementCounter(InstanceCounters::kDocumentCounter);
}
Range* Document::CreateRangeAdjustedToTreeScope(const TreeScope& tree_scope,
const Position& position) {
DCHECK(position.IsNotNull());
// Note: Since |Position::ComputeContainerNode()| returns |nullptr| if
// |position| is |BeforeAnchor| or |AfterAnchor|.
Node* const anchor_node = position.AnchorNode();
if (anchor_node->GetTreeScope() == tree_scope)
return Range::Create(tree_scope.GetDocument(), position, position);
Node* const shadow_host = tree_scope.AncestorInThisScope(anchor_node);
return Range::Create(tree_scope.GetDocument(),
Position::BeforeNode(*shadow_host),
Position::BeforeNode(*shadow_host));
}
SelectorQueryCache& Document::GetSelectorQueryCache() {
if (!selector_query_cache_)
selector_query_cache_ = std::make_unique<SelectorQueryCache>();
return *selector_query_cache_;
}
MediaQueryMatcher& Document::GetMediaQueryMatcher() {
if (!media_query_matcher_)
media_query_matcher_ = MediaQueryMatcher::Create(*this);
return *media_query_matcher_;
}
void Document::MediaQueryAffectingValueChanged() {
GetStyleEngine().MediaQueryAffectingValueChanged();
if (NeedsLayoutTreeUpdate())
evaluate_media_queries_on_style_recalc_ = true;
else
EvaluateMediaQueryList();
probe::mediaQueryResultChanged(this);
}
void Document::SetCompatibilityMode(CompatibilityMode mode) {
if (compatibility_mode_locked_ || mode == compatibility_mode_)
return;
if (compatibility_mode_ == kQuirksMode)
UseCounter::Count(*this, WebFeature::kQuirksModeDocument);
else if (compatibility_mode_ == kLimitedQuirksMode)
UseCounter::Count(*this, WebFeature::kLimitedQuirksModeDocument);
compatibility_mode_ = mode;
GetSelectorQueryCache().Invalidate();
}
String Document::compatMode() const {
return InQuirksMode() ? "BackCompat" : "CSS1Compat";
}
void Document::SetDoctype(DocumentType* doc_type) {
// This should never be called more than once.
DCHECK(!doc_type_ || !doc_type);
doc_type_ = doc_type;
if (doc_type_) {
AdoptIfNeeded(*doc_type_);
if (doc_type_->publicId().StartsWithIgnoringASCIICase(
"-//wapforum//dtd xhtml mobile 1.")) {
is_mobile_document_ = true;
style_engine_->ViewportRulesChanged();
}
}
}
DOMImplementation& Document::implementation() {
if (!implementation_)
implementation_ = DOMImplementation::Create(*this);
return *implementation_;
}
bool Document::HasAppCacheManifest() const {
return IsHTMLHtmlElement(documentElement()) &&
documentElement()->hasAttribute(manifestAttr);
}
Location* Document::location() const {
if (!GetFrame())
return nullptr;
return domWindow()->location();
}
void Document::ChildrenChanged(const ChildrenChange& change) {
ContainerNode::ChildrenChanged(change);
document_element_ = 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 (document_element_ && !IsHTMLDocument())
BeginLifecycleUpdatesIfRenderingReady();
}
void Document::setRootScroller(Element* new_scroller, ExceptionState&) {
root_scroller_controller_->Set(new_scroller);
}
Element* Document::rootScroller() const {
return root_scroller_controller_->Get();
}
bool Document::IsInMainFrame() const {
return GetFrame() && GetFrame()->IsMainFrame();
}
AtomicString Document::ConvertLocalName(const AtomicString& name) {
return IsHTMLDocument() ? name.LowerASCII() : name;
}
// https://dom.spec.whatwg.org/#dom-document-createelement
Element* Document::createElement(const AtomicString& name,
ExceptionState& exception_state) {
if (!IsValidElementName(this, name)) {
exception_state.ThrowDOMException(
kInvalidCharacterError,
"The tag name provided ('" + name + "') is not a valid name.");
return nullptr;
}
if (IsXHTMLDocument() || IsHTMLDocument()) {
// 2. If the context object is an HTML document, let localName be
// converted to ASCII lowercase.
AtomicString local_name = ConvertLocalName(name);
if (CustomElement::ShouldCreateCustomElement(local_name)) {
return CustomElement::CreateCustomElementSync(
*this,
QualifiedName(g_null_atom, local_name, HTMLNames::xhtmlNamespaceURI));
}
return HTMLElementFactory::createHTMLElement(local_name, *this,
kCreatedByCreateElement);
}
return Element::Create(QualifiedName(g_null_atom, name, g_null_atom), this);
}
String GetTypeExtension(Document* document,
const StringOrDictionary& string_or_options,
ExceptionState& exception_state) {
if (string_or_options.IsNull())
return g_empty_string;
if (string_or_options.IsString()) {
UseCounter::Count(document,
WebFeature::kDocumentCreateElement2ndArgStringHandling);
return string_or_options.GetAsString();
}
if (string_or_options.IsDictionary()) {
Dictionary dict = string_or_options.GetAsDictionary();
ElementCreationOptions impl;
V8ElementCreationOptions::ToImpl(dict.GetIsolate(), dict.V8Value(), impl,
exception_state);
if (exception_state.HadException())
return g_empty_string;
if (impl.hasIs())
return impl.is();
}
return g_empty_string;
}
// https://dom.spec.whatwg.org/#dom-document-createelement
Element* Document::createElement(const AtomicString& local_name,
const StringOrDictionary& string_or_options,
ExceptionState& exception_state) {
// 1. If localName does not match Name production, throw InvalidCharacterError
if (!IsValidElementName(this, local_name)) {
exception_state.ThrowDOMException(
kInvalidCharacterError,
"The tag name provided ('" + local_name + "') is not a valid name.");
return nullptr;
}
// 2. localName converted to ASCII lowercase
const AtomicString& converted_local_name = ConvertLocalName(local_name);
bool is_v1 = string_or_options.IsDictionary() || !RegistrationContext();
bool create_v1_builtin =
string_or_options.IsDictionary() &&
RuntimeEnabledFeatures::CustomElementsBuiltinEnabled();
bool should_create_builtin =
create_v1_builtin || string_or_options.IsString();
// 3.
const AtomicString& is =
AtomicString(GetTypeExtension(this, string_or_options, exception_state));
const AtomicString& name = should_create_builtin ? is : converted_local_name;
// 4. Let definition be result of lookup up custom element definition
CustomElementDefinition* definition = nullptr;
if (is_v1) {
// Is the runtime flag enabled for customized builtin elements?
const CustomElementDescriptor desc =
RuntimeEnabledFeatures::CustomElementsBuiltinEnabled()
? CustomElementDescriptor(name, converted_local_name)
: CustomElementDescriptor(converted_local_name,
converted_local_name);
if (CustomElementRegistry* registry = CustomElement::Registry(*this))
definition = registry->DefinitionFor(desc);
// 5. If 'is' is non-null and definition is null, throw NotFoundError
// TODO(yurak): update when https://github.com/w3c/webcomponents/issues/608
// is resolved
if (!definition && create_v1_builtin) {
exception_state.ThrowDOMException(kNotFoundError,
"Custom element definition not found.");
return nullptr;
}
}
// 7. Let element be the result of creating an element
Element* element;
if (definition) {
element = CustomElement::CreateCustomElementSync(
*this, converted_local_name, definition);
} else if (V0CustomElement::IsValidName(local_name) &&
RegistrationContext()) {
element = RegistrationContext()->CreateCustomTagElement(
*this,
QualifiedName(g_null_atom, converted_local_name, xhtmlNamespaceURI));
} else {
element = createElement(local_name, exception_state);
if (exception_state.HadException())
return nullptr;
}
// 8. If 'is' is non-null, set 'is' attribute
if (!is.IsEmpty()) {
if (string_or_options.IsString()) {
V0CustomElementRegistrationContext::SetIsAttributeAndTypeExtension(
element, is);
} else if (string_or_options.IsDictionary()) {
element->setAttribute(HTMLNames::isAttr, is);
}
}
return element;
}
static inline QualifiedName CreateQualifiedName(
const AtomicString& namespace_uri,
const AtomicString& qualified_name,
ExceptionState& exception_state) {
AtomicString prefix, local_name;
if (!Document::ParseQualifiedName(qualified_name, prefix, local_name,
exception_state))
return QualifiedName::Null();
QualifiedName q_name(prefix, local_name, namespace_uri);
if (!Document::HasValidNamespaceForElements(q_name)) {
exception_state.ThrowDOMException(
kNamespaceError,
"The namespace URI provided ('" + namespace_uri +
"') is not valid for the qualified name provided ('" +
qualified_name + "').");
return QualifiedName::Null();
}
return q_name;
}
Element* Document::createElementNS(const AtomicString& namespace_uri,
const AtomicString& qualified_name,
ExceptionState& exception_state) {
QualifiedName q_name(
CreateQualifiedName(namespace_uri, qualified_name, exception_state));
if (q_name == QualifiedName::Null())
return nullptr;
if (CustomElement::ShouldCreateCustomElement(q_name))
return CustomElement::CreateCustomElementSync(*this, q_name);
return createElement(q_name, kCreatedByCreateElement);
}
// https://dom.spec.whatwg.org/#internal-createelementns-steps
Element* Document::createElementNS(const AtomicString& namespace_uri,
const AtomicString& qualified_name,
const StringOrDictionary& string_or_options,
ExceptionState& exception_state) {
// 1. Validate and extract
QualifiedName q_name(
CreateQualifiedName(namespace_uri, qualified_name, exception_state));
if (q_name == QualifiedName::Null())
return nullptr;
bool is_v1 = string_or_options.IsDictionary() || !RegistrationContext();
bool create_v1_builtin =
string_or_options.IsDictionary() &&
RuntimeEnabledFeatures::CustomElementsBuiltinEnabled();
bool should_create_builtin =
create_v1_builtin || string_or_options.IsString();
// 2.
const AtomicString& is =
AtomicString(GetTypeExtension(this, string_or_options, exception_state));
const AtomicString& name = should_create_builtin ? is : qualified_name;
if (!IsValidElementName(this, qualified_name)) {
exception_state.ThrowDOMException(
kInvalidCharacterError, "The tag name provided ('" + qualified_name +
"') is not a valid name.");
return nullptr;
}
// 3. Let definition be result of lookup up custom element definition
CustomElementDefinition* definition = nullptr;
if (is_v1) {
const CustomElementDescriptor desc =
RuntimeEnabledFeatures::CustomElementsBuiltinEnabled()
? CustomElementDescriptor(name, qualified_name)
: CustomElementDescriptor(qualified_name, qualified_name);
if (CustomElementRegistry* registry = CustomElement::Registry(*this))
definition = registry->DefinitionFor(desc);
// 4. If 'is' is non-null and definition is null, throw NotFoundError
if (!definition && create_v1_builtin) {
exception_state.ThrowDOMException(kNotFoundError,
"Custom element definition not found.");
return nullptr;
}
}
// 5. Let element be the result of creating an element
Element* element;
if (CustomElement::ShouldCreateCustomElement(q_name) || create_v1_builtin) {
element = CustomElement::CreateCustomElementSync(*this, q_name, definition);
} else if (V0CustomElement::IsValidName(q_name.LocalName()) &&
RegistrationContext()) {
element = RegistrationContext()->CreateCustomTagElement(*this, q_name);
} else {
element = createElement(q_name, kCreatedByCreateElement);
}
// 6. If 'is' is non-null, set 'is' attribute
if (!is.IsEmpty()) {
if (element->GetCustomElementState() != CustomElementState::kCustom) {
V0CustomElementRegistrationContext::SetIsAttributeAndTypeExtension(
element, is);
} else if (string_or_options.IsDictionary()) {
element->setAttribute(HTMLNames::isAttr, is);
}
}
return element;
}
ScriptValue Document::registerElement(ScriptState* script_state,
const AtomicString& name,
const ElementRegistrationOptions& options,
ExceptionState& exception_state,
V0CustomElement::NameSet valid_names) {
HostsUsingFeatures::CountMainWorldOnly(
script_state, *this,
HostsUsingFeatures::Feature::kDocumentRegisterElement);
if (!RegistrationContext()) {
exception_state.ThrowDOMException(
kNotSupportedError, "No element registration context is available.");
return ScriptValue();
}
V0CustomElementConstructorBuilder constructor_builder(script_state, options);
RegistrationContext()->RegisterElement(this, &constructor_builder, name,
valid_names, exception_state);
return constructor_builder.BindingsReturnValue();
}
V0CustomElementMicrotaskRunQueue* Document::CustomElementMicrotaskRunQueue() {
if (!custom_element_microtask_run_queue_)
custom_element_microtask_run_queue_ =
V0CustomElementMicrotaskRunQueue::Create();
return custom_element_microtask_run_queue_.Get();
}
void Document::ClearImportsController() {
if (!Loader())
fetcher_->ClearContext();
imports_controller_ = nullptr;
}
HTMLImportsController* Document::EnsureImportsController() {
if (!imports_controller_) {
DCHECK(frame_);
imports_controller_ = HTMLImportsController::Create(*this);
}
return imports_controller_;
}
HTMLImportLoader* Document::ImportLoader() const {
if (!imports_controller_)
return nullptr;
return imports_controller_->LoaderFor(*this);
}
bool Document::IsHTMLImport() const {
return imports_controller_ && imports_controller_->Master() != this;
}
Document& Document::MasterDocument() const {
if (!imports_controller_)
return *const_cast<Document*>(this);
Document* master = imports_controller_->Master();
DCHECK(master);
return *master;
}
bool Document::HaveImportsLoaded() const {
if (!imports_controller_)
return true;
return !imports_controller_->ShouldBlockScriptExecution(*this);
}
LocalDOMWindow* Document::ExecutingWindow() const {
if (LocalDOMWindow* owning_window = domWindow())
return owning_window;
if (HTMLImportsController* import = ImportsController())
return import->Master()->domWindow();
return nullptr;
}
LocalFrame* Document::ExecutingFrame() {
LocalDOMWindow* window = ExecutingWindow();
if (!window)
return nullptr;
return window->GetFrame();
}
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& exception_state) {
if (IsHTMLDocument()) {
exception_state.ThrowDOMException(
kNotSupportedError,
"This operation is not supported for HTML documents.");
return nullptr;
}
if (data.Contains("]]>")) {
exception_state.ThrowDOMException(kInvalidCharacterError,
"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& exception_state) {
if (!IsValidName(target)) {
exception_state.ThrowDOMException(
kInvalidCharacterError,
"The target provided ('" + target + "') is not a valid name.");
return nullptr;
}
if (data.Contains("?>")) {
exception_state.ThrowDOMException(
kInvalidCharacterError,
"The data provided ('" + data + "') contains '?>'.");
return nullptr;
}
if (IsHTMLDocument()) {
UseCounter::Count(*this,
WebFeature::kHTMLDocumentCreateProcessingInstruction);
}
return ProcessingInstruction::Create(*this, target, data);
}
Text* Document::CreateEditingTextNode(const String& text) {
return Text::CreateEditingText(*this, text);
}
bool Document::ImportContainerNodeChildren(ContainerNode* old_container_node,
ContainerNode* new_container_node,
ExceptionState& exception_state) {
for (Node& old_child : NodeTraversal::ChildrenOf(*old_container_node)) {
Node* new_child = importNode(&old_child, true, exception_state);
if (exception_state.HadException())
return false;
new_container_node->AppendChild(new_child, exception_state);
if (exception_state.HadException())
return false;
}
return true;
}
Node* Document::importNode(Node* imported_node,
bool deep,
ExceptionState& exception_state) {
switch (imported_node->getNodeType()) {
case kTextNode:
return createTextNode(imported_node->nodeValue());
case kCdataSectionNode:
return CDATASection::Create(*this, imported_node->nodeValue());
case kProcessingInstructionNode:
return createProcessingInstruction(imported_node->nodeName(),
imported_node->nodeValue(),
exception_state);
case kCommentNode:
return createComment(imported_node->nodeValue());
case kDocumentTypeNode: {
DocumentType* doctype = ToDocumentType(imported_node);
return DocumentType::Create(this, doctype->name(), doctype->publicId(),
doctype->systemId());
}
case kElementNode: {
Element* old_element = ToElement(imported_node);
// FIXME: The following check might be unnecessary. Is it possible that
// oldElement has mismatched prefix/namespace?
if (!HasValidNamespaceForElements(old_element->TagQName())) {
exception_state.ThrowDOMException(
kNamespaceError, "The imported node has an invalid namespace.");
return nullptr;
}
Element* new_element =
createElement(old_element->TagQName(), kCreatedByImportNode);
new_element->CloneDataFromElement(*old_element);
if (deep) {
if (!ImportContainerNodeChildren(old_element, new_element,
exception_state))
return nullptr;
if (IsHTMLTemplateElement(*old_element) &&
!EnsureTemplateDocument().ImportContainerNodeChildren(
ToHTMLTemplateElement(old_element)->content(),
ToHTMLTemplateElement(new_element)->content(), exception_state))
return nullptr;
}
return new_element;
}
case kAttributeNode:
return Attr::Create(
*this,
QualifiedName(g_null_atom,
AtomicString(ToAttr(imported_node)->name()),
g_null_atom),
ToAttr(imported_node)->value());
case kDocumentFragmentNode: {
if (imported_node->IsShadowRoot()) {
// ShadowRoot nodes should not be explicitly importable.
// Either they are imported along with their host node, or created
// implicitly.
exception_state.ThrowDOMException(
kNotSupportedError,
"The node provided is a shadow root, which may not be imported.");
return nullptr;
}
DocumentFragment* old_fragment = ToDocumentFragment(imported_node);
DocumentFragment* new_fragment = createDocumentFragment();
if (deep && !ImportContainerNodeChildren(old_fragment, new_fragment,
exception_state))
return nullptr;
return new_fragment;
}
case kDocumentNode:
exception_state.ThrowDOMException(
kNotSupportedError,
"The node provided is a document, which may not be imported.");
return nullptr;
}
NOTREACHED();
return nullptr;
}
Node* Document::adoptNode(Node* source, ExceptionState& exception_state) {
EventQueueScope scope;
switch (source->getNodeType()) {
case kDocumentNode:
exception_state.ThrowDOMException(kNotSupportedError,
"The node provided is of type '" +
source->nodeName() +
"', which may not be adopted.");
return nullptr;
case kAttributeNode: {
Attr* attr = ToAttr(source);
if (Element* owner_element = attr->ownerElement())
owner_element->removeAttributeNode(attr, exception_state);
break;
}
default:
if (source->IsShadowRoot()) {
// ShadowRoot cannot disconnect itself from the host node.
exception_state.ThrowDOMException(
kHierarchyRequestError,
"The node provided is a shadow root, which may not be adopted.");
return nullptr;
}
if (source->IsFrameOwnerElement()) {
HTMLFrameOwnerElement* frame_owner_element =
ToHTMLFrameOwnerElement(source);
if (GetFrame() && GetFrame()->Tree().IsDescendantOf(
frame_owner_element->ContentFrame())) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"The node provided is a frame which contains this document.");
return nullptr;
}
}
if (source->parentNode()) {
source->parentNode()->RemoveChild(source, exception_state);
if (exception_state.HadException())
return nullptr;
// The above removeChild() can execute arbitrary JavaScript code.
if (source->parentNode()) {
AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kWarningMessageLevel,
ExceptionMessages::FailedToExecute("adoptNode", "Document",
"Unable to remove the "
"specified node from the "
"original parent.")));
return nullptr;
}
}
}
AdoptIfNeeded(*source);
return source;
}
bool Document::HasValidNamespaceForElements(const QualifiedName& q_name) {
// These checks are from DOM Core Level 2, createElementNS
// http://www.w3.org/TR/DOM-Level-2-Core/core.html#ID-DocCrElNS
// createElementNS(null, "html:div")
if (!q_name.Prefix().IsEmpty() && q_name.NamespaceURI().IsNull())
return false;
// createElementNS("http://www.example.com", "xml:lang")
if (q_name.Prefix() == g_xml_atom &&
q_name.NamespaceURI() != XMLNames::xmlNamespaceURI)
return false;
// Required by DOM Level 3 Core and unspecified by DOM Level 2 Core:
// http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS
// createElementNS("http://www.w3.org/2000/xmlns/", "foo:bar"),
// createElementNS(null, "xmlns:bar"), createElementNS(null, "xmlns")
if (q_name.Prefix() == g_xmlns_atom ||
(q_name.Prefix().IsEmpty() && q_name.LocalName() == g_xmlns_atom))
return q_name.NamespaceURI() == XMLNSNames::xmlnsNamespaceURI;
return q_name.NamespaceURI() != XMLNSNames::xmlnsNamespaceURI;
}
bool Document::HasValidNamespaceForAttributes(const QualifiedName& q_name) {
return HasValidNamespaceForElements(q_name);
}
// FIXME: This should really be in a possible ElementFactory class
Element* Document::createElement(const QualifiedName& q_name,
CreateElementFlags flags) {
Element* e = nullptr;
// FIXME: Use registered namespaces and look up in a hash to find the right
// factory.
if (q_name.NamespaceURI() == xhtmlNamespaceURI)
e = HTMLElementFactory::createHTMLElement(q_name.LocalName(), *this, flags);
else if (q_name.NamespaceURI() == SVGNames::svgNamespaceURI)
e = SVGElementFactory::createSVGElement(q_name.LocalName(), *this, flags);
if (e)
saw_elements_in_known_namespaces_ = true;
else
e = Element::Create(q_name, this);
if (e->prefix() != q_name.Prefix())
e->SetTagNameForCreateElementNS(q_name);
DCHECK(q_name == 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 (ready_state_) {
case kLoading:
return loading;
case kInteractive:
return interactive;
case kComplete:
return complete;
}
NOTREACHED();
return String();
}
void Document::SetReadyState(DocumentReadyState ready_state) {
if (ready_state == ready_state_)
return;
switch (ready_state) {
case kLoading:
if (!document_timing_.DomLoading()) {
document_timing_.MarkDomLoading();
}
break;
case kInteractive:
if (!document_timing_.DomInteractive())
document_timing_.MarkDomInteractive();
break;
case kComplete:
if (!document_timing_.DomComplete())
document_timing_.MarkDomComplete();
break;
}
ready_state_ = ready_state;
DispatchEvent(Event::Create(EventTypeNames::readystatechange));
}
bool Document::IsLoadCompleted() const {
return ready_state_ == kComplete;
}
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().GetName());
}
void Document::SetContentLanguage(const AtomicString& language) {
if (content_language_ == language)
return;
content_language_ = language;
// Document's style depends on the content language.
SetNeedsStyleRecalc(kSubtreeStyleChange, StyleChangeReasonForTracing::Create(
StyleChangeReason::kLanguage));
}
void Document::setXMLVersion(const String& version,
ExceptionState& exception_state) {
if (!XMLDocumentParser::SupportsXMLVersion(version)) {
exception_state.ThrowDOMException(
kNotSupportedError,
"This document does not support the XML version '" + version + "'.");
return;
}
xml_version_ = version;
}
void Document::setXMLStandalone(bool standalone,
ExceptionState& exception_state) {
xml_standalone_ = standalone ? kStandalone : kNotStandalone;
}
void Document::SetContent(const String& content) {
open();
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* document_loader = Loader())
return document_loader->MimeType();
return String();
}
void Document::SetMimeType(const AtomicString& mime_type) {
mime_type_ = mime_type;
}
AtomicString Document::contentType() const {
if (!mime_type_.IsEmpty())
return mime_type_;
if (DocumentLoader* document_loader = Loader())
return document_loader->MimeType();
String mime_type = SuggestedMIMEType();
if (!mime_type.IsEmpty())
return AtomicString(mime_type);
return AtomicString("application/xml");
}
Element* Document::ElementFromPoint(double x, double y) const {
if (!GetLayoutView())
return nullptr;
return TreeScope::ElementFromPoint(x, y);
}
HeapVector<Member<Element>> Document::ElementsFromPoint(double x,
double y) const {
if (!GetLayoutView())
return HeapVector<Member<Element>>();
return TreeScope::ElementsFromPoint(x, y);
}
Range* Document::caretRangeFromPoint(int x, int y) {
if (!GetLayoutView())
return nullptr;
HitTestResult result = HitTestInDocument(this, x, y);
PositionWithAffinity position_with_affinity = result.GetPosition();
if (position_with_affinity.IsNull())
return nullptr;
Position range_compliant_position =
position_with_affinity.GetPosition().ParentAnchoredEquivalent();
return CreateRangeAdjustedToTreeScope(*this, range_compliant_position);
}
Element* Document::scrollingElement() {
if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled() && InQuirksMode())
UpdateStyleAndLayoutTree();
return ScrollingElementNoLayout();
}
Element* Document::ScrollingElementNoLayout() {
if (RuntimeEnabledFeatures::ScrollTopLeftInteropEnabled()) {
if (InQuirksMode()) {
DCHECK(lifecycle_.GetState() >= DocumentLifecycle::kStyleClean);
HTMLBodyElement* body = FirstBodyElement();
if (body && body->GetLayoutObject() &&
body->GetLayoutObject()->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 builder_index = 0;
const CharacterType* characters = title.GetCharacters<CharacterType>();
StringBuffer<CharacterType> buffer(length);
// Replace control characters with spaces and collapse whitespace.
bool pending_whitespace = false;
for (unsigned i = 0; i < length; ++i) {
UChar32 c = characters[i];
if ((c <= WTF::Unicode::kSpaceCharacter &&
c != WTF::Unicode::kLineTabulationCharacter) ||
c == WTF::Unicode::kDeleteCharacter) {
if (builder_index != 0)
pending_whitespace = true;
} else {
if (pending_whitespace) {
buffer[builder_index++] = ' ';
pending_whitespace = false;
}
buffer[builder_index++] = c;
}
}
buffer.Shrink(builder_index);
return String::Adopt(buffer);
}
void Document::UpdateTitle(const String& title) {
if (raw_title_ == title)
return;
raw_title_ = title;
String old_title = title_;
if (raw_title_.IsEmpty())
title_ = String();
else if (raw_title_.Is8Bit())
title_ = CanonicalizedTitle<LChar>(this, raw_title_);
else
title_ = CanonicalizedTitle<UChar>(this, raw_title_);
if (!frame_ || old_title == title_)
return;
DispatchDidReceiveTitle();
}
void Document::DispatchDidReceiveTitle() {
frame_->Client()->DispatchDidReceiveTitle(title_);
}
void Document::setTitle(const String& title) {
// Title set by JavaScript -- overrides any title elements.
if (!title_element_) {
if (IsHTMLDocument() || IsXHTMLDocument()) {
HTMLElement* head_element = head();
if (!head_element)
return;
title_element_ = HTMLTitleElement::Create(*this);
head_element->AppendChild(title_element_.Get());
} else if (IsSVGDocument()) {
Element* element = documentElement();
if (!IsSVGSVGElement(element))
return;
title_element_ = SVGTitleElement::Create(*this);
element->InsertBefore(title_element_.Get(), element->firstChild());
}
} else {
if (!IsHTMLDocument() && !IsXHTMLDocument() && !IsSVGDocument())
title_element_ = nullptr;
}
if (auto* html_title = ToHTMLTitleElementOrNull(title_element_))
html_title->setText(title);
else if (auto* svg_title = ToSVGTitleElementOrNull(title_element_))
svg_title->SetText(title);
else
UpdateTitle(title);
}
void Document::SetTitleElement(Element* title_element) {
// 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())) {
title_element_ = Traversal<SVGTitleElement>::FirstChild(*documentElement());
} else {
if (title_element_ && title_element_ != title_element)
title_element_ = Traversal<HTMLTitleElement>::FirstWithin(*this);
else
title_element_ = title_element;
// 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(title_element_)) {
title_element_ = nullptr;
return;
}
}
if (auto* html_title = ToHTMLTitleElementOrNull(title_element_))
UpdateTitle(html_title->text());
else if (auto* svg_title = ToSVGTitleElementOrNull(title_element_))
UpdateTitle(svg_title->textContent());
}
void Document::RemoveTitle(Element* title_element) {
if (title_element_ != title_element)
return;
title_element_ = 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 (!title_element_)
UpdateTitle(String());
}
const AtomicString& Document::dir() {
Element* root_element = documentElement();
if (auto* html = ToHTMLHtmlElementOrNull(root_element))
return html->dir();
return g_null_atom;
}
void Document::setDir(const AtomicString& value) {
Element* root_element = documentElement();
if (auto* html = ToHTMLHtmlElementOrNull(root_element))
html->setDir(value);
}
mojom::PageVisibilityState Document::GetPageVisibilityState() const {
// The visibility of the document is inherited from the visibility of the
// page. If there is no page associated with the document, we will assume
// that the page is hidden, as specified by the spec:
// https://w3c.github.io/page-visibility/#hidden-attribute
if (!frame_ || !frame_->GetPage())
return mojom::PageVisibilityState::kHidden;
// While visibilitychange is being dispatched during unloading it is
// expected that the visibility is hidden regardless of the page's
// visibility.
if (load_event_progress_ >= kUnloadVisibilityChangeInProgress)
return mojom::PageVisibilityState::kHidden;
return frame_->GetPage()->VisibilityState();
}
bool Document::IsPrefetchOnly() const {
if (!frame_ || !frame_->GetPage())
return false;
PrerendererClient* prerenderer_client =
PrerendererClient::From(frame_->GetPage());
return prerenderer_client && prerenderer_client->IsPrefetchOnly();
}
String Document::visibilityState() const {
return PageVisibilityStateString(GetPageVisibilityState());
}
bool Document::hidden() const {
return GetPageVisibilityState() != mojom::PageVisibilityState::kVisible;
}
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 (GetPageVisibilityState() == mojom::PageVisibilityState::kVisible)
Timeline().SetAllCompositorPending();
if (hidden() && canvas_font_cache_)
canvas_font_cache_->PruneAll();
}
String Document::nodeName() const {
return "#document";
}
Node::NodeType Document::getNodeType() const {
return kDocumentNode;
}
FormController& Document::GetFormController() {
if (!form_controller_) {
form_controller_ = FormController::Create();
HistoryItem* history_item = Loader() ? Loader()->GetHistoryItem() : nullptr;
if (history_item)
history_item->SetDocumentState(form_controller_->FormElementsState());
}
return *form_controller_;
}
DocumentState* Document::FormElementsState() const {
if (!form_controller_)
return nullptr;
return form_controller_->FormElementsState();
}
void Document::SetStateForNewFormElements(const Vector<String>& state_vector) {
if (!state_vector.size() && !form_controller_)
return;
GetFormController().SetStateForNewFormElements(state_vector);
}
LocalFrameView* Document::View() const {
return frame_ ? frame_->View() : nullptr;
}
Page* Document::GetPage() const {
return frame_ ? frame_->GetPage() : nullptr;
}
LocalFrame* Document::GetFrameOfMasterDocument() const {
if (frame_)
return frame_;
if (imports_controller_)
return imports_controller_->Master()->GetFrame();
return nullptr;
}
Settings* Document::GetSettings() const {
return frame_ ? frame_->GetSettings() : nullptr;
}
Range* Document::createRange() {
return Range::Create(*this);
}
NodeIterator* Document::createNodeIterator(Node* root,
unsigned what_to_show,
V8NodeFilterCondition* filter) {
DCHECK(root);
return NodeIterator::Create(root, what_to_show, filter);
}
TreeWalker* Document::createTreeWalker(Node* root,
unsigned what_to_show,
V8NodeFilterCondition* filter) {
DCHECK(root);
return TreeWalker::Create(root, what_to_show, filter);
}
bool Document::NeedsLayoutTreeUpdate() const {
if (!IsActive() || !View())
return false;
if (NeedsFullLayoutTreeUpdate())
return true;
if (ChildNeedsStyleRecalc())
return true;
if (ChildNeedsStyleInvalidation())
return true;
if (GetLayoutView() && GetLayoutView()->WasNotifiedOfSubtreeChange())
return true;
return false;
}
bool Document::NeedsFullLayoutTreeUpdate() const {
if (!IsActive() || !View())
return false;
if (style_engine_->NeedsActiveStyleUpdate())
return true;
if (style_engine_->NeedsWhitespaceReattachment())
return true;
if (!use_elements_needing_update_.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 (lifecycle_.GetState() == DocumentLifecycle::kInPreLayout)
return false;
if (!ShouldScheduleLayout())
return false;
return true;
}
void Document::ScheduleLayoutTreeUpdate() {
DCHECK(!HasPendingVisualUpdate());
DCHECK(ShouldScheduleLayoutTreeUpdate());
DCHECK(NeedsLayoutTreeUpdate());
if (!View()->CanThrottleRendering())
GetPage()->Animator().ScheduleVisualUpdate(GetFrame());
lifecycle_.EnsureStateAtMost(DocumentLifecycle::kVisualUpdatePending);
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"ScheduleStyleRecalculation", TRACE_EVENT_SCOPE_THREAD,
"data",
InspectorRecalculateStylesEvent::Data(GetFrame()));
++style_version_;
}
bool Document::HasPendingForcedStyleRecalc() const {
return HasPendingVisualUpdate() && !InStyleRecalc() &&
GetStyleChangeType() >= kSubtreeStyleChange;
}
void Document::UpdateStyleInvalidationIfNeeded() {
DCHECK(IsActive());
ScriptForbiddenScope forbid_script;
if (!ChildNeedsStyleInvalidation() && !NeedsStyleInvalidation())
return;
TRACE_EVENT0("blink", "Document::updateStyleInvalidationIfNeeded");
GetStyleEngine().GetStyleInvalidator().Invalidate(*this);
}
void Document::SetupFontBuilder(ComputedStyle& document_style) {
FontBuilder font_builder(this);
CSSFontSelector* selector = GetStyleEngine().GetFontSelector();
font_builder.CreateFontForDocument(selector, document_style);
}
void Document::PropagateStyleToViewport() {
DCHECK(InStyleRecalc());
DCHECK(documentElement());
HTMLElement* body = this->body();
const ComputedStyle* body_style =
body ? body->EnsureComputedStyle() : nullptr;
const ComputedStyle* document_element_style =
documentElement()->EnsureComputedStyle();
WritingMode root_writing_mode = document_element_style->GetWritingMode();
TextDirection root_direction = document_element_style->Direction();
if (body_style) {
root_writing_mode = body_style->GetWritingMode();
root_direction = body_style->Direction();
}
const ComputedStyle* background_style = document_element_style;
// 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) &&
!background_style->HasBackground())
background_style = body_style;
Color background_color =
background_style->VisitedDependentColor(GetCSSPropertyBackgroundColor());
FillLayer background_layers = background_style->BackgroundLayers();
for (auto current_layer = &background_layers; current_layer;
current_layer = current_layer->Next()) {
// http://www.w3.org/TR/css3-background/#root-background
// The root element background always have painting area of the whole
// canvas.
current_layer->SetClip(EFillBox::kBorder);
// 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 (current_layer->Attachment() == EFillAttachment::kScroll)
current_layer->SetAttachment(EFillAttachment::kLocal);
}
EImageRendering image_rendering = background_style->ImageRendering();
const ComputedStyle* overflow_style = nullptr;
if (Element* element = ViewportDefiningElement(document_element_style)) {
if (element == body) {
overflow_style = body_style;
} else {
DCHECK_EQ(element, documentElement());
overflow_style = document_element_style;
// 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 (body_style && !body_style->IsOverflowVisible())
UseCounter::Count(*this, WebFeature::kBodyScrollsInAdditionToViewport);
}
}
EOverflowAnchor overflow_anchor = EOverflowAnchor::kAuto;
EOverflow overflow_x = EOverflow::kAuto;
EOverflow overflow_y = EOverflow::kAuto;
bool column_gap_normal = true;
float column_gap = 0;
if (overflow_style) {
overflow_anchor = overflow_style->OverflowAnchor();
overflow_x = overflow_style->OverflowX();
overflow_y = overflow_style->OverflowY();
// Visible overflow on the viewport is meaningless, and the spec says to
// treat it as 'auto':
if (overflow_x == EOverflow::kVisible)
overflow_x = EOverflow::kAuto;
if (overflow_y == EOverflow::kVisible)
overflow_y = EOverflow::kAuto;
if (overflow_anchor == EOverflowAnchor::kVisible)
overflow_anchor = EOverflowAnchor::kAuto;
// 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.
if (!overflow_style->HasNormalColumnGap()) {
column_gap_normal = false;
column_gap = overflow_style->ColumnGap();
}
}
ScrollSnapType snap_type = overflow_style->GetScrollSnapType();
ScrollBehavior scroll_behavior = document_element_style->GetScrollBehavior();
EOverscrollBehavior overscroll_behavior_x =
overflow_style->OverscrollBehaviorX();
EOverscrollBehavior overscroll_behavior_y =
overflow_style->OverscrollBehaviorY();
using OverscrollBehaviorType = WebOverscrollBehavior::OverscrollBehaviorType;
if (RuntimeEnabledFeatures::CSSOverscrollBehaviorEnabled() &&
IsInMainFrame()) {
GetPage()->GetOverscrollController().SetOverscrollBehavior(
WebOverscrollBehavior(
static_cast<OverscrollBehaviorType>(overscroll_behavior_x),
static_cast<OverscrollBehaviorType>(overscroll_behavior_y)));
}
scoped_refptr<ComputedStyle> viewport_style = GetLayoutView()->MutableStyle();
if (viewport_style->GetWritingMode() != root_writing_mode ||
viewport_style->Direction() != root_direction ||
viewport_style->VisitedDependentColor(GetCSSPropertyBackgroundColor()) !=
background_color ||
viewport_style->BackgroundLayers() != background_layers ||
viewport_style->ImageRendering() != image_rendering ||
viewport_style->OverflowAnchor() != overflow_anchor ||
viewport_style->OverflowX() != overflow_x ||
viewport_style->OverflowY() != overflow_y ||
viewport_style->HasNormalColumnGap() != column_gap_normal ||
viewport_style->ColumnGap() != column_gap ||
viewport_style->GetScrollSnapType() != snap_type ||
viewport_style->GetScrollBehavior() != scroll_behavior ||
viewport_style->OverscrollBehaviorX() != overscroll_behavior_x ||
viewport_style->OverscrollBehaviorY() != overscroll_behavior_y) {
scoped_refptr<ComputedStyle> new_style =
ComputedStyle::Clone(*viewport_style);
new_style->SetWritingMode(root_writing_mode);
new_style->SetDirection(root_direction);
new_style->SetBackgroundColor(background_color);
new_style->AccessBackgroundLayers() = background_layers;
new_style->SetImageRendering(image_rendering);
new_style->SetOverflowAnchor(overflow_anchor);
new_style->SetOverflowX(overflow_x);
new_style->SetOverflowY(overflow_y);
if (column_gap_normal)
new_style->SetHasNormalColumnGap();
else
new_style->SetColumnGap(column_gap);
new_style->SetScrollSnapType(snap_type);
new_style->SetScrollBehavior(scroll_behavior);
new_style->SetOverscrollBehaviorX(overscroll_behavior_x);
new_style->SetOverscrollBehaviorY(overscroll_behavior_y);
GetLayoutView()->SetStyle(new_style);
SetupFontBuilder(*new_style);
}
}
#if DCHECK_IS_ON()
static void AssertLayoutTreeUpdated(Node& root) {
for (Node& node : NodeTraversal::InclusiveDescendantsOf(root)) {
// We leave some nodes with dirty bits in the tree because they don't
// matter like Comment and ProcessingInstruction nodes.
// TODO(esprehn): Don't even mark those nodes as needing recalcs in the
// first place.
if (!node.IsElementNode() && !node.IsTextNode() && !node.IsShadowRoot() &&
!node.IsDocumentNode())
continue;
DCHECK(!node.NeedsStyleRecalc());
DCHECK(!node.ChildNeedsStyleRecalc());
DCHECK(!node.NeedsReattachLayoutTree());
DCHECK(!node.ChildNeedsReattachLayoutTree());
DCHECK(!node.ChildNeedsDistributionRecalc());
DCHECK(!node.NeedsStyleInvalidation());
DCHECK(!node.ChildNeedsStyleInvalidation());
for (ShadowRoot* shadow_root = node.YoungestShadowRoot(); shadow_root;
shadow_root = shadow_root->OlderShadowRoot())
AssertLayoutTreeUpdated(*shadow_root);
}
}
#endif
void Document::UpdateStyleAndLayoutTree() {
DCHECK(IsMainThread());
HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose;
ScriptForbiddenScope forbid_script;
if (!View() || !IsActive())
return;
if (View()->ShouldThrottleRendering())
return;
if (!NeedsLayoutTreeUpdate()) {
if (Lifecycle().GetState() < DocumentLifecycle::kStyleClean) {
// 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::kInStyleRecalc);
Lifecycle().AdvanceTo(DocumentLifecycle::kStyleClean);
}
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.
CHECK(Lifecycle().StateAllowsTreeMutations());
TRACE_EVENT_BEGIN1("blink,devtools.timeline", "UpdateLayoutTree", "beginData",
InspectorRecalculateStylesEvent::Data(GetFrame()));
unsigned start_element_count = GetStyleEngine().StyleForElementCount();
probe::RecalculateStyle recalculate_style_scope(this);
DocumentAnimations::UpdateAnimationTimingIfNeeded(*this);
EvaluateMediaQueryListIfNeeded();
UpdateUseShadowTreesIfNeeded();
UpdateDistribution();
UpdateActiveStyle();
UpdateStyleInvalidationIfNeeded();
// FIXME: We should update style on our ancestor chain before proceeding
// however doing so currently causes several tests to crash, as
// LocalFrame::setDocument calls Document::attach before setting the
// LocalDOMWindow on the LocalFrame, or the SecurityOrigin on the
// document. The attach, in turn resolves style (here) and then when we
// resolve style on the parent chain, we may end up re-attaching our
// containing iframe, which when asked HTMLFrameElementBase::isURLAllowed hits
// a null-dereference due to security code always assuming the document has a
// SecurityOrigin.
UpdateStyle();
NotifyLayoutTreeOfSubtreeChanges();
// As a result of the style recalculation, the currently hovered element might
// have been detached (for example, by setting display:none in the :hover
// style), schedule another mouseMove event to check if any other elements
// ended up under the mouse pointer due to re-layout.
if (HoverElement() && !HoverElement()->GetLayoutObject() && GetFrame()) {
GetFrame()->GetEventHandler().DispatchFakeMouseMoveEventSoon(
MouseEventManager::FakeMouseMoveReason::kPerFrame);
}
if (focused_element_ && !focused_element_->IsFocusable())
ClearFocusedElementSoon();
GetLayoutView()->ClearHitTestCache();
DCHECK(!DocumentAnimations::NeedsAnimationTimingUpdate(*this));
unsigned element_count =
GetStyleEngine().StyleForElementCount() - start_element_count;
TRACE_EVENT_END1("blink,devtools.timeline", "UpdateLayoutTree",
"elementCount", element_count);
#if DCHECK_IS_ON()
AssertLayoutTreeUpdated(*this);
#endif
}
void Document::UpdateActiveStyle() {
DCHECK(IsActive());
DCHECK(IsMainThread());
TRACE_EVENT0("blink", "Document::updateActiveStyle");
GetStyleEngine().UpdateActiveStyle();
}
void Document::UpdateStyle() {
DCHECK(!View()->ShouldThrottleRendering());
TRACE_EVENT_BEGIN0("blink,blink_style", "Document::updateStyle");
RUNTIME_CALL_TIMER_SCOPE(V8PerIsolateData::MainThreadIsolate(),
RuntimeCallStats::CounterId::kUpdateStyle);
double start_time = CurrentTimeTicksInSeconds();
unsigned initial_element_count = GetStyleEngine().StyleForElementCount();
lifecycle_.AdvanceTo(DocumentLifecycle::kInStyleRecalc);
StyleRecalcChange change = kNoChange;
if (GetStyleChangeType() >= kSubtreeStyleChange)
change = kForce;
NthIndexCache nth_index_cache(*this);
// TODO(futhark@chromium.org): Cannot access the EnsureStyleResolver() before
// calling StyleForViewport() 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 == kForce) {
has_nodes_with_placeholder_style_ = false;
scoped_refptr<ComputedStyle> viewport_style =
StyleResolver::StyleForViewport(*this);
StyleRecalcChange local_change = ComputedStyle::StylePropagationDiff(
viewport_style.get(), GetLayoutView()->Style());
if (local_change != kNoChange)
GetLayoutView()->SetStyle(std::move(viewport_style));
}
ClearNeedsStyleRecalc();
ClearNeedsReattachLayoutTree();
StyleResolver& resolver = EnsureStyleResolver();
bool should_record_stats;
TRACE_EVENT_CATEGORY_GROUP_ENABLED("blink,blink_style", &should_record_stats);
GetStyleEngine().SetStatsEnabled(should_record_stats);
if (Element* document_element = documentElement()) {
if (document_element->ShouldCallRecalcStyle(change)) {
TRACE_EVENT0("blink,blink_style", "Document::recalcStyle");
Element* viewport_defining = ViewportDefiningElement();
document_element->RecalcStyle(change);
if (viewport_defining != ViewportDefiningElement())
ViewportDefiningElementDidChange();
}
GetStyleEngine().MarkForWhitespaceReattachment();
PropagateStyleToViewport();
if (document_element->NeedsReattachLayoutTree() ||
document_element->ChildNeedsReattachLayoutTree()) {
TRACE_EVENT0("blink,blink_style", "Document::rebuildLayoutTree");
WhitespaceAttacher whitespace_attacher;
document_element->RebuildLayoutTree(whitespace_attacher);
}
}
GetStyleEngine().ClearWhitespaceReattachSet();
View()->UpdateCountersAfterStyleChange();
View()->RecalcOverflowAfterStyleChange();
ClearChildNeedsStyleRecalc();
ClearChildNeedsReattachLayoutTree();
DCHECK(!NeedsStyleRecalc());
DCHECK(!ChildNeedsStyleRecalc());
DCHECK(!NeedsReattachLayoutTree());
DCHECK(!ChildNeedsReattachLayoutTree());
DCHECK(InStyleRecalc());
DCHECK_EQ(GetStyleResolver(), &resolver);
lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean);
if (should_record_stats) {
TRACE_EVENT_END2(
"blink,blink_style", "Document::updateStyle", "resolverAccessCount",
GetStyleEngine().StyleForElementCount() - initial_element_count,
"counters", GetStyleEngine().Stats()->ToTracedValue());
} else {
TRACE_EVENT_END1(
"blink,blink_style", "Document::updateStyle", "resolverAccessCount",
GetStyleEngine().StyleForElementCount() - initial_element_count);
}
double update_duration_seconds = CurrentTimeTicksInSeconds() - start_time;
DEFINE_STATIC_LOCAL(CustomCountHistogram, update_histogram,
("Style.UpdateTime", 0, 10000000, 50));
update_histogram.Count(update_duration_seconds * 1000 * 1000);
CSSTiming::From(*this).RecordUpdateDuration(update_duration_seconds);
}
void Document::ViewportDefiningElementDidChange() {
HTMLBodyElement* body = FirstBodyElement();
if (!body)
return;
LayoutObject* layout_object = body->GetLayoutObject();
if (layout_object && layout_object->IsLayoutBlock()) {
// When the overflow style for documentElement changes to or from visible,
// it changes whether the body element's box should have scrollable overflow
// on its own box or propagated to the viewport. If the body style did not
// need a recalc, this will not be updated as its done as part of setting
// ComputedStyle on the LayoutObject. Force a SetStyle for body when the
// ViewportDefiningElement changes in order to trigger an update of
// HasOverflowClip() and the PaintLayer in StyleDidChange().
layout_object->SetStyle(ComputedStyle::Clone(*layout_object->Style()));
}
}
void Document::NotifyLayoutTreeOfSubtreeChanges() {
if (!GetLayoutView()->WasNotifiedOfSubtreeChange())
return;
lifecycle_.AdvanceTo(DocumentLifecycle::kInLayoutSubtreeChange);
GetLayoutView()->HandleSubtreeModifications();
DCHECK(!GetLayoutView()->WasNotifiedOfSubtreeChange());
lifecycle_.AdvanceTo(DocumentLifecycle::kLayoutSubtreeChangeClean);
}
bool Document::NeedsLayoutTreeUpdateForNode(const Node& node) const {
if (!node.CanParticipateInFlatTree())
return false;
if (!NeedsLayoutTreeUpdate())
return false;
if (!node.isConnected())
return false;
if (NeedsFullLayoutTreeUpdate() || node.NeedsStyleRecalc() ||
node.NeedsStyleInvalidation())
return true;
for (const ContainerNode* ancestor = LayoutTreeBuilderTraversal::Parent(node);
ancestor; ancestor = LayoutTreeBuilderTraversal::Parent(*ancestor)) {
if (ancestor->NeedsStyleRecalc() || ancestor->NeedsStyleInvalidation() ||
ancestor->NeedsAdjacentStyleRecalc())
return true;
}
return false;
}
void Document::UpdateStyleAndLayoutTreeForNode(const Node* node) {
DCHECK(node);
if (!NeedsLayoutTreeUpdateForNode(*node))
return;
UpdateStyleAndLayoutTree();
}
void Document::UpdateStyleAndLayoutIgnorePendingStylesheetsForNode(Node* node) {
DCHECK(node);
if (!node->InActiveDocument())
return;
UpdateStyleAndLayoutIgnorePendingStylesheets();
}
void Document::UpdateStyleAndLayout() {
DCHECK(IsMainThread());
HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose;
ScriptForbiddenScope forbid_script;
LocalFrameView* frame_view = View();
if (frame_view && frame_view->IsInPerformLayout()) {
// View layout should not be re-entrant.
NOTREACHED();
return;
}
if (HTMLFrameOwnerElement* owner = LocalOwner())
owner->GetDocument().UpdateStyleAndLayout();
UpdateStyleAndLayoutTree();
if (!IsActive())
return;
if (frame_view->NeedsLayout())
frame_view->UpdateLayout();
if (goto_anchor_needed_after_stylesheets_load_)
frame_view->ProcessUrlFragment(url_);
if (Lifecycle().GetState() < DocumentLifecycle::kLayoutClean)
Lifecycle().AdvanceTo(DocumentLifecycle::kLayoutClean);
if (LocalFrameView* frame_view = View())
frame_view->PerformScrollAnchoringAdjustments();
}
void Document::LayoutUpdated() {
// Plugins can run script inside layout which can detach the page.
// TODO(dcheng): Does it make sense to do any of this work if detached?
if (GetFrame() && GetFrame()->IsMainFrame())
GetFrame()->GetPage()->GetChromeClient().LayoutUpdated();
Markers().InvalidateRectsForAllTextMatchMarkers();
// 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() &&
!GetStyleEngine().HasPendingScriptBlockingSheets()) {
if (!document_timing_.FirstLayout())
document_timing_.MarkFirstLayout();
}
root_scroller_controller_->DidUpdateLayout();
}
void Document::ClearFocusedElementSoon() {
if (!clear_focused_element_timer_.IsActive())
clear_focused_element_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void Document::ClearFocusedElementTimerFired(TimerBase*) {
UpdateStyleAndLayoutTree();
if (focused_element_ && !focused_element_->IsFocusable())
focused_element_->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() {
// See comment for equivalent CHECK in Document::UpdateStyleAndLayoutTree.
// Updating style and layout can dirty state that must remain clean during
// lifecycle updates.
CHECK(Lifecycle().StateAllowsTreeMutations());
StyleEngine::IgnoringPendingStylesheet ignoring(GetStyleEngine());
if (GetStyleEngine().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* body_element = body();
if (body_element && !body_element->GetLayoutObject() &&
pending_sheet_layout_ == kNoLayoutWithPendingSheets) {
pending_sheet_layout_ = kDidLayoutWithPendingSheets;
GetStyleEngine().MarkAllTreeScopesDirty();
}
if (has_nodes_with_placeholder_style_) {
// 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(kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kCleanupPlaceholderStyles));
}
}
UpdateStyleAndLayoutTree();
}
void Document::UpdateStyleAndLayoutIgnorePendingStylesheets(
Document::RunPostLayoutTasks run_post_layout_tasks) {
UpdateStyleAndLayoutTreeIgnorePendingStylesheets();
UpdateStyleAndLayout();
if (run_post_layout_tasks == kRunPostLayoutTasksSynchronously && View())
View()->FlushAnyPendingPostLayoutTasks();
}
scoped_refptr<ComputedStyle>
Document::StyleForElementIgnoringPendingStylesheets(Element* element) {
DCHECK_EQ(element->GetDocument(), this);
StyleEngine::IgnoringPendingStylesheet ignoring(GetStyleEngine());
if (!element->CanParticipateInFlatTree())
return EnsureStyleResolver().StyleForElement(element, nullptr);
ContainerNode* parent = LayoutTreeBuilderTraversal::Parent(*element);
const ComputedStyle* parent_style =
parent ? parent->EnsureComputedStyle() : nullptr;
ContainerNode* layout_parent =
parent ? LayoutTreeBuilderTraversal::LayoutParent(*element) : nullptr;
const ComputedStyle* layout_parent_style =
layout_parent ? layout_parent->EnsureComputedStyle() : parent_style;
return EnsureStyleResolver().StyleForElement(element, parent_style,
layout_parent_style);
}
scoped_refptr<ComputedStyle> Document::StyleForPage(int page_index) {
UpdateDistribution();
return EnsureStyleResolver().StyleForPage(page_index);
}
void Document::EnsurePaintLocationDataValidForNode(const Node* node) {
DCHECK(node);
if (!node->InActiveDocument())
return;
// For all nodes we must have up-to-date style and have performed layout to do
// any location-based calculation.
UpdateStyleAndLayoutIgnorePendingStylesheets();
// The location of elements that are position: sticky is not known until
// compositing inputs are cleaned. Therefore, for any elements that are either
// sticky or are in a sticky sub-tree (e.g. are affected by a sticky element),
// we need to also clean compositing inputs.
if (View() && node->GetLayoutObject() &&
node->GetLayoutObject()->StyleRef().SubtreeIsSticky()) {
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
// In SPv2, compositing inputs are cleaned as part of PrePaint.
View()->UpdateAllLifecyclePhasesExceptPaint();
} else {
View()->UpdateLifecycleToCompositingInputsClean();
}
}
}
bool Document::IsPageBoxVisible(int page_index) {
return StyleForPage(page_index)->Visibility() !=
EVisibility::kHidden; // display property doesn't apply to @page.
}
void Document::PageSizeAndMarginsInPixels(int page_index,
DoubleSize& page_size,
int& margin_top,
int& margin_right,
int& margin_bottom,
int& margin_left) {
scoped_refptr<ComputedStyle> style = StyleForPage(page_index);
double width = page_size.Width();
double height = page_size.Height();
switch (style->PageSizeType()) {
case EPageSizeType::kAuto:
break;
case EPageSizeType::kLandscape:
if (width < height)
std::swap(width, height);
break;
case EPageSizeType::kPortrait:
if (width > height)
std::swap(width, height);
break;
case EPageSizeType::kResolved: {
FloatSize size = style->PageSize();
width = size.Width();
height = size.Height();
break;
}
default:
NOTREACHED();
}
page_size = DoubleSize(width, height);
// The percentage is calculated with respect to the width even for margin top
// and bottom.
// http://www.w3.org/TR/CSS2/box.html#margin-properties
margin_top = style->MarginTop().IsAuto()
? margin_top
: IntValueForLength(style->MarginTop(), width);
margin_right = style->MarginRight().IsAuto()
? margin_right
: IntValueForLength(style->MarginRight(), width);
margin_bottom = style->MarginBottom().IsAuto()
? margin_bottom
: IntValueForLength(style->MarginBottom(), width);
margin_left = style->MarginLeft().IsAuto()
? margin_left
: IntValueForLength(style->MarginLeft(), width);
}
void Document::SetIsViewSource(bool is_view_source) {
is_view_source_ = is_view_source;
if (!is_view_source_)
return;
}
void Document::ScheduleUseShadowTreeUpdate(SVGUseElement& element) {
use_elements_needing_update_.insert(&element);
ScheduleLayoutTreeUpdateIfNeeded();
}
void Document::UnscheduleUseShadowTreeUpdate(SVGUseElement& element) {
use_elements_needing_update_.erase(&element);
}
void Document::UpdateUseShadowTreesIfNeeded() {
ScriptForbiddenScope forbid_script;
if (use_elements_needing_update_.IsEmpty())
return;
HeapHashSet<Member<SVGUseElement>> elements;
use_elements_needing_update_.swap(elements);
for (SVGUseElement* element : elements)
element->BuildPendingResource();
}
StyleResolver* Document::GetStyleResolver() const {
return style_engine_->Resolver();
}
StyleResolver& Document::EnsureStyleResolver() const {
return style_engine_->EnsureResolver();
}
void Document::Initialize() {
DCHECK_EQ(lifecycle_.GetState(), DocumentLifecycle::kInactive);
DCHECK(!ax_object_cache_ || this != &AXObjectCacheOwner());
layout_view_ = new LayoutView(this);
SetLayoutObject(layout_view_);
layout_view_->SetIsInWindow(true);
layout_view_->SetStyle(StyleResolver::StyleForViewport(*this));
layout_view_->Compositor()->SetNeedsCompositingUpdate(
kCompositingUpdateAfterCompositingInputChange);
AttachContext context;
ContainerNode::AttachLayoutTree(context);
// The TextAutosizer can't update layout view info while the Document is
// detached, so update now in case anything changed.
if (TextAutosizer* autosizer = GetTextAutosizer())
autosizer->UpdatePageInfo();
frame_->DocumentAttached();
lifecycle_.AdvanceTo(DocumentLifecycle::kStyleClean);
if (View())
View()->DidAttachDocument();
// Observer(s) should not be initialized until the document is initialized /
// attached to a frame. Otherwise ContextLifecycleObserver::contextDestroyed
// wouldn't be fired.
network_state_observer_ = new NetworkStateObserver(*this);
}
void Document::Shutdown() {
TRACE_EVENT0("blink", "Document::shutdown");
CHECK(!frame_ || 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 run loops.
FrameNavigationDisabler navigation_disabler(*frame_);
// Defer plugin dispose to avoid plugins trying to run script inside
// ScriptForbiddenScope, which will crash the renderer after
// https://crrev.com/200984
HTMLFrameOwnerElement::PluginDisposeSuspendScope suspend_plugin_dispose;
// Don't allow script to run in the middle of detachLayoutTree() because a
// detaching Document is not in a consistent state.
ScriptForbiddenScope forbid_script;
lifecycle_.AdvanceTo(DocumentLifecycle::kStopping);
View()->Dispose();
// TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
CHECK(!View()->IsAttached());
// If the EmbeddedContentView of the document's frame owner doesn't match
// view() then LocalFrameView::Dispose() didn't clear the owner's
// EmbeddedContentView. If we don't clear it here, it may be clobbered later
// in LocalFrame::CreateView(). See also https://crbug.com/673170 and the
// comment in LocalFrameView::Dispose().
HTMLFrameOwnerElement* owner_element = frame_->DeprecatedLocalOwner();
// In the case of a provisional frame, skip clearing the EmbeddedContentView.
// A provisional frame is not fully attached to the DOM yet and clearing the
// EmbeddedContentView here could clear a not-yet-swapped-out frame
// (https://crbug.com/807772).
if (owner_element && !frame_->IsProvisional())
owner_element->SetEmbeddedContentView(nullptr);
markers_->PrepareForDestruction();
if (GetPage())
GetPage()->DocumentDetached(this);
probe::documentDetached(this);
if (frame_->Client()->GetSharedWorkerRepositoryClient())
frame_->Client()->GetSharedWorkerRepositoryClient()->DocumentDetached(this);
// FIXME: consider using PausableObject.
if (scripted_animation_controller_)
scripted_animation_controller_->ClearDocumentPointer();
scripted_animation_controller_.Clear();
scripted_idle_task_controller_.Clear();
if (SvgExtensions())
AccessSVGExtensions().PauseAnimations();
// FIXME: This shouldn't be needed once LocalDOMWindow becomes
// ExecutionContext.
if (dom_window_)
dom_window_->ClearEventQueue();
if (layout_view_)
layout_view_->SetIsInWindow(false);
if (RegistrationContext())
RegistrationContext()->DocumentWasDetached();
MutationObserver::CleanSlotChangeList(*this);
hover_element_ = nullptr;
active_element_ = nullptr;
autofocus_element_ = nullptr;
if (focused_element_.Get()) {
Element* old_focused_element = focused_element_;
focused_element_ = nullptr;
if (GetPage())
GetPage()->GetChromeClient().FocusedNodeChanged(old_focused_element,
nullptr);
}
sequential_focus_navigation_starting_point_ = nullptr;
if (this == &AXObjectCacheOwner())
ClearAXObjectCache();
layout_view_ = nullptr;
ContainerNode::DetachLayoutTree();
// TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
CHECK(!View()->IsAttached());
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);
}
}
}
GetStyleEngine().DidDetach();
GetPage()->GetEventHandlerRegistry().DocumentDetached(*this);
// Signal destruction to mutation observers.
DocumentShutdownNotifier::NotifyContextDestroyed();
SynchronousMutationNotifier::NotifyContextDestroyed();
// 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())
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 (imports_controller_) {
imports_controller_->Dispose();
ClearImportsController();
}
timers_.SetTimerTaskRunner(
Platform::Current()->CurrentThread()->Scheduler()->TimerTaskRunner());
if (media_query_matcher_)
media_query_matcher_->DocumentDetached();
lifecycle_.AdvanceTo(DocumentLifecycle::kStopped);
// TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
CHECK(!View()->IsAttached());
// TODO(haraken): Call contextDestroyed() before we start any disruptive
// operations.
// TODO(haraken): Currently we call notifyContextDestroyed() only in
// Document::detachLayoutTree(), which means that we don't call
// notifyContextDestroyed() for a document that doesn't get detached.
// If such a document has any observer, the observer won't get
// a contextDestroyed() notification. This can happen for a document
// created by DOMImplementation::createDocument().
ExecutionContext::NotifyContextDestroyed();
// TODO(crbug.com/729196): Trace why LocalFrameView::DetachFromLayout crashes.
CHECK(!View()->IsAttached());
needs_to_record_ukm_outlive_time_ = IsInMainFrame();
if (needs_to_record_ukm_outlive_time_) {
// Ensure |ukm_recorder_| and |ukm_source_id_|.
UkmRecorder();
}
// This is required, as our LocalFrame might delete itself as soon as it
// detaches us. However, this violates Node::detachLayoutTree() semantics, as
// it's never possible to re-attach. Eventually Document::detachLayoutTree()
// should be renamed, or this setting of the frame to 0 could be made
// explicit in each of the callers of Document::detachLayoutTree().
frame_ = nullptr;
document_outlive_time_reporter_ =
WTF::WrapUnique(new DocumentOutliveTimeReporter(this));
}
void Document::RemoveAllEventListeners() {
ContainerNode::RemoveAllEventListeners();
if (LocalDOMWindow* dom_window = domWindow())
dom_window->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->GetFrame() && doc->GetFrame()->PagePopupOwner()) {
DCHECK(!doc->ax_object_cache_);
return doc->GetFrame()
->PagePopupOwner()
->GetDocument()
.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 (ax_object_cache_)
ax_object_cache_->Dispose();
ax_object_cache_.Clear();
}
AXObjectCache* Document::ExistingAXObjectCache() const {
auto& cache_owner = AXObjectCacheOwner();
// If the layoutObject is gone then we are in the process of destruction.
// This method will be called before m_frame = nullptr.
if (!cache_owner.GetLayoutView())
return nullptr;
return cache_owner.ax_object_cache_.Get();
}
AXObjectCache* Document::GetOrCreateAXObjectCache() const {
Settings* settings = GetSettings();
if (!settings || !settings->GetAccessibilityEnabled())
return nullptr;
// Every document has its own AXObjectCache if accessibility is enabled,
// except for page popups (such as select popups or context menus),
// which share the AXObjectCache of their owner.
//
// See http://crbug.com/532249
Document& cache_owner = AXObjectCacheOwner();
// If the document has already been detached, do not make a new axObjectCache.
if (!cache_owner.GetLayoutView())
return nullptr;
DCHECK(&cache_owner == this || !ax_object_cache_);
if (!cache_owner.ax_object_cache_)
cache_owner.ax_object_cache_ = AXObjectCache::Create(cache_owner);
return cache_owner.ax_object_cache_.Get();
}
CanvasFontCache* Document::GetCanvasFontCache() {
if (!canvas_font_cache_)
canvas_font_cache_ = CanvasFontCache::Create(*this);
return canvas_font_cache_.Get();
}
DocumentParser* Document::CreateParser() {
if (IsHTMLDocument())
return HTMLDocumentParser::Create(ToHTMLDocument(*this),
parser_sync_policy_);
// 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::GetScriptableDocumentParser() const {
return Parser() ? Parser()->AsScriptableDocumentParser() : nullptr;
}
void Document::open(Document* entered_document,
ExceptionState& exception_state) {
if (ImportLoader()) {
exception_state.ThrowDOMException(
kInvalidStateError, "Imported document doesn't support open().");
return;
}
if (!IsHTMLDocument()) {
exception_state.ThrowDOMException(kInvalidStateError,
"Only HTML documents support open().");
return;
}
if (throw_on_dynamic_markup_insertion_count_) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Custom Element constructor should not use open().");
return;
}
if (entered_document) {
if (!GetSecurityOrigin()->IsSameSchemeHostPortAndSuborigin(
entered_document->GetSecurityOrigin())) {
exception_state.ThrowSecurityError(
"Can only call open() on same-origin documents.");
return;
}
SetSecurityOrigin(entered_document->GetMutableSecurityOrigin());
if (this != entered_document) {
// Clear the hash fragment from the inherited URL to prevent a
// scroll-into-view for any document.open()'d frame.
KURL new_url = entered_document->Url();
new_url.SetFragmentIdentifier(String());
SetURL(new_url);
SetReferrerPolicy(entered_document->GetReferrerPolicy());
}
cookie_url_ = entered_document->CookieURL();
}
open();
}
void Document::open() {
DCHECK(!ImportLoader());
if (frame_) {
if (ScriptableDocumentParser* parser = GetScriptableDocumentParser()) {
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 (frame_->Loader().HasProvisionalNavigation()) {
frame_->Loader().StopAllLoaders();
// PlzNavigate: navigations handled by the client should also be
// cancelled.
if (frame_->Client() &&
frame_->GetSettings()->GetBrowserSideNavigationEnabled()) {
frame_->Client()->AbortClientNavigation();
}
}
}
RemoveAllEventListenersRecursively();
ResetTreeScope();
if (frame_)
frame_->Selection().Clear();
ImplicitOpen(kForceSynchronousParsing);
if (ScriptableDocumentParser* parser = GetScriptableDocumentParser())
parser->SetWasCreatedByScript(true);
if (frame_)
frame_->Loader().DidExplicitOpen();
}
void Document::DetachParser() {
if (!parser_)
return;
parser_->Detach();
parser_.Clear();
DocumentParserTiming::From(*this).MarkParserDetached();
}
void Document::CancelParsing() {
DetachParser();
SetParsingState(kFinishedParsing);
SetReadyState(kComplete);
SuppressLoadEvent();
}
DocumentParser* Document::OpenForNavigation(
ParserSynchronizationPolicy parser_sync_policy,
const AtomicString& mime_type,
const AtomicString& encoding) {
DocumentParser* parser = ImplicitOpen(parser_sync_policy);
if (parser->NeedsDecoder())
parser->SetDecoder(BuildTextResourceDecoderFor(this, mime_type, encoding));
return parser;
}
DocumentParser* Document::ImplicitOpen(
ParserSynchronizationPolicy parser_sync_policy) {
RemoveChildren();
DCHECK(!focused_element_);
SetCompatibilityMode(kNoQuirksMode);
if (!ThreadedParsingEnabledForTesting()) {
parser_sync_policy = kForceSynchronousParsing;
} else if (parser_sync_policy == kAllowAsynchronousParsing &&
IsPrefetchOnly()) {
// Prefetch must be synchronous.
parser_sync_policy = kForceSynchronousParsing;
}
DetachParser();
parser_sync_policy_ = parser_sync_policy;
parser_ = CreateParser();
DocumentParserTiming::From(*this).MarkParserStart();
SetParsingState(kParsing);
SetReadyState(kLoading);
if (load_event_progress_ != kLoadEventInProgress &&
PageDismissalEventBeingDispatched() == kNoDismissal) {
load_event_progress_ = kLoadEventNotRun;
}
return parser_;
}
HTMLElement* Document::body() const {
if (!documentElement() || !IsHTMLHtmlElement(documentElement()))
return nullptr;
for (HTMLElement* child =
Traversal<HTMLElement>::FirstChild(*documentElement());
child; child = Traversal<HTMLElement>::NextSibling(*child)) {
if (IsHTMLFrameSetElement(*child) || IsHTMLBodyElement(*child))
return child;
}
return nullptr;
}
HTMLBodyElement* Document::FirstBodyElement() const {
if (!documentElement() || !IsHTMLHtmlElement(documentElement()))
return nullptr;
for (HTMLElement* child =
Traversal<HTMLElement>::FirstChild(*documentElement());
child; child = Traversal<HTMLElement>::NextSibling(*child)) {
if (auto* body = ToHTMLBodyElementOrNull(*child))
return body;
}
return nullptr;
}
void Document::setBody(HTMLElement* prp_new_body,
ExceptionState& exception_state) {
HTMLElement* new_body = prp_new_body;
if (!new_body) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
ExceptionMessages::ArgumentNullOrIncorrectType(1, "HTMLElement"));
return;
}
if (!documentElement()) {
exception_state.ThrowDOMException(kHierarchyRequestError,
"No document element exists.");
return;
}
if (!IsHTMLBodyElement(*new_body) && !IsHTMLFrameSetElement(*new_body)) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"The new body element is of type '" + new_body->tagName() +
"'. It must be either a 'BODY' or 'FRAMESET' element.");
return;
}
HTMLElement* old_body = body();
if (old_body == new_body)
return;
if (old_body)
documentElement()->ReplaceChild(new_body, old_body, exception_state);
else
documentElement()->AppendChild(new_body, exception_state);
}
void Document::WillInsertBody() {
if (GetFrame())
GetFrame()->Client()->DispatchWillInsertBody();
if (auto* loader = Loader())
loader->Fetcher()->LoosenLoadThrottlingPolicy();
// 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 nullptr;
return Traversal<HTMLHeadElement>::FirstChild(*de);
}
Element* Document::ViewportDefiningElement(
const ComputedStyle* root_style) 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* root_element = documentElement();
Element* body_element = body();
if (!root_element)
return nullptr;
if (!root_style) {
root_style = root_element->GetComputedStyle();
if (!root_style)
return nullptr;
}
if (body_element && root_style->IsOverflowVisible() &&
IsHTMLHtmlElement(*root_element))
return body_element;
return root_element;
}
Document* Document::open(LocalDOMWindow* entered_window,
const AtomicString& type,
const AtomicString& replace,
ExceptionState& exception_state) {
open(entered_window->document(), exception_state);
return this;
}
DOMWindow* Document::open(LocalDOMWindow* current_window,
LocalDOMWindow* entered_window,
const AtomicString& url,
const AtomicString& name,
const AtomicString& features,
ExceptionState& exception_state) {
if (!domWindow()) {
exception_state.ThrowDOMException(kInvalidAccessError,
"The document has no window associated.");
return nullptr;
}
AtomicString frame_name = name.IsEmpty() ? "_blank" : name;
return domWindow()->open(url, frame_name, features, current_window,
entered_window, exception_state);
}
void Document::close(ExceptionState& exception_state) {
// FIXME: We should follow the specification more closely:
// http://www.whatwg.org/specs/web-apps/current-work/#dom-document-close
if (ImportLoader()) {
exception_state.ThrowDOMException(
kInvalidStateError, "Imported document doesn't support close().");
return;
}
if (!IsHTMLDocument()) {
exception_state.ThrowDOMException(kInvalidStateError,
"Only HTML documents support close().");
return;
}
if (throw_on_dynamic_markup_insertion_count_) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Custom Element constructor should not use close().");
return;
}
close();
}
void Document::close() {
if (!GetScriptableDocumentParser() ||
!GetScriptableDocumentParser()->WasCreatedByScript() ||
!GetScriptableDocumentParser()->IsParsing())
return;
parser_->Finish();
if (!parser_ || !parser_->IsParsing())
SetReadyState(kComplete);
CheckCompleted();
}
void Document::ImplicitClose() {
DCHECK(!InStyleRecalc());
DCHECK(parser_);
load_event_progress_ = kLoadEventInProgress;
// We have to clear the parser, in case someone document.write()s from the
// onLoad event handler, as in Radar 3206524.
DetachParser();
// 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 (domWindow())
domWindow()->DocumentWasClosed();
if (GetFrame()) {
GetFrame()->Client()->DispatchDidHandleOnloadEvents();
Loader()->GetApplicationCacheHost()->StopDeferringEvents();
}
if (!GetFrame()) {
load_event_progress_ = kLoadEventCompleted;
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 (GetFrame()->GetNavigationScheduler().LocationChangePending() &&
ElapsedTime() < kCLayoutScheduleThreshold) {
// 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.
load_event_progress_ = kLoadEventCompleted;
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()->GetLayoutObject() &&
!LocalOwner()->GetLayoutObject()->NeedsLayout())) {
UpdateStyleAndLayoutTree();
// Always do a layout after loading if needed.
if (View() && GetLayoutView() &&
(!GetLayoutView()->FirstChild() || GetLayoutView()->NeedsLayout()))
View()->UpdateLayout();
// TODO(bokan): This is a temporary fix to https://crbug.com/788486.
// There's some better cleanups that should be done to follow-up:
// https://crbug.com/795381.
if (View() && goto_anchor_needed_after_stylesheets_load_)
View()->ProcessUrlFragment(url_);
}
load_event_progress_ = kLoadEventCompleted;
if (GetFrame() && GetLayoutView() &&
GetSettings()->GetAccessibilityEnabled()) {
if (AXObjectCache* cache = GetOrCreateAXObjectCache()) {
if (this == &AXObjectCacheOwner())
cache->HandleLoadComplete(this);
else
cache->HandleLayoutComplete(this);
}
}
if (SvgExtensions())
AccessSVGExtensions().StartAnimations();
}
static bool AllDescendantsAreComplete(Frame* frame) {
if (!frame)
return true;
for (Frame* child = frame->Tree().FirstChild(); child;
child = child->Tree().TraverseNext(frame)) {
if (child->IsLoading())
return false;
}
return true;
}
bool Document::ShouldComplete() {
return parsing_state_ == kFinishedParsing && HaveImportsLoaded() &&
!fetcher_->BlockingRequestCount() && !IsDelayingLoadEvent() &&
load_event_progress_ != kLoadEventInProgress &&
AllDescendantsAreComplete(frame_);
}
void Document::CheckCompleted() {
if (!ShouldComplete())
return;
if (frame_) {
frame_->Client()->RunScriptsAtDocumentIdle();
// Injected scripts may have disconnected this frame.
if (!frame_)
return;
// Check again, because runScriptsAtDocumentIdle() may have delayed the load
// event.
if (!ShouldComplete())
return;
}
// OK, completed. Fire load completion events as needed.
SetReadyState(kComplete);
if (LoadEventStillNeeded())
ImplicitClose();
// The readystatechanged or load event may have disconnected this frame.
if (!frame_ || !frame_->IsAttached())
return;
if (frame_->GetSettings()->GetSavePreviousDocumentResources() ==
SavePreviousDocumentResources::kUntilOnLoad) {
fetcher_->ClearResourcesFromPreviousFetcher();
}
frame_->GetNavigationScheduler().StartTimer();
View()->HandleLoadCompleted();
// The document itself is complete, but if a child frame was restarted due to
// an event, this document is still considered to be in progress.
if (!AllDescendantsAreComplete(frame_))
return;
// No need to repeat if we've already notified this load as finished.
if (!Loader()->SentDidFinishLoad()) {
if (frame_->IsMainFrame())
GetViewportDescription().ReportMobilePageStats(frame_);
Loader()->SetSentDidFinishLoad();
frame_->Client()->DispatchDidFinishLoad();
if (!frame_)
return;
}
frame_->Loader().DidFinishNavigation();
}
bool Document::DispatchBeforeUnloadEvent(ChromeClient& chrome_client,
bool is_reload,
bool& did_allow_navigation) {
if (!dom_window_)
return true;
if (!body())
return true;
if (ProcessingBeforeUnload())
return false;
BeforeUnloadEvent* before_unload_event = BeforeUnloadEvent::Create();
before_unload_event->initEvent(EventTypeNames::beforeunload, false, true);
load_event_progress_ = kBeforeUnloadEventInProgress;
const double beforeunload_event_start = CurrentTimeTicksInSeconds();
dom_window_->DispatchEvent(before_unload_event, this);
const double beforeunload_event_end = CurrentTimeTicksInSeconds();
load_event_progress_ = kBeforeUnloadEventCompleted;
DEFINE_STATIC_LOCAL(
CustomCountHistogram, beforeunload_histogram,
("DocumentEventTiming.BeforeUnloadDuration", 0, 10000000, 50));
beforeunload_histogram.Count(
(beforeunload_event_end - beforeunload_event_start) * 1000000.0);
if (!before_unload_event->defaultPrevented())
DefaultEventHandler(before_unload_event);
enum BeforeUnloadDialogHistogramEnum {
kNoDialogNoText,
kNoDialogNoUserGesture,
kNoDialogMultipleConfirmationForNavigation,
kShowDialog,
kDialogEnumMax
};
DEFINE_STATIC_LOCAL(EnumerationHistogram, beforeunload_dialog_histogram,
("Document.BeforeUnloadDialog", kDialogEnumMax));
if (before_unload_event->returnValue().IsNull()) {
beforeunload_dialog_histogram.Count(kNoDialogNoText);
}
if (!GetFrame() || before_unload_event->returnValue().IsNull())
return true;
if (!GetFrame()->HasBeenActivated()) {
beforeunload_dialog_histogram.Count(kNoDialogNoUserGesture);
AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kErrorMessageLevel,
"Blocked attempt to show a 'beforeunload' confirmation panel for a "
"frame that never had a user gesture since its load. "
"https://www.chromestatus.com/feature/5082396709879808"));
return true;
}
if (did_allow_navigation) {
beforeunload_dialog_histogram.Count(
kNoDialogMultipleConfirmationForNavigation);
AddConsoleMessage(ConsoleMessage::Create(
kJSMessageSource, kErrorMessageLevel,
"Blocked attempt to show multiple 'beforeunload' confirmation panels "
"for a single navigation."));
return true;
}
String text = before_unload_event->returnValue();
beforeunload_dialog_histogram.Count(
BeforeUnloadDialogHistogramEnum::kShowDialog);
if (chrome_client.OpenBeforeUnloadConfirmPanel(text, frame_, is_reload)) {
did_allow_navigation = true;
return true;
}
return false;
}
void Document::DispatchUnloadEvents() {
PluginScriptForbiddenScope forbid_plugin_destructor_scripting;
if (parser_)
parser_->StopParsing();
if (load_event_progress_ == kLoadEventNotRun)
return;
if (load_event_progress_ <= kUnloadEventInProgress) {
Element* current_focused_element = FocusedElement();
if (auto* input = ToHTMLInputElementOrNull(current_focused_element))
input->EndEditing();
if (load_event_progress_ < kPageHideInProgress) {
load_event_progress_ = kPageHideInProgress;
if (LocalDOMWindow* window = domWindow()) {
const double pagehide_event_start = CurrentTimeTicksInSeconds();
window->DispatchEvent(
PageTransitionEvent::Create(EventTypeNames::pagehide, false), this);
const double pagehide_event_end = CurrentTimeTicksInSeconds();
DEFINE_STATIC_LOCAL(
CustomCountHistogram, pagehide_histogram,
("DocumentEventTiming.PageHideDuration", 0, 10000000, 50));
pagehide_histogram.Count((pagehide_event_end - pagehide_event_start) *
1000000.0);
}
if (!frame_)
return;
mojom::PageVisibilityState visibility_state = GetPageVisibilityState();
load_event_progress_ = kUnloadVisibilityChangeInProgress;
if (visibility_state != mojom::PageVisibilityState::kHidden) {
// Dispatch visibilitychange event, but don't bother doing
// other notifications as we're about to be unloaded.
const double pagevisibility_hidden_event_start =
CurrentTimeTicksInSeconds();
DispatchEvent(Event::CreateBubble(EventTypeNames::visibilitychange));
const double pagevisibility_hidden_event_end =
CurrentTimeTicksInSeconds();
DEFINE_STATIC_LOCAL(CustomCountHistogram, pagevisibility_histogram,
("DocumentEventTiming.PageVibilityHiddenDuration",
0, 10000000, 50));
pagevisibility_histogram.Count((pagevisibility_hidden_event_end -
pagevisibility_hidden_event_start) *
1000000.0);
DispatchEvent(
Event::CreateBubble(EventTypeNames::webkitvisibilitychange));
}
if (!frame_)
return;
frame_->Loader().SaveScrollAnchor();
DocumentLoader* document_loader =
frame_->Loader().GetProvisionalDocumentLoader();
load_event_progress_ = kUnloadEventInProgress;
Event* unload_event(Event::Create(EventTypeNames::unload));
if (document_loader && !document_loader->GetTiming().UnloadEventStart() &&
!document_loader->GetTiming().UnloadEventEnd()) {
DocumentLoadTiming& timing = document_loader->GetTiming();
DCHECK(timing.NavigationStart());
const double unload_event_start = CurrentTimeTicksInSeconds();
timing.MarkUnloadEventStart(unload_event_start);
frame_->DomWindow()->DispatchEvent(unload_event, this);
const double unload_event_end = CurrentTimeTicksInSeconds();
DEFINE_STATIC_LOCAL(
CustomCountHistogram, unload_histogram,
("DocumentEventTiming.UnloadDuration", 0, 10000000, 50));
unload_histogram.Count((unload_event_end - unload_event_start) *
1000000.0);
timing.MarkUnloadEventEnd(unload_event_end);
} else {
frame_->DomWindow()->DispatchEvent(unload_event, frame_->GetDocument());
}
}
load_event_progress_ = kUnloadEventHandled;
}
if (!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 keep_event_listeners =
frame_->Loader().GetProvisionalDocumentLoader() &&
frame_->ShouldReuseDefaultView(
frame_->Loader().GetProvisionalDocumentLoader()->Url());
if (!keep_event_listeners)
RemoveAllEventListenersRecursively();
}
Document::PageDismissalType Document::PageDismissalEventBeingDispatched()
const {
switch (load_event_progress_) {
case kBeforeUnloadEventInProgress:
return kBeforeUnloadDismissal;
case kPageHideInProgress:
return kPageHideDismissal;
case kUnloadVisibilityChangeInProgress:
return kUnloadVisibilityChangeDismissal;
case kUnloadEventInProgress:
return kUnloadDismissal;
case kLoadEventNotRun:
case kLoadEventInProgress:
case kLoadEventCompleted:
case kBeforeUnloadEventCompleted:
case kUnloadEventHandled:
return kNoDismissal;
}
NOTREACHED();
return kNoDismissal;
}
void Document::SetParsingState(ParsingState parsing_state) {
parsing_state_ = parsing_state;
if (Parsing() && !element_data_cache_)
element_data_cache_ = ElementDataCache::Create();
}
bool Document::ShouldScheduleLayout() const {
// This function will only be called when LocalFrameView 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() - start_time_) * 1000);
}
bool Document::CanCreateHistoryEntry() const {
if (!frame_ || frame_->HasBeenActivated())
return true;
if (ElapsedTime() >= kElapsedTimeForHistoryEntryWithoutUserGestureMS)
return true;
UseCounter::Count(*this, WebFeature::kSuppressHistoryEntryWithoutUserGesture);
// TODO(japhet): This flag controls an intervention to require a user gesture
// or a long time on page in order for a content-initiated navigation to add
// an entry to the back/forward list. Removing the flag and making this the
// default will require updating a couple hundred tests that currently depend
// on creating history entries without user gestures. I'm waiting to update
// the tests until the feature is proven to minimize churn.
// https://bugs.chromium.org/p/chromium/issues/detail?id=638198
if (!GetSettings() || !GetSettings()->GetHistoryEntryRequiresUserGesture())
return true;
return false;
}
void Document::write(const String& text,
Document* entered_document,
ExceptionState& exception_state) {
if (ImportLoader()) {
exception_state.ThrowDOMException(
kInvalidStateError, "Imported document doesn't support write().");
return;
}
if (!IsHTMLDocument()) {
exception_state.ThrowDOMException(kInvalidStateError,
"Only HTML documents support write().");
return;
}
if (throw_on_dynamic_markup_insertion_count_) {
exception_state.ThrowDOMException(
kInvalidStateError,
"Custom Element constructor should not use write().");
return;
}
if (entered_document &&
!GetSecurityOrigin()->IsSameSchemeHostPortAndSuborigin(
entered_document->GetSecurityOrigin())) {
exception_state.ThrowSecurityError(
"Can only call write() on same-origin documents.");
return;
}
NestingLevelIncrementer nesting_level_incrementer(write_recursion_depth_);
write_recursion_is_too_deep_ =
(write_recursion_depth_ > 1) && write_recursion_is_too_deep_;
write_recursion_is_too_deep_ =
(write_recursion_depth_ > kCMaxWriteRecursionDepth) ||
write_recursion_is_too_deep_;
if (write_recursion_is_too_deep_)
return;
bool has_insertion_point = parser_ && parser_->HasInsertionPoint();
if (!has_insertion_point && ignore_destructive_write_count_) {
AddConsoleMessage(
ConsoleMessage::Create(kJSMessageSource, kWarningMessageLevel,
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 (!has_insertion_point)
open(entered_document, ASSERT_NO_EXCEPTION);
DCHECK(parser_);
PerformanceMonitor::ReportGenericViolation(
this, PerformanceMonitor::kDiscouragedAPIUse,
"Avoid using document.write().", 0, nullptr);
probe::breakableLocation(this, "Document.write");
parser_->insert(text);
}
void Document::writeln(const String& text,
Document* entered_document,
ExceptionState& exception_state) {
write(text, entered_document, exception_state);
if (exception_state.HadException())
return;
write("\n", entered_document);
}
void Document::write(LocalDOMWindow* calling_window,
const Vector<String>& text,
ExceptionState& exception_state) {
DCHECK(calling_window);
if (GetSecurityContext().RequireTrustedTypes()) {
DCHECK(RuntimeEnabledFeatures::TrustedDOMTypesEnabled());
exception_state.ThrowTypeError(
"This document can only write `TrustedHTML` objects.");
return;
}
StringBuilder builder;
for (const String& string : text)
builder.Append(string);
write(builder.ToString(), calling_window->document(), exception_state);
}
void Document::writeln(LocalDOMWindow* calling_window,
const Vector<String>& text,
ExceptionState& exception_state) {
DCHECK(calling_window);
if (GetSecurityContext().RequireTrustedTypes()) {
DCHECK(RuntimeEnabledFeatures::TrustedDOMTypesEnabled());
exception_state.ThrowTypeError(
"This document can only write `TrustedHTML` objects.");
return;
}
StringBuilder builder;
for (const String& string : text)
builder.Append(string);
writeln(builder.ToString(), calling_window->document(), exception_state);
}
void Document::write(LocalDOMWindow* calling_window,
TrustedHTML* text,
ExceptionState& exception_state) {
DCHECK(calling_window);
DCHECK(RuntimeEnabledFeatures::TrustedDOMTypesEnabled());
write(text->toString(), calling_window->document(), exception_state);
}
void Document::writeln(LocalDOMWindow* calling_window,
TrustedHTML* text,
ExceptionState& exception_state) {
DCHECK(calling_window);
DCHECK(RuntimeEnabledFeatures::TrustedDOMTypesEnabled());
writeln(text->toString(), calling_window->document(), exception_state);
}
DOMTimerCoordinator* Document::Timers() {
return &timers_;
}
EventTarget* Document::ErrorEventTarget() {
return domWindow();
}
void Document::ExceptionThrown(ErrorEvent* event) {
MainThreadDebugger::Instance()->ExceptionThrown(this, event);
}
KURL Document::urlForBinding() const {
if (!Url().IsNull()) {
return Url();
}
return BlankURL();
}
void Document::SetURL(const KURL& url) {
const KURL& new_url = url.IsEmpty() ? BlankURL() : url;
if (new_url == url_)
return;
url_ = new_url;
access_entry_from_url_ = nullptr;
UpdateBaseURL();
GetContextFeatures().UrlDidChange(this);
// TODO(crbug/795354): Move handling of URL recording out of the renderer.
// URL must only be recorded from the main frame.
if (ukm_recorder_ && IsInMainFrame())
ukm_recorder_->UpdateSourceURL(ukm_source_id_, url_);
}
KURL Document::ValidBaseElementURL() const {
if (base_element_url_.IsValid())
return base_element_url_;
return KURL();
}
void Document::UpdateBaseURL() {
KURL old_base_url = base_url_;
// 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 (!base_element_url_.IsEmpty())
base_url_ = base_element_url_;
else if (!base_url_override_.IsEmpty())
base_url_ = base_url_override_;
else
base_url_ = FallbackBaseURL();
GetSelectorQueryCache().Invalidate();
if (!base_url_.IsValid())
base_url_ = KURL();
if (elem_sheet_) {
// Element sheet is silly. It never contains anything.
DCHECK(!elem_sheet_->Contents()->RuleCount());
elem_sheet_ = CSSStyleSheet::CreateInline(*this, base_url_);
}
if (!EqualIgnoringFragmentIdentifier(old_base_url, base_url_)) {
// 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();
}
}
KURL Document::FallbackBaseURL() const {
if (IsSrcdocDocument()) {
// TODO(tkent): Referring to ParentDocument() is not correct. See
// crbug.com/751329.
if (Document* parent = ParentDocument())
return parent->BaseURL();
} else if (urlForBinding().IsAboutBlankURL()) {
if (context_document_)
return context_document_->BaseURL();
// TODO(tkent): Referring to ParentDocument() is not correct. See
// crbug.com/751329.
if (Document* parent = ParentDocument())
return parent->BaseURL();
}
return urlForBinding();
}
const KURL& Document::BaseURL() const {
if (!base_url_.IsNull())
return base_url_;
return BlankURL();
}
void Document::SetBaseURLOverride(const KURL& url) {
base_url_override_ = url;
UpdateBaseURL();
}
void Document::ProcessBaseElement() {
UseCounter::Count(*this, WebFeature::kBaseElement);
// Find the first href attribute in a base element and the first target
// attribute in a base element.
const AtomicString* href = nullptr;
const AtomicString* target = nullptr;
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 (GetContentSecurityPolicy()->IsActive()) {
UseCounter::Count(*this,
WebFeature::kContentSecurityPolicyWithBaseElement);
}
}
// FIXME: Since this doesn't share code with completeURL it may not handle
// encodings correctly.
KURL base_element_url;
if (href) {
String stripped_href = StripLeadingAndTrailingHTMLSpaces(*href);
if (!stripped_href.IsEmpty())
base_element_url = KURL(FallbackBaseURL(), stripped_href);
}
if (!base_element_url.IsEmpty()) {
if (base_element_url.ProtocolIsData() ||
base_element_url.ProtocolIsJavaScript()) {
UseCounter::Count(*this, WebFeature::kBaseWithDataHref);
AddConsoleMessage(ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel,
"'" + base_element_url.Protocol() +
"' URLs may not be used as base URLs for a document."));
}
if (!GetSecurityOrigin()->CanRequest(base_element_url))
UseCounter::Count(*this, WebFeature::kBaseWithCrossOriginHref);
}
if (base_element_url != base_element_url_ &&
!base_element_url.ProtocolIsData() &&
!base_element_url.ProtocolIsJavaScript() &&
GetContentSecurityPolicy()->AllowBaseURI(base_element_url)) {
base_element_url_ = base_element_url;
UpdateBaseURL();
}
if (target) {
if (target->Contains('\n') || target->Contains('\r'))
UseCounter::Count(*this, WebFeature::kBaseWithNewlinesInTarget);
if (target->Contains('<'))
UseCounter::Count(*this, WebFeature::kBaseWithOpenBracketInTarget);
base_target_ = *target;
} else {
base_target_ = g_null_atom;
}
}
String Document::UserAgent() const {
return GetFrame() ? GetFrame()->Loader().UserAgent() : String();
}
void Document::DisableEval(const String& error_message) {
if (!GetFrame())
return;
GetFrame()->GetScriptController().DisableEval(error_message);
}
void Document::DidLoadAllImports() {
if (!HaveScriptBlockingStylesheetsLoaded())
return;
if (!ImportLoader())
StyleResolverMayHaveChanged();
DidLoadAllScriptBlockingResources();
}
void Document::DidAddPendingStylesheetInBody() {
if (ScriptableDocumentParser* parser = GetScriptableDocumentParser())
parser->DidAddPendingStylesheetInBody();
}
void Document::DidRemoveAllPendingStylesheet() {
StyleResolverMayHaveChanged();
// Only imports on master documents can trigger rendering.
if (HTMLImportLoader* import = ImportLoader())
import->DidRemoveAllPendingStylesheet();
if (!HaveImportsLoaded())
return;
DidLoadAllScriptBlockingResources();
}
void Document::DidRemoveAllPendingBodyStylesheets() {
if (ScriptableDocumentParser* parser = GetScriptableDocumentParser())
parser->DidLoadAllBodyStylesheets();
}
void Document::DidLoadAllScriptBlockingResources() {
// Use wrapWeakPersistent because the task should not keep this Document alive
// just for executing scripts.
execute_scripts_waiting_for_resources_task_handle_ = PostCancellableTask(
*GetTaskRunner(TaskType::kNetworking), FROM_HERE,
WTF::Bind(&Document::ExecuteScriptsWaitingForResources,
WrapWeakPersistent(this)));
if (IsHTMLDocument() && body()) {
// For HTML if we have no more stylesheets to load and we're past the body
// tag, we should have something to paint so resume.
BeginLifecycleUpdatesIfRenderingReady();
} else if (!IsHTMLDocument() && documentElement()) {
// For non-HTML there is no body so resume as soon as the sheets are loaded.
BeginLifecycleUpdatesIfRenderingReady();
}
}
void Document::ExecuteScriptsWaitingForResources() {
if (!IsScriptExecutionReady())
return;
if (ScriptableDocumentParser* parser = GetScriptableDocumentParser())
parser->ExecuteScriptsWaitingForResources();
}
CSSStyleSheet& Document::ElementSheet() {
if (!elem_sheet_)
elem_sheet_ = CSSStyleSheet::CreateInline(*this, base_url_);
return *elem_sheet_;
}
void Document::MaybeHandleHttpRefresh(const String& content,
HttpRefreshType http_refresh_type) {
if (is_view_source_ || !frame_)
return;
double delay;
String refresh_url_string;
if (!ParseHTTPRefresh(content,
http_refresh_type == kHttpRefreshFromMetaTag
? IsHTMLSpace<UChar>
: nullptr,
delay, refresh_url_string))
return;
KURL refresh_url =
refresh_url_string.IsEmpty() ? Url() : CompleteURL(refresh_url_string);
if (refresh_url.ProtocolIsJavaScript()) {
String message =
"Refused to refresh " + url_.ElidedString() + " to a javascript: URL";
AddConsoleMessage(ConsoleMessage::Create(kSecurityMessageSource,
kErrorMessageLevel, message));
return;
}
if (http_refresh_type == kHttpRefreshFromMetaTag &&
IsSandboxed(kSandboxAutomaticFeatures)) {
String message =
"Refused to execute the redirect specified via '<meta "
"http-equiv='refresh' content='...'>'. The document is sandboxed, and "
"the 'allow-scripts' keyword is not set.";
AddConsoleMessage(ConsoleMessage::Create(kSecurityMessageSource,
kErrorMessageLevel, message));
return;
}
frame_->GetNavigationScheduler().ScheduleRedirect(delay, refresh_url,
http_refresh_type);
}
bool Document::ShouldMergeWithLegacyDescription(
ViewportDescription::Type origin) const {
return GetSettings() && GetSettings()->GetViewportMetaMergeContentQuirk() &&
legacy_viewport_description_.IsMetaViewportType() &&
legacy_viewport_description_.type == origin;
}
void Document::SetViewportDescription(
const ViewportDescription& viewport_description) {
if (viewport_description.IsLegacyViewportType()) {
if (viewport_description == legacy_viewport_description_)
return;
legacy_viewport_description_ = viewport_description;
} else {
if (viewport_description == viewport_description_)
return;
viewport_description_ = viewport_description;
// The UA-defined min-width is considered specifically by Android WebView
// quirks mode.
if (!viewport_description.IsSpecifiedByAuthor())
viewport_default_min_width_ = viewport_description.min_width;
}
UpdateViewportDescription();
}
ViewportDescription Document::GetViewportDescription() const {
ViewportDescription applied_viewport_description = viewport_description_;
bool viewport_meta_enabled =
GetSettings() && GetSettings()->GetViewportMetaEnabled();
if (legacy_viewport_description_.type !=
ViewportDescription::kUserAgentStyleSheet &&
viewport_meta_enabled)
applied_viewport_description = legacy_viewport_description_;
if (ShouldOverrideLegacyDescription(viewport_description_.type))
applied_viewport_description = viewport_description_;
return applied_viewport_description;
}
void Document::UpdateViewportDescription() {
if (GetFrame() && GetFrame()->IsMainFrame()) {
GetPage()->GetChromeClient().DispatchViewportPropertiesDidChange(
GetViewportDescription());
}
}
String Document::OutgoingReferrer() const {
if (GetSecurityOrigin()->IsUnique()) {
// Return |no-referrer|.
return String();
}
// See http://www.whatwg.org/specs/web-apps/current-work/#fetching-resources
// for why we walk the parent chain for srcdoc documents.
const Document* referrer_document = this;
if (LocalFrame* frame = frame_) {
while (frame->GetDocument()->IsSrcdocDocument()) {
// Srcdoc documents must be local within the containing frame.
frame = ToLocalFrame(frame->Tree().Parent());
// Srcdoc documents cannot be top-level documents, by definition,
// because they need to be contained in iframes with the srcdoc.
DCHECK(frame);
}
referrer_document = frame->GetDocument();
}
return referrer_document->url_.StrippedForUseAsReferrer();
}
ReferrerPolicy Document::GetReferrerPolicy() const {
ReferrerPolicy policy = ExecutionContext::GetReferrerPolicy();
// For srcdoc documents without their own policy, walk up the frame
// tree to find the document that is either not a srcdoc or doesn't
// have its own policy. This algorithm is defined in
// https://html.spec.whatwg.org/multipage/browsers.html#set-up-a-browsing-context-environment-settings-object.
if (!frame_ || policy != kReferrerPolicyDefault || !IsSrcdocDocument()) {
return policy;
}
LocalFrame* frame = ToLocalFrame(frame_->Tree().Parent());
DCHECK(frame);
return frame->GetDocument()->GetReferrerPolicy();
}
MouseEventWithHitTestResults Document::PerformMouseEventHitTest(
const HitTestRequest& request,
const LayoutPoint& document_point,
const WebMouseEvent& event) {
DCHECK(!GetLayoutView() || GetLayoutView()->IsLayoutView());
// LayoutView::hitTest causes a layout, and we don't want to hit that until
// the first layout because until then, there is nothing shown on the screen -
// the user can't have intentionally clicked on something belonging to this
// page. Furthermore, mousemove events before the first layout should not
// lead to a premature layout() happening, which could show a flash of white.
// See also the similar code in EventHandler::hitTestResultAtPoint.
if (!GetLayoutView() || !View() || !View()->DidFirstLayout())
return MouseEventWithHitTestResults(event,
HitTestResult(request, LayoutPoint()));
HitTestResult result(request, document_point);
GetLayoutView()->HitTest(result);
if (!request.ReadOnly())
UpdateHoverActiveState(request, result.InnerElement());
if (auto* canvas = ToHTMLCanvasElementOrNull(result.InnerNode())) {
HitTestCanvasResult* hit_test_canvas_result =
canvas->GetControlAndIdIfHitRegionExists(
result.PointInInnerNodeFrame());
if (hit_test_canvas_result->GetControl()) {
result.SetInnerNode(hit_test_canvas_result->GetControl());
}
result.SetCanvasRegionId(hit_test_canvas_result->GetId());
}
return MouseEventWithHitTestResults(event, result);
}
// DOM Section 1.1.1
bool Document::ChildTypeAllowed(NodeType type) const {
switch (type) {
case kAttributeNode:
case kCdataSectionNode:
case kDocumentFragmentNode:
case kDocumentNode:
case kTextNode:
return false;
case kCommentNode:
case kProcessingInstructionNode:
return true;
case kDocumentTypeNode:
case kElementNode:
// Documents may contain no more than one of each of these.
// (One Element and one DocumentType.)
for (Node& c : NodeTraversal::ChildrenOf(*this)) {
if (c.getNodeType() == type)
return false;
}
return true;
}
return false;
}
// This is an implementation of step 6 of
// https://dom.spec.whatwg.org/#concept-node-ensure-pre-insertion-validity
// and https://dom.spec.whatwg.org/#concept-node-replace .
//
// 6. If parent is a document, and any of the statements below, switched on
// node, are true, throw a HierarchyRequestError.
// -> DocumentFragment node
// If node has more than one element child or has a Text node child.
// Otherwise, if node has one element child and either parent has an element
// child, child is a doctype, or child is not null and a doctype is
// following child.
// -> element
// parent has an element child, child is a doctype, or child is not null and
// a doctype is following child.
// -> doctype
// parent has a doctype child, child is non-null and an element is preceding
// child, or child is null and parent has an element child.
//
// 6. If parent is a document, and any of the statements below, switched on
// node, are true, throw a HierarchyRequestError.
// -> DocumentFragment node
// If node has more than one element child or has a Text node child.
// Otherwise, if node has one element child and either parent has an element
// child that is not child or a doctype is following child.
// -> element
// parent has an element child that is not child or a doctype is following
// child.
// -> doctype
// parent has a doctype child that is not child, or an element is preceding
// child.
bool Document::CanAcceptChild(const Node& new_child,
const Node* next,
const Node* old_child,
ExceptionState& exception_state) const {
DCHECK(!(next && old_child));
if (old_child && old_child->getNodeType() == new_child.getNodeType())
return true;
int num_doctypes = 0;
int num_elements = 0;
bool has_doctype_after_reference_node = false;
bool has_element_after_reference_node = false;
// First, check how many doctypes and elements we have, not counting
// the child we're about to remove.
bool saw_reference_node = false;
for (Node& child : NodeTraversal::ChildrenOf(*this)) {
if (old_child && *old_child == child) {
saw_reference_node = true;
continue;
}
if (&child == next)
saw_reference_node = true;
switch (child.getNodeType()) {
case kDocumentTypeNode:
num_doctypes++;
has_doctype_after_reference_node = saw_reference_node;
break;
case kElementNode:
num_elements++;
has_element_after_reference_node = saw_reference_node;
break;
default:
break;
}
}
// Then, see how many doctypes and elements might be added by the new child.
if (new_child.IsDocumentFragment()) {
for (Node& child :
NodeTraversal::ChildrenOf(ToDocumentFragment(new_child))) {
switch (child.getNodeType()) {
case kAttributeNode:
case kCdataSectionNode:
case kDocumentFragmentNode:
case kDocumentNode:
case kTextNode:
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Nodes of type '" + new_child.nodeName() +
"' may not be inserted inside nodes of type '#document'.");
return false;
case kCommentNode:
case kProcessingInstructionNode:
break;
case kDocumentTypeNode:
num_doctypes++;
break;
case kElementNode:
num_elements++;
if (has_doctype_after_reference_node) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Can't insert an element before a doctype.");
return false;
}
break;
}
}
} else {
switch (new_child.getNodeType()) {
case kAttributeNode:
case kCdataSectionNode:
case kDocumentFragmentNode:
case kDocumentNode:
case kTextNode:
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Nodes of type '" + new_child.nodeName() +
"' may not be inserted inside nodes of type '#document'.");
return false;
case kCommentNode:
case kProcessingInstructionNode:
return true;
case kDocumentTypeNode:
num_doctypes++;
if (num_elements > 0 && !has_element_after_reference_node) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Can't insert a doctype before the root element.");
return false;
}
break;
case kElementNode:
num_elements++;
if (has_doctype_after_reference_node) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
"Can't insert an element before a doctype.");
return false;
}
break;
}
}
if (num_elements > 1 || num_doctypes > 1) {
exception_state.ThrowDOMException(
kHierarchyRequestError,
String::Format("Only one %s on document allowed.",
num_elements > 1 ? "element" : "doctype"));
return false;
}
return true;
}
Node* Document::cloneNode(bool deep, ExceptionState&) {
Document* clone = CloneDocumentWithoutChildren();
clone->CloneDataFromDocument(*this);
if (deep)
CloneChildNodes(clone);
return clone;
}
Document* Document::CloneDocumentWithoutChildren() {
DocumentInit init = DocumentInit::Create()
.WithContextDocument(ContextDocument())
.WithURL(Url());
if (IsXMLDocument()) {
if (IsXHTMLDocument())
return XMLDocument::CreateXHTML(
init.WithRegistrationContext(RegistrationContext()));
return XMLDocument::Create(init);
}
return Create(init);
}
void Document::CloneDataFromDocument(const Document& other) {
SetCompatibilityMode(other.GetCompatibilityMode());
SetEncodingData(other.encoding_data_);
SetContextFeatures(other.GetContextFeatures());
SetSecurityOrigin(other.GetSecurityOrigin()->IsolatedCopy());
SetMimeType(other.contentType());
}
StyleSheetList& Document::StyleSheets() {
if (!style_sheet_list_)
style_sheet_list_ = StyleSheetList::Create(this);
return *style_sheet_list_;
}
String Document::preferredStylesheetSet() const {
return style_engine_->PreferredStylesheetSetName();
}
String Document::selectedStylesheetSet() const {
UseCounter::Count(*this, WebFeature::kDocumentGetSelectedStylesheetSet);
return style_engine_->SelectedStylesheetSetName();
}
void Document::setSelectedStylesheetSet(const String& a_string) {
UseCounter::Count(*this, WebFeature::kDocumentSetSelectedStylesheetSet);
GetStyleEngine().SetSelectedStylesheetSetName(a_string);
}
void Document::EvaluateMediaQueryListIfNeeded() {
if (!evaluate_media_queries_on_style_recalc_)
return;
EvaluateMediaQueryList();
evaluate_media_queries_on_style_recalc_ = false;
}
void Document::EvaluateMediaQueryList() {
if (media_query_matcher_)
media_query_matcher_->MediaFeaturesChanged();
}
void Document::SetResizedForViewportUnits() {
if (media_query_matcher_)
media_query_matcher_->ViewportChanged();
if (!HasViewportUnits())
return;
EnsureStyleResolver().SetResizedForViewportUnits();
SetNeedsStyleRecalcForViewportUnits();
}
void Document::ClearResizedForViewportUnits() {
EnsureStyleResolver().ClearResizedForViewportUnits();
}
void Document::StyleResolverMayHaveChanged() {
if (HasNodesWithPlaceholderStyle()) {
SetNeedsStyleRecalc(kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kCleanupPlaceholderStyles));
}
if (DidLayoutWithPendingStylesheets() &&
!GetStyleEngine().HasPendingScriptBlockingSheets()) {
// We need to manually repaint because we avoid doing all repaints in layout
// or style recalc while sheets are still loading to avoid FOUC.
pending_sheet_layout_ = kIgnoreLayoutWithPendingSheets;
DCHECK(GetLayoutView() || ImportsController());
if (GetLayoutView())
GetLayoutView()->InvalidatePaintForViewAndCompositedLayers();
}
}
void Document::SetHoverElement(Element* new_hover_element) {
hover_element_ = new_hover_element;
}
void Document::SetActiveElement(Element* new_active_element) {
if (!new_active_element) {
active_element_.Clear();
return;
}
active_element_ = new_active_element;
}
void Document::RemoveFocusedElementOfSubtree(Node* node,
bool among_children_only) {
if (!focused_element_)
return;
// We can't be focused if we're not in the document.
if (!node->isConnected())
return;
bool contains =
node->IsShadowIncludingInclusiveAncestorOf(focused_element_.Get());
if (contains && (focused_element_ != node || !among_children_only))
ClearFocusedElement();
}
static Element* SkipDisplayNoneAncestors(Element* element) {
for (; element; element = FlatTreeTraversal::ParentElement(*element)) {
if (element->GetLayoutObject() || element->HasDisplayContentsStyle())
return element;
}
return nullptr;
}
void Document::HoveredElementDetached(Element& element) {
if (!hover_element_)
return;
if (element != hover_element_)
return;
hover_element_->UpdateDistribution();
hover_element_ = SkipDisplayNoneAncestors(&element);
// If the mouse cursor is not visible, do not clear existing
// hover effects on the ancestors of |element| and do not invoke
// new hover effects on any other element.
if (!GetPage()->IsCursorVisible())
return;
if (GetFrame())
GetFrame()->GetEventHandler().ScheduleHoverStateUpdate();
}
void Document::ActiveChainNodeDetached(Element& element) {
if (element == active_element_)
active_element_ = SkipDisplayNoneAncestors(&element);
}
const Vector<AnnotatedRegionValue>& Document::AnnotatedRegions() const {
return annotated_regions_;
}
void Document::SetAnnotatedRegions(
const Vector<AnnotatedRegionValue>& regions) {
annotated_regions_ = regions;
SetAnnotatedRegionsDirty(false);
}
bool Document::SetFocusedElement(Element* new_focused_element,
const FocusParams& params) {
DCHECK(!lifecycle_.InDetach());
clear_focused_element_timer_.Stop();
// Make sure newFocusedNode is actually in this document
if (new_focused_element && (new_focused_element->GetDocument() != this))
return true;
if (NodeChildRemovalTracker::IsBeingRemoved(new_focused_element))
return true;
if (focused_element_ == new_focused_element)
return true;
bool focus_change_blocked = false;
Element* old_focused_element = focused_element_;
focused_element_ = nullptr;
UpdateDistribution();
Node* ancestor = (old_focused_element && old_focused_element->isConnected() &&
new_focused_element)
? FlatTreeTraversal::CommonAncestor(*old_focused_element,
*new_focused_element)
: nullptr;
// Remove focus from the existing focus node (if any)
if (old_focused_element) {
old_focused_element->SetFocused(false, params.type);
old_focused_element->SetHasFocusWithinUpToAncestor(false, ancestor);
// Dispatch the blur event and let the node do any other blur related
// activities (important for text fields)
// If page lost focus, blur event will have already been dispatched
if (GetPage() && (GetPage()->GetFocusController().IsFocused())) {
old_focused_element->DispatchBlurEvent(new_focused_element, params.type,
params.source_capabilities);
if (focused_element_) {
// handler shifted focus
focus_change_blocked = true;
new_focused_element = nullptr;
}
// 'focusout' is a DOM level 3 name for the bubbling blur event.
old_focused_element->DispatchFocusOutEvent(EventTypeNames::focusout,
new_focused_element,
params.source_capabilities);
// 'DOMFocusOut' is a DOM level 2 name for compatibility.
// FIXME: We should remove firing DOMFocusOutEvent event when we are sure
// no content depends on it, probably when <rdar://problem/8503958> is
// resolved.
old_focused_element->DispatchFocusOutEvent(EventTypeNames::DOMFocusOut,
new_focused_element,
params.source_capabilities);
if (focused_element_) {
// handler shifted focus
focus_change_blocked = true;
new_focused_element = nullptr;
}
}
}
if (new_focused_element)
UpdateStyleAndLayoutTreeForNode(new_focused_element);
if (new_focused_element && new_focused_element->IsFocusable()) {
if (IsRootEditableElement(*new_focused_element) &&
!AcceptsEditingFocus(*new_focused_element)) {
// delegate blocks focus change
focus_change_blocked = true;
goto SetFocusedElementDone;
}
// Set focus on the new node
focused_element_ = new_focused_element;
SetSequentialFocusNavigationStartingPoint(focused_element_.Get());
focused_element_->SetFocused(true, params.type);
focused_element_->SetHasFocusWithinUpToAncestor(true, ancestor);
// Element::setFocused for frames can dispatch events.
if (focused_element_ != new_focused_element) {
focus_change_blocked = true;
goto SetFocusedElementDone;
}
CancelFocusAppearanceUpdate();
focused_element_->UpdateFocusAppearanceWithOptions(
params.selection_behavior, params.options);
// Dispatch the focus event and let the node do any other focus related
// activities (important for text fields)
// If page lost focus, event will be dispatched on page focus, don't
// duplicate
if (GetPage() && (GetPage()->GetFocusController().IsFocused())) {
focused_element_->DispatchFocusEvent(old_focused_element, params.type,
params.source_capabilities);
if (focused_element_ != new_focused_element) {
// handler shifted focus
focus_change_blocked = true;
goto SetFocusedElementDone;
}
// DOM level 3 bubbling focus event.
focused_element_->DispatchFocusInEvent(EventTypeNames::focusin,
old_focused_element, params.type,
params.source_capabilities);
if (focused_element_ != new_focused_element) {
// handler shifted focus
focus_change_blocked = true;
goto SetFocusedElementDone;
}
// For DOM level 2 compatibility.
// FIXME: We should remove firing DOMFocusInEvent event when we are sure
// no content depends on it, probably when <rdar://problem/8503958> is m.
focused_element_->DispatchFocusInEvent(EventTypeNames::DOMFocusIn,
old_focused_element, params.type,
params.source_capabilities);
if (focused_element_ != new_focused_element) {
// handler shifted focus
focus_change_blocked = true;
goto SetFocusedElementDone;
}
}
}
if (!focus_change_blocked && focused_element_) {
// Create the AXObject cache in a focus change because Chromium relies on
// it.
if (AXObjectCache* cache = GetOrCreateAXObjectCache()) {
cache->HandleFocusedUIElementChanged(old_focused_element,
new_focused_element);
}
}
if (!focus_change_blocked && GetPage()) {
GetPage()->GetChromeClient().FocusedNodeChanged(old_focused_element,
focused_element_.Get());
}
SetFocusedElementDone:
UpdateStyleAndLayoutTree();
if (LocalFrame* frame = GetFrame())
frame->Selection().DidChangeFocus();
return !focus_change_blocked;
}
void Document::ClearFocusedElement() {
SetFocusedElement(nullptr, FocusParams(SelectionBehaviorOnFocus::kNone,
kWebFocusTypeNone, nullptr));
}
void Document::SetSequentialFocusNavigationStartingPoint(Node* node) {
if (!frame_)
return;
if (!node) {
sequential_focus_navigation_starting_point_ = nullptr;
return;
}
DCHECK_EQ(node->GetDocument(), this);
if (!sequential_focus_navigation_starting_point_)
sequential_focus_navigation_starting_point_ = Range::Create(*this);
sequential_focus_navigation_starting_point_->selectNodeContents(
node, ASSERT_NO_EXCEPTION);
}
Element* Document::SequentialFocusNavigationStartingPoint(
WebFocusType type) const {
if (focused_element_)
return focused_element_.Get();
if (!sequential_focus_navigation_starting_point_)
return nullptr;
if (!sequential_focus_navigation_starting_point_->collapsed()) {
Node* node = sequential_focus_navigation_starting_point_->startContainer();
DCHECK_EQ(node,
sequential_focus_navigation_starting_point_->endContainer());
if (node->IsElementNode())
return ToElement(node);
if (Element* neighbor_element = type == kWebFocusTypeForward
? ElementTraversal::Previous(*node)
: ElementTraversal::Next(*node))
return neighbor_element;
return node->ParentOrShadowHostElement();
}
// Range::selectNodeContents didn't select contents because the element had
// no children.
if (sequential_focus_navigation_starting_point_->startContainer()
->IsElementNode() &&
!sequential_focus_navigation_starting_point_->startContainer()
->hasChildren() &&
sequential_focus_navigation_starting_point_->startOffset() == 0)
return ToElement(
sequential_focus_navigation_starting_point_->startContainer());
// A node selected by Range::selectNodeContents was removed from the
// document tree.
if (Node* next_node =
sequential_focus_navigation_starting_point_->FirstNode()) {
if (type == kWebFocusTypeForward)
return ElementTraversal::Previous(*next_node);
if (next_node->IsElementNode())
return ToElement(next_node);
return ElementTraversal::Next(*next_node);
}
return nullptr;
}
void Document::SetCSSTarget(Element* new_target) {
if (css_target_)
css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget);
css_target_ = new_target;
if (css_target_)
css_target_->PseudoStateChanged(CSSSelector::kPseudoTarget);
}
static void LiveNodeListBaseWriteBarrier(void* parent,
const LiveNodeListBase* list) {
if (IsHTMLCollectionType(list->GetType())) {
ScriptWrappableVisitor::WriteBarrier(
static_cast<const HTMLCollection*>(list));
} else {
ScriptWrappableVisitor::WriteBarrier(
static_cast<const LiveNodeList*>(list));
}
}
void Document::RegisterNodeList(const LiveNodeListBase* list) {
node_lists_.Add(list, list->InvalidationType());
LiveNodeListBaseWriteBarrier(this, list);
if (list->IsRootedAtTreeScope())
lists_invalidated_at_document_.insert(list);
}
void Document::UnregisterNodeList(const LiveNodeListBase* list) {
node_lists_.Remove(list, list->InvalidationType());
if (list->IsRootedAtTreeScope()) {
DCHECK(lists_invalidated_at_document_.Contains(list));
lists_invalidated_at_document_.erase(list);
}
}
void Document::RegisterNodeListWithIdNameCache(const LiveNodeListBase* list) {
node_lists_.Add(list, kInvalidateOnIdNameAttrChange);
LiveNodeListBaseWriteBarrier(this, list);
}
void Document::UnregisterNodeListWithIdNameCache(const LiveNodeListBase* list) {
node_lists_.Remove(list, kInvalidateOnIdNameAttrChange);
}
void Document::AttachNodeIterator(NodeIterator* ni) {
node_iterators_.insert(ni);
}
void Document::DetachNodeIterator(NodeIterator* ni) {
// The node iterator can be detached without having been attached if its root
// node didn't have a document when the iterator was created, but has it now.
node_iterators_.erase(ni);
}
void Document::MoveNodeIteratorsToNewDocument(Node& node,
Document& new_document) {
HeapHashSet<WeakMember<NodeIterator>> node_iterators_list = node_iterators_;
for (NodeIterator* ni : node_iterators_list) {
if (ni->root() == node) {
DetachNodeIterator(ni);
new_document.AttachNodeIterator(ni);
}
}
}
void Document::DidMoveTreeToNewDocument(const Node& root) {
DCHECK_NE(root.GetDocument(), this);
if (!ranges_.IsEmpty()) {
AttachedRangeSet ranges = ranges_;
for (Range* range : ranges)
range->UpdateOwnerDocumentIfNeeded();
}
NotifyMoveTreeToNewDocument(root);
}
void Document::NodeChildrenWillBeRemoved(ContainerNode& container) {
EventDispatchForbiddenScope assert_no_event_dispatch;
for (Range* range : ranges_)
range->NodeChildrenWillBeRemoved(container);
for (NodeIterator* ni : node_iterators_) {
for (Node& n : NodeTraversal::ChildrenOf(container))
ni->NodeWillBeRemoved(n);
}
NotifyNodeChildrenWillBeRemoved(container);
if (ContainsV1ShadowTree()) {
for (Node& n : NodeTraversal::ChildrenOf(container))
n.CheckSlotChangeBeforeRemoved();
}
}
void Document::NodeWillBeRemoved(Node& n) {
for (NodeIterator* ni : node_iterators_)
ni->NodeWillBeRemoved(n);
for (Range* range : ranges_)
range->NodeWillBeRemoved(n);
NotifyNodeWillBeRemoved(n);
if (ContainsV1ShadowTree())
n.CheckSlotChangeBeforeRemoved();
if (n.InActiveDocument())
GetStyleEngine().NodeWillBeRemoved(n);
}
void Document::DidInsertText(const CharacterData& text,
unsigned offset,
unsigned length) {
for (Range* range : ranges_)
range->DidInsertText(text, offset, length);
}
void Document::DidRemoveText(const CharacterData& text,
unsigned offset,
unsigned length) {
for (Range* range : ranges_)
range->DidRemoveText(text, offset, length);
}
void Document::DidMergeTextNodes(const Text& merged_node,
const Text& node_to_be_removed,
unsigned old_length) {
NodeWithIndex node_to_be_removed_with_index(
const_cast<Text&>(node_to_be_removed));
if (!ranges_.IsEmpty()) {
for (Range* range : ranges_)
range->DidMergeTextNodes(node_to_be_removed_with_index, old_length);
}
NotifyMergeTextNodes(merged_node, node_to_be_removed_with_index, old_length);
// FIXME: This should update markers for spelling and grammar checking.
}
void Document::DidSplitTextNode(const Text& old_node) {
for (Range* range : ranges_)
range->DidSplitTextNode(old_node);
NotifySplitTextNode(old_node);
// FIXME: This should update markers for spelling and grammar checking.
}
void Document::SetWindowAttributeEventListener(const AtomicString& event_type,
EventListener* listener) {
LocalDOMWindow* dom_window = domWindow();
if (!dom_window)
return;
dom_window->SetAttributeEventListener(event_type, listener);
}
EventListener* Document::GetWindowAttributeEventListener(
const AtomicString& event_type) {
LocalDOMWindow* dom_window = domWindow();
if (!dom_window)
return nullptr;
return dom_window->GetAttributeEventListener(event_type);
}
EventQueue* Document::GetEventQueue() const {
if (!dom_window_)
return nullptr;
return dom_window_->GetEventQueue();
}
void Document::EnqueueAnimationFrameTask(base::OnceClosure task) {
EnsureScriptedAnimationController().EnqueueTask(std::move(task));
}
void Document::EnqueueAnimationFrameEvent(Event* event) {
EnsureScriptedAnimationController().EnqueueEvent(event);
}
void Document::EnqueueUniqueAnimationFrameEvent(Event* event) {
EnsureScriptedAnimationController().EnqueuePerFrameEvent(event);
}
void Document::EnqueueScrollEventForNode(Node* target) {
// Per the W3C CSSOM View Module only scroll events fired at the document
// should bubble.
Event* scroll_event = target->IsDocumentNode()
? Event::CreateBubble(EventTypeNames::scroll)
: Event::Create(EventTypeNames::scroll);
scroll_event->SetTarget(target);
EnsureScriptedAnimationController().EnqueuePerFrameEvent(scroll_event);
}
void Document::EnqueueResizeEvent() {
Event* event = Event::Create(EventTypeNames::resize);
event->SetTarget(domWindow());
EnsureScriptedAnimationController().EnqueuePerFrameEvent(event);
}
void Document::EnqueueMediaQueryChangeListeners(
HeapVector<Member<MediaQueryListListener>>& listeners) {
EnsureScriptedAnimationController().EnqueueMediaQueryChangeListeners(
listeners);
}
void Document::EnqueueVisualViewportScrollEvent() {
VisualViewportScrollEvent* event = VisualViewportScrollEvent::Create();
event->SetTarget(domWindow()->visualViewport());
EnsureScriptedAnimationController().EnqueuePerFrameEvent(event);
}
void Document::EnqueueVisualViewportResizeEvent() {
VisualViewportResizeEvent* event = VisualViewportResizeEvent::Create();
event->SetTarget(domWindow()->visualViewport());
EnsureScriptedAnimationController().EnqueuePerFrameEvent(event);
}
void Document::DispatchEventsForPrinting() {
if (!scripted_animation_controller_)
return;
scripted_animation_controller_->DispatchEventsAndCallbacksForPrinting();
}
Document::EventFactorySet& Document::EventFactories() {
DEFINE_STATIC_LOCAL(EventFactorySet, event_factory, ());
return event_factory;
}
const OriginAccessEntry& Document::AccessEntryFromURL() {
if (!access_entry_from_url_) {
access_entry_from_url_ = WTF::WrapUnique(
new OriginAccessEntry(Url().Protocol(), Url().Host(),
OriginAccessEntry::kAllowRegisterableDomains));
}
return *access_entry_from_url_;
}
void Document::SendSensitiveInputVisibility() {
if (sensitive_input_visibility_task_.IsActive())
return;
sensitive_input_visibility_task_ = PostCancellableTask(
*GetTaskRunner(TaskType::kUnspecedLoading), FROM_HERE,
WTF::Bind(&Document::SendSensitiveInputVisibilityInternal,
WrapWeakPersistent(this)));
}
void Document::SendSensitiveInputVisibilityInternal() {
if (!GetFrame())
return;
mojom::blink::InsecureInputServicePtr insecure_input_service_ptr;
GetFrame()->GetInterfaceProvider().GetInterface(
mojo::MakeRequest(&insecure_input_service_ptr));
if (password_count_ > 0) {
insecure_input_service_ptr->PasswordFieldVisibleInInsecureContext();
return;
}
insecure_input_service_ptr->AllPasswordFieldsInInsecureContextInvisible();
}
void Document::SendDidEditFieldInInsecureContext() {
if (!GetFrame())
return;
mojom::blink::InsecureInputServicePtr insecure_input_service_ptr;
GetFrame()->GetInterfaceProvider().GetInterface(
mojo::MakeRequest(&insecure_input_service_ptr));
insecure_input_service_ptr->DidEditFieldInInsecureContext();
}
void Document::RegisterEventFactory(
std::unique_ptr<EventFactoryBase> event_factory) {
DCHECK(!EventFactories().Contains(event_factory.get()));
EventFactories().insert(std::move(event_factory));
}
Event* Document::createEvent(ScriptState* script_state,
const String& event_type,
ExceptionState& exception_state) {
Event* event = nullptr;
ExecutionContext* execution_context = ExecutionContext::From(script_state);
for (const auto& factory : EventFactories()) {
event = factory->Create(execution_context, event_type);
if (event) {
// createEvent for TouchEvent should throw DOM exception if touch event
// feature detection is not enabled. See crbug.com/392584#c22
if (DeprecatedEqualIgnoringCase(event_type, "TouchEvent") &&
!OriginTrials::touchEventFeatureDetectionEnabled(execution_context))
break;
return event;
}
}
exception_state.ThrowDOMException(
kNotSupportedError,
"The provided event type ('" + event_type + "') is invalid.");
return nullptr;
}
void Document::AddMutationEventListenerTypeIfEnabled(
ListenerType listener_type) {
if (ContextFeatures::MutationEventsEnabled(this))
AddListenerType(listener_type);
}
void Document::AddListenerTypeIfNeeded(const AtomicString& event_type,
EventTarget& event_target) {
if (event_type == EventTypeNames::DOMSubtreeModified) {
UseCounter::Count(*this, WebFeature::kDOMSubtreeModifiedEvent);
AddMutationEventListenerTypeIfEnabled(kDOMSubtreeModifiedListener);
} else if (event_type == EventTypeNames::DOMNodeInserted) {
UseCounter::Count(*this, WebFeature::kDOMNodeInsertedEvent);
AddMutationEventListenerTypeIfEnabled(kDOMNodeInsertedListener);
} else if (event_type == EventTypeNames::DOMNodeRemoved) {
UseCounter::Count(*this, WebFeature::kDOMNodeRemovedEvent);
AddMutationEventListenerTypeIfEnabled(kDOMNodeRemovedListener);
} else if (event_type == EventTypeNames::DOMNodeRemovedFromDocument) {
UseCounter::Count(*this, WebFeature::kDOMNodeRemovedFromDocumentEvent);
AddMutationEventListenerTypeIfEnabled(kDOMNodeRemovedFromDocumentListener);
} else if (event_type == EventTypeNames::DOMNodeInsertedIntoDocument) {
UseCounter::Count(*this, WebFeature::kDOMNodeInsertedIntoDocumentEvent);
AddMutationEventListenerTypeIfEnabled(kDOMNodeInsertedIntoDocumentListener);
} else if (event_type == EventTypeNames::DOMCharacterDataModified) {
UseCounter::Count(*this, WebFeature::kDOMCharacterDataModifiedEvent);
AddMutationEventListenerTypeIfEnabled(kDOMCharacterDataModifiedListener);
} else if (event_type == EventTypeNames::webkitAnimationStart ||
event_type == EventTypeNames::animationstart) {
AddListenerType(kAnimationStartListener);
} else if (event_type == EventTypeNames::webkitAnimationEnd ||
event_type == EventTypeNames::animationend) {
AddListenerType(kAnimationEndListener);
} else if (event_type == EventTypeNames::webkitAnimationIteration ||
event_type == EventTypeNames::animationiteration) {
AddListenerType(kAnimationIterationListener);
if (View()) {
// Need to re-evaluate time-to-effect-change for any running animations.
View()->ScheduleAnimation();
}
} else if (event_type == EventTypeNames::webkitTransitionEnd ||
event_type == EventTypeNames::transitionend) {
AddListenerType(kTransitionEndListener);
} else if (event_type == EventTypeNames::scroll) {
AddListenerType(kScrollListener);
} else if (event_type == EventTypeNames::load) {
if (Node* node = event_target.ToNode()) {
if (IsHTMLStyleElement(*node)) {
AddListenerType(kLoadListenerAtCapturePhaseOrAtStyleElement);
return;
}
}
if (event_target.HasCapturingEventListeners(event_type))
AddListenerType(kLoadListenerAtCapturePhaseOrAtStyleElement);
}
}
HTMLFrameOwnerElement* Document::LocalOwner() const {
if (!GetFrame())
return nullptr;
// FIXME: This probably breaks the attempts to layout after a load is finished
// in implicitClose(), and probably tons of other things...
return GetFrame()->DeprecatedLocalOwner();
}
void Document::WillChangeFrameOwnerProperties(int margin_width,
int margin_height,
ScrollbarMode scrolling_mode,
bool is_display_none) {
DCHECK(GetFrame() && GetFrame()->Owner());
FrameOwner* owner = GetFrame()->Owner();
if (RuntimeEnabledFeatures::DisplayNoneIFrameCreatesNoLayoutObjectEnabled()) {
if (documentElement()) {
if (is_display_none != owner->IsDisplayNone())
documentElement()->LazyReattachIfAttached();
}
}
if (!body())
return;
if (margin_width != owner->MarginWidth())
body()->SetIntegralAttribute(marginwidthAttr, margin_width);
if (margin_height != owner->MarginHeight())
body()->SetIntegralAttribute(marginheightAttr, margin_height);
if (scrolling_mode != owner->ScrollingMode() && View())
View()->SetNeedsLayout();
}
bool Document::IsInInvisibleSubframe() const {
if (!LocalOwner())
return false; // this is a local root element
// TODO(bokan): This looks like it doesn't work in OOPIF.
DCHECK(GetFrame());
return !GetFrame()->OwnerLayoutObject();
}
String Document::cookie(ExceptionState& exception_state) const {
if (GetSettings() && !GetSettings()->GetCookieEnabled())
return String();
UseCounter::Count(*this, WebFeature::kCookieGet);
// FIXME: The HTML5 DOM spec states that this attribute can raise an
// InvalidStateError exception on getting if the Document has no
// browsing context.
if (!GetSecurityOrigin()->CanAccessCookies()) {
if (IsSandboxed(kSandboxOrigin))
exception_state.ThrowSecurityError(
"The document is sandboxed and lacks the 'allow-same-origin' flag.");
else if (Url().ProtocolIs("data"))
exception_state.ThrowSecurityError(
"Cookies are disabled inside 'data:' URLs.");
else
exception_state.ThrowSecurityError("Access is denied for this document.");
return String();
} else if (GetSecurityOrigin()->IsLocal()) {
UseCounter::Count(*this, WebFeature::kFileAccessedCookies);
}
// Suborigins are cookie-averse and thus should always return the empty
// string, unless the 'unsafe-cookies' option is provided.
if (GetSecurityOrigin()->HasSuborigin() &&
!GetSecurityOrigin()->GetSuborigin()->PolicyContains(
Suborigin::SuboriginPolicyOptions::kUnsafeCookies))
return String();
KURL cookie_url = CookieURL();
if (cookie_url.IsEmpty())
return String();
return Cookies(this, cookie_url);
}
void Document::setCookie(const String& value, ExceptionState& exception_state) {
if (GetSettings() && !GetSettings()->GetCookieEnabled())
return;
UseCounter::Count(*this, WebFeature::kCookieSet);
// FIXME: The HTML5 DOM spec states that this attribute can raise an
// InvalidStateError exception on setting if the Document has no
// browsing context.
if (!GetSecurityOrigin()->CanAccessCookies()) {
if (IsSandboxed(kSandboxOrigin))
exception_state.ThrowSecurityError(
"The document is sandboxed and lacks the 'allow-same-origin' flag.");
else if (Url().ProtocolIs("data"))
exception_state.ThrowSecurityError(
"Cookies are disabled inside 'data:' URLs.");
else
exception_state.ThrowSecurityError("Access is denied for this document.");
return;
} else if (GetSecurityOrigin()->IsLocal()) {
UseCounter::Count(*this, WebFeature::kFileAccessedCookies);
}
// Suborigins are cookie-averse and thus setting should be a no-op, unless
// the 'unsafe-cookies' option is provided.
if (GetSecurityOrigin()->HasSuborigin() &&
!GetSecurityOrigin()->GetSuborigin()->PolicyContains(
Suborigin::SuboriginPolicyOptions::kUnsafeCookies))
return;
KURL cookie_url = CookieURL();
if (cookie_url.IsEmpty())
return;
SetCookies(this, cookie_url, value);
}
const AtomicString& Document::referrer() const {
if (Loader())
return Loader()->GetRequest().HttpReferrer();
return g_null_atom;
}
String Document::domain() const {
return GetSecurityOrigin()->Domain();
}
void Document::setDomain(const String& raw_domain,
ExceptionState& exception_state) {
UseCounter::Count(*this, WebFeature::kDocumentSetDomain);
if (IsSandboxed(kSandboxDocumentDomain)) {
exception_state.ThrowSecurityError(
"Assignment is forbidden for sandboxed iframes.");
return;
}
if (SchemeRegistry::IsDomainRelaxationForbiddenForURLScheme(
GetSecurityOrigin()->Protocol())) {
exception_state.ThrowSecurityError("Assignment is forbidden for the '" +
GetSecurityOrigin()->Protocol() +
"' scheme.");
return;
}
bool success = false;
String new_domain = SecurityOrigin::CanonicalizeHost(raw_domain, &success);
if (!success) {
exception_state.ThrowSecurityError("'" + raw_domain +
"' could not be parsed properly.");
return;
}
if (new_domain.IsEmpty()) {
exception_state.ThrowSecurityError("'" + new_domain +
"' is an empty domain.");
return;
}
// TODO(mkwst): If we decide to ship this, change the IDL file to make the
// value nullable (via `TreatNullAs=NullString`, for example). For the moment,
// just rely on JavaScript's inherent nuttiness for implicit conversion to the
// string "null". https://crbug.com/733150
if (!RuntimeEnabledFeatures::NullableDocumentDomainEnabled() ||
new_domain != "null") {
OriginAccessEntry access_entry(GetSecurityOrigin()->Protocol(), new_domain,
OriginAccessEntry::kAllowSubdomains);
OriginAccessEntry::MatchResult result =
access_entry.MatchesOrigin(*GetSecurityOrigin());
if (result == OriginAccessEntry::kDoesNotMatchOrigin) {
exception_state.ThrowSecurityError(
"'" + new_domain + "' is not a suffix of '" + domain() + "'.");
return;
}
if (result == OriginAccessEntry::kMatchesOriginButIsPublicSuffix) {
exception_state.ThrowSecurityError("'" + new_domain +
"' is a top-level domain.");
return;
}
}
if (frame_) {
UseCounter::Count(*this,
GetSecurityOrigin()->Port() == 0
? WebFeature::kDocumentDomainSetWithDefaultPort
: WebFeature::kDocumentDomainSetWithNonDefaultPort);
bool was_cross_domain = frame_->IsCrossOriginSubframe();
GetMutableSecurityOrigin()->SetDomainFromDOM(new_domain);
if (View() && (was_cross_domain != frame_->IsCrossOriginSubframe()))
View()->CrossOriginStatusChanged();
frame_->GetScriptController().UpdateSecurityOrigin(GetSecurityOrigin());
}
}
// http://www.whatwg.org/specs/web-apps/current-work/#dom-document-lastmodified
String Document::lastModified() const {
DateComponents date;
bool found_date = false;
if (frame_) {
if (DocumentLoader* document_loader = Loader()) {
const AtomicString& http_last_modified =
document_loader->GetResponse().HttpHeaderField(
HTTPNames::Last_Modified);
if (!http_last_modified.IsEmpty()) {
double date_value = ParseDate(http_last_modified);
if (!std::isnan(date_value)) {
date.SetMillisecondsSinceEpochForDateTime(
ConvertToLocalTime(date_value));
found_date = true;
}
}
}
}
// FIXME: If this document came from the file system, the HTML5
// specificiation tells us to read the last modification date from the file
// system.
if (!found_date)
date.SetMillisecondsSinceEpochForDateTime(
ConvertToLocalTime(CurrentTimeMS()));
return String::Format("%02d/%02d/%04d %02d:%02d:%02d", date.Month() + 1,
date.MonthDay(), date.FullYear(), date.Hour(),
date.Minute(), date.Second());
}
const KURL Document::SiteForCookies() const {
// TODO(mkwst): This doesn't properly handle HTML Import documents.
// If this is an imported document, grab its master document's first-party:
if (IsHTMLImport())
return ImportsController()->Master()->SiteForCookies();
if (!GetFrame())
return SecurityOrigin::UrlWithUniqueSecurityOrigin();
// TODO(mkwst): This doesn't correctly handle sandboxed documents; we want to
// look at their URL, but we can't because we don't know what it is.
Frame& top = GetFrame()->Tree().Top();
KURL top_document_url;
if (top.IsLocalFrame()) {
top_document_url = ToLocalFrame(top).GetDocument()->Url();
} else {
const SecurityOrigin* origin =
top.GetSecurityContext()->GetSecurityOrigin();
// TODO(yhirano): Ideally |origin| should not be null here.
if (origin)
top_document_url = KURL(NullURL(), origin->ToString());
else
top_document_url = SecurityOrigin::UrlWithUniqueSecurityOrigin();
}
if (SchemeRegistry::ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
top_document_url.Protocol()))
return top_document_url;
// We're intentionally using the URL of each document rather than the
// document's SecurityOrigin. Sandboxing a document into a unique origin
// shouldn't effect first-/third-party status for cookies and site data.
const OriginAccessEntry& access_entry =
top.IsLocalFrame()
? ToLocalFrame(top).GetDocument()->AccessEntryFromURL()
: OriginAccessEntry(top_document_url.Protocol(),
top_document_url.Host(),
OriginAccessEntry::kAllowRegisterableDomains);
const Frame* current_frame = GetFrame();
while (current_frame) {
// Skip over srcdoc documents, as they are always same-origin with their
// closest non-srcdoc parent.
while (current_frame->IsLocalFrame() &&
ToLocalFrame(current_frame)->GetDocument()->IsSrcdocDocument())
current_frame = current_frame->Tree().Parent();
DCHECK(current_frame);
// We use 'matchesDomain' here, as it turns out that some folks embed HTTPS
// login forms
// into HTTP pages; we should allow this kind of upgrade.
if (access_entry.MatchesDomain(
*current_frame->GetSecurityContext()->GetSecurityOrigin()) ==
OriginAccessEntry::kDoesNotMatchOrigin)
return SecurityOrigin::UrlWithUniqueSecurityOrigin();
current_frame = current_frame->Tree().Parent();
}
return top_document_url;
}
static bool IsValidNameNonASCII(const LChar* characters, unsigned length) {
if (!IsValidNameStart(characters[0]))
return false;
for (unsigned i = 1; i < length; ++i) {
if (!IsValidNamePart(characters[i]))
return false;
}
return true;
}
static bool IsValidNameNonASCII(const UChar* characters, unsigned length) {
for (unsigned i = 0; i < length;) {
bool first = i == 0;
UChar32 c;
U16_NEXT(characters, i, length, c); // Increments i.
if (first ? !IsValidNameStart(c) : !IsValidNamePart(c))
return false;
}
return true;
}
template <typename CharType>
static inline bool IsValidNameASCII(const CharType* characters,
unsigned length) {
CharType c = characters[0];
if (!(IsASCIIAlpha(c) || c == ':' || c == '_'))
return false;
for (unsigned i = 1; i < length; ++i) {
c = characters[i];
if (!(IsASCIIAlphanumeric(c) || c == ':' || c == '_' || c == '-' ||
c == '.'))
return false;
}
return true;
}
bool Document::IsValidName(const String& name) {
unsigned length = name.length();
if (!length)
return false;
if (name.Is8Bit()) {
const LChar* characters = name.Characters8();
if (IsValidNameASCII(characters, length))
return true;
return IsValidNameNonASCII(characters, length);
}
const UChar* characters = name.Characters16();
if (IsValidNameASCII(characters, length))
return true;
return IsValidNameNonASCII(characters, length);
}
enum QualifiedNameStatus {
kQNValid,
kQNMultipleColons,
kQNInvalidStartChar,
kQNInvalidChar,
kQNEmptyPrefix,
kQNEmptyLocalName
};
struct ParseQualifiedNameResult {
QualifiedNameStatus status;
UChar32 character;
ParseQualifiedNameResult() = default;
explicit ParseQualifiedNameResult(QualifiedNameStatus status)
: status(status) {}
ParseQualifiedNameResult(QualifiedNameStatus status, UChar32 character)
: status(status), character(character) {}
};
template <typename CharType>
static ParseQualifiedNameResult ParseQualifiedNameInternal(
const AtomicString& qualified_name,
const CharType* characters,
unsigned length,
AtomicString& prefix,
AtomicString& local_name) {
bool name_start = true;
bool saw_colon = false;
int colon_pos = 0;
for (unsigned i = 0; i < length;) {
UChar32 c;
U16_NEXT(characters, i, length, c)
if (c == ':') {
if (saw_colon)
return ParseQualifiedNameResult(kQNMultipleColons);
name_start = true;
saw_colon = true;
colon_pos = i - 1;
} else if (name_start) {
if (!IsValidNameStart(c))
return ParseQualifiedNameResult(kQNInvalidStartChar, c);
name_start = false;
} else {
if (!IsValidNamePart(c))
return ParseQualifiedNameResult(kQNInvalidChar, c);
}
}
if (!saw_colon) {
prefix = g_null_atom;
local_name = qualified_name;
} else {
prefix = AtomicString(characters, colon_pos);
if (prefix.IsEmpty())
return ParseQualifiedNameResult(kQNEmptyPrefix);
int prefix_start = colon_pos + 1;
local_name = AtomicString(characters + prefix_start, length - prefix_start);
}
if (local_name.IsEmpty())
return ParseQualifiedNameResult(kQNEmptyLocalName);
return ParseQualifiedNameResult(kQNValid);
}
bool Document::ParseQualifiedName(const AtomicString& qualified_name,
AtomicString& prefix,
AtomicString& local_name,
ExceptionState& exception_state) {
unsigned length = qualified_name.length();
if (!length) {
exception_state.ThrowDOMException(kInvalidCharacterError,
"The qualified name provided is empty.");
return false;
}
ParseQualifiedNameResult return_value;
if (qualified_name.Is8Bit())
return_value =
ParseQualifiedNameInternal(qualified_name, qualified_name.Characters8(),
length, prefix, local_name);
else
return_value = ParseQualifiedNameInternal(qualified_name,
qualified_name.Characters16(),
length, prefix, local_name);
if (return_value.status == kQNValid)
return true;
StringBuilder message;
message.Append("The qualified name provided ('");
message.Append(qualified_name);
message.Append("') ");
if (return_value.status == kQNMultipleColons) {
message.Append("contains multiple colons.");
} else if (return_value.status == kQNInvalidStartChar) {
message.Append("contains the invalid name-start character '");
message.Append(return_value.character);
message.Append("'.");
} else if (return_value.status == kQNInvalidChar) {
message.Append("contains the invalid character '");
message.Append(return_value.character);
message.Append("'.");
} else if (return_value.status == kQNEmptyPrefix) {
message.Append("has an empty namespace prefix.");
} else {
DCHECK_EQ(return_value.status, kQNEmptyLocalName);
message.Append("has an empty local name.");
}
exception_state.ThrowDOMException(kInvalidCharacterError, message.ToString());
return false;
}
void Document::SetEncodingData(const DocumentEncodingData& new_data) {
// It's possible for the encoding of the document to change while we're
// decoding data. That can only occur while we're processing the <head>
// portion of the document. There isn't much user-visible content in the
// <head>, but there is the <title> element. This function detects that
// situation and re-decodes the document's title so that the user doesn't see
// an incorrectly decoded title in the title bar.
if (title_element_ && Encoding() != new_data.Encoding() &&
!ElementTraversal::FirstWithin(*title_element_) &&
Encoding() == Latin1Encoding() &&
title_element_->textContent().ContainsOnlyLatin1()) {
CString original_bytes = title_element_->textContent().Latin1();
std::unique_ptr<TextCodec> codec = NewTextCodec(new_data.Encoding());
String correctly_decoded_title = codec->Decode(
original_bytes.data(), original_bytes.length(), WTF::kDataEOF);
title_element_->setTextContent(correctly_decoded_title);
}
DCHECK(new_data.Encoding().IsValid());
encoding_data_ = new_data;
// FIXME: Should be removed as part of
// https://code.google.com/p/chromium/issues/detail?id=319643
bool should_use_visual_ordering =
encoding_data_.Encoding().UsesVisualOrdering();
if (should_use_visual_ordering != visually_ordered_) {
visually_ordered_ = should_use_visual_ordering;
// FIXME: How is possible to not have a layoutObject here?
if (GetLayoutView()) {
GetLayoutView()->MutableStyleRef().SetRtlOrdering(
visually_ordered_ ? EOrder::kVisual : EOrder::kLogical);
}
SetNeedsStyleRecalc(kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(
StyleChangeReason::kVisuallyOrdered));
}
}
KURL Document::CompleteURL(const String& url) const {
return CompleteURLWithOverride(url, base_url_);
}
KURL Document::CompleteURLWithOverride(const String& url,
const KURL& base_url_override) const {
DCHECK(base_url_override.IsEmpty() || base_url_override.IsValid());
// Always return a null URL when passed a null string.
// FIXME: Should we change the KURL constructor to have this behavior?
// See also [CSS]StyleSheet::completeURL(const String&)
if (url.IsNull())
return KURL();
if (!Encoding().IsValid())
return KURL(base_url_override, url);
return KURL(base_url_override, url, Encoding());
}
// static
bool Document::ShouldInheritSecurityOriginFromOwner(const KURL& url) {
// https://html.spec.whatwg.org/multipage/browsers.html#origin
//
// If a Document is the initial "about:blank" document The origin and
// effective script origin of the Document are those 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();
}
KURL Document::OpenSearchDescriptionURL() {
static const char kOpenSearchMIMEType[] =
"application/opensearchdescription+xml";
static const char kOpenSearchRelation[] = "search";
// FIXME: Why do only top-level frames have openSearchDescriptionURLs?
if (!GetFrame() || GetFrame()->Tree().Parent())
return KURL();
// FIXME: Why do we need to wait for load completion?
if (!LoadEventFinished())
return KURL();
if (!head())
return KURL();
for (HTMLLinkElement* link_element =
Traversal<HTMLLinkElement>::FirstChild(*head());
link_element;
link_element = Traversal<HTMLLinkElement>::NextSibling(*link_element)) {
if (!DeprecatedEqualIgnoringCase(link_element->GetType(),
kOpenSearchMIMEType) ||
!DeprecatedEqualIgnoringCase(link_element->Rel(), kOpenSearchRelation))
continue;
if (link_element->Href().IsEmpty())
continue;
// Count usage; perhaps we can lock this to secure contexts.
WebFeature osd_disposition;
scoped_refptr<const SecurityOrigin> target =
SecurityOrigin::Create(link_element->Href());
if (IsSecureContext()) {
osd_disposition = target->IsPotentiallyTrustworthy()
? WebFeature::kOpenSearchSecureOriginSecureTarget
: WebFeature::kOpenSearchSecureOriginInsecureTarget;
} else {
osd_disposition =
target->IsPotentiallyTrustworthy()
? WebFeature::kOpenSearchInsecureOriginSecureTarget
: WebFeature::kOpenSearchInsecureOriginInsecureTarget;
}
UseCounter::Count(*this, osd_disposition);
return link_element->Href();
}
return KURL();
}
void Document::currentScriptForBinding(
HTMLScriptElementOrSVGScriptElement& script_element) const {
if (!current_script_stack_.IsEmpty()) {
if (ScriptElementBase* script_element_base = current_script_stack_.back())
script_element_base->SetScriptElementForBinding(script_element);
}
}
void Document::PushCurrentScript(ScriptElementBase* new_current_script) {
current_script_stack_.push_back(new_current_script);
}
void Document::PopCurrentScript(ScriptElementBase* script) {
DCHECK(!current_script_stack_.IsEmpty());
DCHECK_EQ(current_script_stack_.back(), script);
current_script_stack_.pop_back();
}
void Document::SetTransformSource(std::unique_ptr<TransformSource> source) {
transform_source_ = std::move(source);
}
String Document::designMode() const {
return InDesignMode() ? "on" : "off";
}
void Document::setDesignMode(const String& value) {
bool new_value = design_mode_;
if (DeprecatedEqualIgnoringCase(value, "on")) {
new_value = true;
UseCounter::Count(*this, WebFeature::kDocumentDesignModeEnabeld);
} else if (DeprecatedEqualIgnoringCase(value, "off")) {
new_value = false;
}
if (new_value == design_mode_)
return;
design_mode_ = new_value;
SetNeedsStyleRecalc(kSubtreeStyleChange, StyleChangeReasonForTracing::Create(
StyleChangeReason::kDesignMode));
}
Document* Document::ParentDocument() const {
if (!frame_)
return nullptr;
Frame* parent = frame_->Tree().Parent();
if (!parent || !parent->IsLocalFrame())
return nullptr;
return ToLocalFrame(parent)->GetDocument();
}
Document& Document::TopDocument() const {
// FIXME: Not clear what topDocument() should do in the OOPI case--should it
// return the topmost available Document, or something else?
Document* doc = const_cast<Document*>(this);
for (HTMLFrameOwnerElement* element = doc->LocalOwner(); element;
element = doc->LocalOwner())
doc = &element->GetDocument();
DCHECK(doc);
return *doc;
}
Document* Document::ContextDocument() {
if (context_document_)
return context_document_;
if (frame_)
return this;
return nullptr;
}
Attr* Document::createAttribute(const AtomicString& name,
ExceptionState& exception_state) {
return createAttributeNS(g_null_atom, ConvertLocalName(name), exception_state,
true);
}
Attr* Document::createAttributeNS(const AtomicString& namespace_uri,
const AtomicString& qualified_name,
ExceptionState& exception_state,
bool should_ignore_namespace_checks) {
AtomicString prefix, local_name;
if (!ParseQualifiedName(qualified_name, prefix, local_name, exception_state))
return nullptr;
QualifiedName q_name(prefix, local_name, namespace_uri);
if (!should_ignore_namespace_checks &&
!HasValidNamespaceForAttributes(q_name)) {
exception_state.ThrowDOMException(
kNamespaceError,
"The namespace URI provided ('" + namespace_uri +
"') is not valid for the qualified name provided ('" +
qualified_name + "').");
return nullptr;
}
return Attr::Create(*this, q_name, g_empty_atom);
}
const SVGDocumentExtensions* Document::SvgExtensions() {
return svg_extensions_.Get();
}
SVGDocumentExtensions& Document::AccessSVGExtensions() {
if (!svg_extensions_)
svg_extensions_ = new SVGDocumentExtensions(this);
return *svg_extensions_;
}
bool Document::HasSVGRootNode() const {
return IsSVGSVGElement(documentElement());
}
HTMLCollection* Document::images() {
return EnsureCachedCollection<HTMLCollection>(kDocImages);
}
HTMLCollection* Document::applets() {
return EnsureCachedCollection<HTMLCollection>(kDocApplets);
}
HTMLCollection* Document::embeds() {
return EnsureCachedCollection<HTMLCollection>(kDocEmbeds);
}
HTMLCollection* Document::scripts() {
return EnsureCachedCollection<HTMLCollection>(kDocScripts);
}
HTMLCollection* Document::links() {
return EnsureCachedCollection<HTMLCollection>(kDocLinks);
}
HTMLCollection* Document::forms() {
return EnsureCachedCollection<HTMLCollection>(kDocForms);
}
HTMLCollection* Document::anchors() {
return EnsureCachedCollection<HTMLCollection>(kDocAnchors);
}
HTMLAllCollection* Document::all() {
return EnsureCachedCollection<HTMLAllCollection>(kDocAll);
}
HTMLCollection* Document::WindowNamedItems(const AtomicString& name) {
return EnsureCachedCollection<WindowNameCollection>(kWindowNamedItems, name);
}
DocumentNameCollection* Document::DocumentNamedItems(const AtomicString& name) {
return EnsureCachedCollection<DocumentNameCollection>(kDocumentNamedItems,
name);
}
HTMLCollection* Document::DocumentAllNamedItems(const AtomicString& name) {
return EnsureCachedCollection<DocumentAllNameCollection>(
kDocumentAllNamedItems, name);
}
LocalDOMWindow* Document::defaultView() const {
// The HTML spec requires to return null if the document is detached from the
// DOM. However, |dom_window_| is not cleared on the detachment. So, we need
// to check |frame_| to tell whether the document is attached or not.
return frame_ ? dom_window_ : nullptr;
}
void Document::FinishedParsing() {
DCHECK(!GetScriptableDocumentParser() || !parser_->IsParsing());
DCHECK(!GetScriptableDocumentParser() || ready_state_ != kLoading);
SetParsingState(kInDOMContentLoaded);
DocumentParserTiming::From(*this).MarkParserStop();
// FIXME: DOMContentLoaded is dispatched synchronously, but this should be
// dispatched in a queued task, see https://crbug.com/425790
if (!document_timing_.DomContentLoadedEventStart())
document_timing_.MarkDomContentLoadedEventStart();
DispatchEvent(Event::CreateBubble(EventTypeNames::DOMContentLoaded));
if (!document_timing_.DomContentLoadedEventEnd())
document_timing_.MarkDomContentLoadedEventEnd();
SetParsingState(kFinishedParsing);
// Ensure Custom Element callbacks are drained before DOMContentLoaded.
// FIXME: Remove this ad-hoc checkpoint when DOMContentLoaded is dispatched in
// a queued task, which will do a checkpoint anyway. https://crbug.com/425790
Microtask::PerformCheckpoint(V8PerIsolateData::MainThreadIsolate());
ScriptableDocumentParser* parser = GetScriptableDocumentParser();
well_formed_ = parser && parser->WellFormed();
if (LocalFrame* frame = GetFrame()) {
// Guarantee at least one call to the client specifying a title. (If
// |title_| is not empty, then the title has already been dispatched.)
if (title_.IsEmpty())
DispatchDidReceiveTitle();
// Don't update the layout tree if we haven't requested the main resource
// yet to avoid adding extra latency. Note that the first layout tree update
// can be expensive since it triggers the parsing of the default stylesheets
// which are compiled-in.
const bool main_resource_was_already_requested =
frame->Loader().StateMachine()->CommittedFirstRealDocumentLoad();
// FrameLoader::finishedParsing() might end up calling
// Document::implicitClose() if all resource loads are
// complete. HTMLObjectElements can start loading their resources from post
// attach callbacks triggered by recalcStyle(). This means if we parse out
// an <object> tag and then reach the end of the document without updating
// styles, we might not have yet started the resource load and might fire
// the window load event too early. To avoid this we force the styles to be
// up to date before calling FrameLoader::finishedParsing(). See
// https://bugs.webkit.org/show_bug.cgi?id=36864 starting around comment 35.
if (main_resource_was_already_requested)
UpdateStyleAndLayoutTree();
BeginLifecycleUpdatesIfRenderingReady();
frame->Loader().FinishedParsing();
TRACE_EVENT_INSTANT1("devtools.timeline", "MarkDOMContent",
TRACE_EVENT_SCOPE_THREAD, "data",
InspectorMarkLoadEvent::Data(frame));
probe::domContentLoadedEventFired(frame);
frame->GetIdlenessDetector()->DomContentLoadedEventFired();
}
// Schedule dropping of the ElementDataCache. We keep it alive for a while
// after parsing finishes so that dynamically inserted content can also
// benefit from sharing optimizations. Note that we don't refresh the timer
// on cache access since that could lead to huge caches being kept alive
// indefinitely by something innocuous like JS setting .innerHTML repeatedly
// on a timer.
element_data_cache_clear_timer_.StartOneShot(TimeDelta::FromSeconds(10),
FROM_HERE);
// Parser should have picked up all preloads by now
fetcher_->ClearPreloads(ResourceFetcher::kClearSpeculativeMarkupPreloads);
if (!frame_ || frame_->GetSettings()->GetSavePreviousDocumentResources() ==
SavePreviousDocumentResources::kUntilOnDOMContentLoaded) {
fetcher_->ClearResourcesFromPreviousFetcher();
}
if (IsPrefetchOnly())
WebPrerenderingSupport::Current()->PrefetchFinished();
}
void Document::ElementDataCacheClearTimerFired(TimerBase*) {
element_data_cache_.Clear();
}
void Document::BeginLifecycleUpdatesIfRenderingReady() {
if (!IsActive())
return;
if (!IsRenderingReady())
return;
View()->BeginLifecycleUpdates();
}
Vector<IconURL> Document::IconURLs(int icon_types_mask) {
IconURL first_favicon;
IconURL first_touch_icon;
IconURL first_touch_precomposed_icon;
Vector<IconURL> secondary_icons;
using TraversalFunction = HTMLLinkElement* (*)(const Node&);
TraversalFunction find_next_candidate =
&Traversal<HTMLLinkElement>::NextSibling;
HTMLLinkElement* first_element = nullptr;
if (head()) {
first_element = Traversal<HTMLLinkElement>::FirstChild(*head());
} else if (IsSVGDocument() && IsSVGSVGElement(documentElement())) {
first_element = Traversal<HTMLLinkElement>::FirstWithin(*documentElement());
find_next_candidate = &Traversal<HTMLLinkElement>::Next;
}
// Start from the first child node so that icons seen later take precedence as
// required by the spec.
for (HTMLLinkElement* link_element = first_element; link_element;
link_element = find_next_candidate(*link_element)) {
if (!(link_element->GetIconType() & icon_types_mask))
continue;
if (link_element->Href().IsEmpty())
continue;
IconURL new_url(link_element->Href(), link_element->IconSizes(),
link_element->GetType(), link_element->GetIconType());
if (link_element->GetIconType() == kFavicon) {
if (first_favicon.icon_type_ != kInvalidIcon)
secondary_icons.push_back(first_favicon);
first_favicon = new_url;
} else if (link_element->GetIconType() == kTouchIcon) {
if (first_touch_icon.icon_type_ != kInvalidIcon)
secondary_icons.push_back(first_touch_icon);
first_touch_icon = new_url;
} else if (link_element->GetIconType() == kTouchPrecomposedIcon) {
if (first_touch_precomposed_icon.icon_type_ != kInvalidIcon)
secondary_icons.push_back(first_touch_precomposed_icon);
first_touch_precomposed_icon = new_url;
} else {
NOTREACHED();
}
}
Vector<IconURL> icon_urls;
if (first_favicon.icon_type_ != kInvalidIcon)
icon_urls.push_back(first_favicon);
else if (url_.ProtocolIsInHTTPFamily() && icon_types_mask & kFavicon)
icon_urls.push_back(IconURL::DefaultFavicon(url_));
if (first_touch_icon.icon_type_ != kInvalidIcon)
icon_urls.push_back(first_touch_icon);
if (first_touch_precomposed_icon.icon_type_ != kInvalidIcon)
icon_urls.push_back(first_touch_precomposed_icon);
for (int i = secondary_icons.size() - 1; i >= 0; --i)
icon_urls.push_back(secondary_icons[i]);
return icon_urls;
}
Color Document::ThemeColor() const {
auto root_element = documentElement();
if (!root_element)
return Color();
for (HTMLMetaElement& meta_element :
Traversal<HTMLMetaElement>::DescendantsOf(*root_element)) {
Color color = Color::kTransparent;
if (DeprecatedEqualIgnoringCase(meta_element.GetName(), "theme-color") &&
CSSParser::ParseColor(
color, meta_element.Content().GetString().StripWhiteSpace(), true))
return color;
}
return Color();
}
static HTMLLinkElement* GetLinkElement(const Document* doc,
bool (*match_fn)(HTMLLinkElement&)) {
HTMLHeadElement* head = doc->head();
if (!head)
return nullptr;
// The first matching link element is used. Others are ignored.
for (HTMLLinkElement& link_element :
Traversal<HTMLLinkElement>::ChildrenOf(*head)) {
if (match_fn(link_element))
return &link_element;
}
return nullptr;
}
HTMLLinkElement* Document::LinkManifest() const {
return GetLinkElement(this, [](HTMLLinkElement& link_element) {
return link_element.RelAttribute().IsManifest();
});
}
HTMLLinkElement* Document::LinkCanonical() const {
return GetLinkElement(this, [](HTMLLinkElement& link_element) {
return link_element.RelAttribute().IsCanonical();
});
}
void Document::ApplyFeaturePolicyFromHeader(
const String& feature_policy_header) {
if (!feature_policy_header.IsEmpty())
UseCounter::Count(*this, WebFeature::kFeaturePolicyHeader);
Vector<String> messages;
const ParsedFeaturePolicy& declared_policy = ParseFeaturePolicyHeader(
feature_policy_header, GetSecurityOrigin(), &messages);
for (auto& message : messages) {
AddConsoleMessage(
ConsoleMessage::Create(kSecurityMessageSource, kErrorMessageLevel,
"Error with Feature-Policy header: " + message));
}
ApplyFeaturePolicy(declared_policy);
if (frame_) {
frame_->Client()->DidSetFramePolicyHeaders(GetSandboxFlags(),
declared_policy);
}
}
void Document::ApplyFeaturePolicy(const ParsedFeaturePolicy& declared_policy) {
if (!RuntimeEnabledFeatures::FeaturePolicyEnabled())
return;
FeaturePolicy* parent_feature_policy = nullptr;
ParsedFeaturePolicy container_policy;
// If this frame is not the main frame, then get the appropriate parent policy
// and container policy to construct the policy for this frame.
if (frame_) {
if (!frame_->IsMainFrame()) {
parent_feature_policy =
frame_->Tree().Parent()->GetSecurityContext()->GetFeaturePolicy();
}
if (frame_->Owner())
container_policy = frame_->Owner()->ContainerPolicy();
}
InitializeFeaturePolicy(declared_policy, container_policy,
parent_feature_policy);
}
ukm::UkmRecorder* Document::UkmRecorder() {
if (ukm_recorder_)
return ukm_recorder_.get();
ukm_recorder_ =
ukm::MojoUkmRecorder::Create(Platform::Current()->GetConnector());
// TODO(crbug/795354): Move handling of URL recording out of the renderer.
// URL must only be recorded from the main frame.
if (IsInMainFrame())
ukm_recorder_->UpdateSourceURL(ukm_source_id_, url_);
return ukm_recorder_.get();
}
int64_t Document::UkmSourceID() const {
return ukm_source_id_;
}
void Document::InitSecurityContext(const DocumentInit& initializer) {
DCHECK(!GetSecurityOrigin());
if (!initializer.HasSecurityContext()) {
// No source for a security context.
// This can occur via document.implementation.createDocument().
cookie_url_ = KURL(g_empty_string);
SetSecurityOrigin(SecurityOrigin::CreateUnique());
InitContentSecurityPolicy();
ApplyFeaturePolicy({});
// Unique security origins cannot have a suborigin
return;
}
SandboxFlags sandbox_flags = initializer.GetSandboxFlags();
if (fetcher_->Archive()) {
// The URL of a Document loaded from a MHTML archive is controlled by the
// Content-Location header. This would allow UXSS, since Content-Location
// can be arbitrarily controlled to control the Document's URL and origin.
// Instead, force a Document loaded from a MHTML archive to be sandboxed,
// providing exceptions only for creating new windows.
sandbox_flags |=
kSandboxAll &
~(kSandboxPopups | kSandboxPropagatesToAuxiliaryBrowsingContexts);
}
// In the common case, create the security context from the currently
// loading URL with a fresh content security policy.
EnforceSandboxFlags(sandbox_flags);
SetInsecureRequestPolicy(initializer.GetInsecureRequestPolicy());
if (initializer.InsecureNavigationsToUpgrade()) {
for (auto to_upgrade : *initializer.InsecureNavigationsToUpgrade())
AddInsecureNavigationUpgrade(to_upgrade);
}
ContentSecurityPolicy* policy_to_inherit = nullptr;
if (IsSandboxed(kSandboxOrigin)) {
cookie_url_ = url_;
scoped_refptr<SecurityOrigin> security_origin =
SecurityOrigin::CreateUnique();
// If we're supposed to inherit our security origin from our
// owner, but we're also sandboxed, the only things we inherit are
// the origin's potential trustworthiness and the ability to
// load local resources. The latter lets about:blank iframes in
// file:// URL documents load images and other resources from
// the file system.
Document* owner = initializer.OwnerDocument();
if (owner) {
if (owner->GetSecurityOrigin()->IsPotentiallyTrustworthy())
security_origin->SetUniqueOriginIsPotentiallyTrustworthy(true);
if (owner->GetSecurityOrigin()->CanLoadLocalResources())
security_origin->GrantLoadLocalResources();
policy_to_inherit = owner->GetContentSecurityPolicy();
}
SetSecurityOrigin(std::move(security_origin));
} else if (Document* owner = initializer.OwnerDocument()) {
cookie_url_ = owner->CookieURL();
// We alias the SecurityOrigins to match Firefox, see Bug 15313
// https://bugs.webkit.org/show_bug.cgi?id=15313
SetSecurityOrigin(owner->GetMutableSecurityOrigin());
policy_to_inherit = owner->GetContentSecurityPolicy();
} else {
cookie_url_ = url_;
SetSecurityOrigin(SecurityOrigin::Create(url_));
}
// Set the address space before setting up CSP, as the latter may override
// the former via the 'treat-as-public-address' directive (see
// https://wicg.github.io/cors-rfc1918/#csp).
if (initializer.IsHostedInReservedIPRange()) {
SetAddressSpace(GetSecurityOrigin()->IsLocalhost()
? mojom::IPAddressSpace::kLocal
: mojom::IPAddressSpace::kPrivate);
} else if (GetSecurityOrigin()->IsLocal()) {
// "Local" security origins (like 'file://...') are treated as having
// a local address space.
//
// TODO(mkwst): It's not entirely clear that this is a good idea.
SetAddressSpace(mojom::IPAddressSpace::kLocal);
} else {
SetAddressSpace(mojom::IPAddressSpace::kPublic);
}
if (ImportsController()) {
// If this document is an HTML import, grab a reference to it's master
// document's Content Security Policy. We don't call
// 'initContentSecurityPolicy' in this case, as we can't rebind the master
// document's policy object: its ExecutionContext needs to remain tied to
// the master document.
SetContentSecurityPolicy(
ImportsController()->Master()->GetContentSecurityPolicy());
} else {
InitContentSecurityPolicy(nullptr, policy_to_inherit);
}
if (Settings* settings = initializer.GetSettings()) {
if (!settings->GetWebSecurityEnabled()) {
// Web security is turned off. We should let this document access every
// other document. This is used primary by testing harnesses for web
// sites.
GetMutableSecurityOrigin()->GrantUniversalAccess();
} else if (GetSecurityOrigin()->IsLocal()) {
if (settings->GetAllowUniversalAccessFromFileURLs()) {
// Some clients want local URLs to have universal access, but that
// setting is dangerous for other clients.
GetMutableSecurityOrigin()->GrantUniversalAccess();
} else if (!settings->GetAllowFileAccessFromFileURLs()) {
// Some clients do not want local URLs to have access to other local
// URLs.
GetMutableSecurityOrigin()->BlockLocalAccessFromLocalOrigin();
}
}
}
if (GetSecurityOrigin()->IsUnique() &&
SecurityOrigin::Create(url_)->IsPotentiallyTrustworthy())
GetMutableSecurityOrigin()->SetUniqueOriginIsPotentiallyTrustworthy(true);
ApplyFeaturePolicy({});
InitSecureContextState();
}
void Document::InitSecureContextState() {
DCHECK_EQ(secure_context_state_, SecureContextState::kUnknown);
if (!GetSecurityOrigin()->IsPotentiallyTrustworthy()) {
secure_context_state_ = SecureContextState::kNonSecure;
} else if (SchemeRegistry::SchemeShouldBypassSecureContextCheck(
GetSecurityOrigin()->Protocol())) {
secure_context_state_ = SecureContextState::kSecure;
} else if (frame_) {
Frame* parent = frame_->Tree().Parent();
while (parent) {
if (!parent->GetSecurityContext()
->GetSecurityOrigin()
->IsPotentiallyTrustworthy()) {
secure_context_state_ = SecureContextState::kNonSecure;
break;
}
parent = parent->Tree().Parent();
}
if (secure_context_state_ == SecureContextState::kUnknown)
secure_context_state_ = SecureContextState::kSecure;
} else {
secure_context_state_ = SecureContextState::kNonSecure;
}
DCHECK_NE(secure_context_state_, SecureContextState::kUnknown);
}
// the first parameter specifies a policy to use as the document csp meaning
// the document will take ownership of the policy
// the second parameter specifies a policy to inherit meaning the document
// will attempt to copy over the policy
void Document::InitContentSecurityPolicy(
ContentSecurityPolicy* csp,
const ContentSecurityPolicy* policy_to_inherit) {
SetContentSecurityPolicy(csp ? csp : ContentSecurityPolicy::Create());
GetContentSecurityPolicy()->BindToExecutionContext(this);
// We inherit the parent/opener's CSP for documents with "local" schemes:
// 'about', 'blob', 'data', and 'filesystem'. We also inherit CSP for
// documents with empty/invalid URLs because we treat those URLs as
// 'about:blank' in Blink.
//
// https://w3c.github.io/webappsec-csp/#initialize-document-csp
//
// TODO(dcheng): This is similar enough to work we're doing in
// 'DocumentLoader::ensureWriter' that it might make sense to combine them.
if (policy_to_inherit) {
GetContentSecurityPolicy()->CopyStateFrom(policy_to_inherit);
} else if (frame_) {
Frame* inherit_from = frame_->Tree().Parent() ? frame_->Tree().Parent()
: frame_->Client()->Opener();
if (inherit_from && frame_ != inherit_from) {
DCHECK(inherit_from->GetSecurityContext() &&
inherit_from->GetSecurityContext()->GetContentSecurityPolicy());
policy_to_inherit =
inherit_from->GetSecurityContext()->GetContentSecurityPolicy();
if (url_.IsEmpty() || url_.ProtocolIsAbout() || url_.ProtocolIsData() ||
url_.ProtocolIs("blob") || url_.ProtocolIs("filesystem")) {
GetContentSecurityPolicy()->CopyStateFrom(policy_to_inherit);
}
}
}
// Plugin documents inherit their parent/opener's 'plugin-types' directive
// regardless of URL.
if (policy_to_inherit && IsPluginDocument())
GetContentSecurityPolicy()->CopyPluginTypesFrom(policy_to_inherit);
}
bool Document::IsSecureTransitionTo(const KURL& url) const {
scoped_refptr<const SecurityOrigin> other = SecurityOrigin::Create(url);
return GetSecurityOrigin()->CanAccess(other.get());
}
bool Document::CanExecuteScripts(ReasonForCallingCanExecuteScripts reason) {
if (IsSandboxed(kSandboxScripts)) {
// FIXME: This message should be moved off the console once a solution to
// https://bugs.webkit.org/show_bug.cgi?id=103274 exists.
if (reason == kAboutToExecuteScript) {
AddConsoleMessage(ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel,
"Blocked script execution in '" + Url().ElidedString() +
"' because the document's frame is sandboxed and the "
"'allow-scripts' permission is not set."));
}
return false;
}
DCHECK(GetFrame())
<< "you are querying canExecuteScripts on a non contextDocument.";
ContentSettingsClient* settings_client =
GetFrame()->GetContentSettingsClient();
if (!settings_client)
return false;
Settings* settings = GetFrame()->GetSettings();
if (!settings_client->AllowScript(settings && settings->GetScriptEnabled())) {
if (reason == kAboutToExecuteScript)
settings_client->DidNotAllowScript();
return false;
}
return true;
}
bool Document::IsRenderingReady() const {
return style_engine_->IgnoringPendingStylesheets() ||
(HaveImportsLoaded() && HaveRenderBlockingStylesheetsLoaded());
}
bool Document::AllowInlineEventHandler(Node* node,
EventListener* listener,
const String& context_url,
const WTF::OrdinalNumber& context_line) {
Element* element = node && node->IsElementNode() ? ToElement(node) : nullptr;
if (!ContentSecurityPolicy::ShouldBypassMainWorld(this) &&
!GetContentSecurityPolicy()->AllowInlineEventHandler(
element, listener->Code(), context_url, context_line))
return false;
// HTML says that inline script needs browsing context to create its execution
// environment.
// http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#event-handler-attributes
// Also, if the listening node came from other document, which happens on
// context-less event dispatching, we also need to ask the owner document of
// the node.
LocalFrame* frame = ExecutingFrame();
if (!frame)
return false;
if (!ContextDocument()->CanExecuteScripts(kNotAboutToExecuteScript))
return false;
if (node && node->GetDocument() != this &&
!node->GetDocument().AllowInlineEventHandler(node, listener, context_url,
context_line))
return false;
return true;
}
void Document::EnforceSandboxFlags(SandboxFlags mask) {
scoped_refptr<const SecurityOrigin> stand_in_origin = GetSecurityOrigin();
bool is_potentially_trustworthy =
stand_in_origin && stand_in_origin->IsPotentiallyTrustworthy();
ApplySandboxFlags(mask, is_potentially_trustworthy);
}
void Document::UpdateSecurityOrigin(scoped_refptr<SecurityOrigin> origin) {
SetSecurityOrigin(std::move(origin));
DidUpdateSecurityOrigin();
}
String Document::origin() const {
return GetSecurityOrigin()->ToString();
}
String Document::suborigin() const {
return GetSecurityOrigin()->HasSuborigin()
? GetSecurityOrigin()->GetSuborigin()->GetName()
: String();
}
void Document::DidUpdateSecurityOrigin() {
if (!frame_)
return;
frame_->GetScriptController().UpdateSecurityOrigin(GetSecurityOrigin());
}
bool Document::IsContextThread() const {
return IsMainThread();
}
void Document::UpdateFocusAppearanceLater() {
if (!update_focus_appearance_timer_.IsActive())
update_focus_appearance_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void Document::CancelFocusAppearanceUpdate() {
update_focus_appearance_timer_.Stop();
}
void Document::UpdateFocusAppearanceTimerFired(TimerBase*) {
Element* element = FocusedElement();
if (!element)
return;
UpdateStyleAndLayout();
if (element->IsFocusable())
element->UpdateFocusAppearance(SelectionBehaviorOnFocus::kRestore);
}
void Document::AttachRange(Range* range) {
DCHECK(!ranges_.Contains(range));
ranges_.insert(range);
}
void Document::DetachRange(Range* range) {
// We don't ASSERT m_ranges.contains(range) to allow us to call this
// unconditionally to fix: https://bugs.webkit.org/show_bug.cgi?id=26044
ranges_.erase(range);
}
void Document::InitDNSPrefetch() {
Settings* settings = GetSettings();
have_explicitly_disabled_dns_prefetch_ = false;
is_dns_prefetch_enabled_ = settings && settings->GetDNSPrefetchingEnabled() &&
GetSecurityOrigin()->Protocol() == "http";
// Inherit DNS prefetch opt-out from parent frame
if (Document* parent = ParentDocument()) {
if (!parent->IsDNSPrefetchEnabled())
is_dns_prefetch_enabled_ = false;
}
}
void Document::ParseDNSPrefetchControlHeader(
const String& dns_prefetch_control) {
if (DeprecatedEqualIgnoringCase(dns_prefetch_control, "on") &&
!have_explicitly_disabled_dns_prefetch_) {
is_dns_prefetch_enabled_ = true;
return;
}
is_dns_prefetch_enabled_ = false;
have_explicitly_disabled_dns_prefetch_ = true;
}
IntersectionObserverController* Document::GetIntersectionObserverController() {
return intersection_observer_controller_;
}
IntersectionObserverController&
Document::EnsureIntersectionObserverController() {
if (!intersection_observer_controller_)
intersection_observer_controller_ =
IntersectionObserverController::Create(this);
return *intersection_observer_controller_;
}
ResizeObserverController& Document::EnsureResizeObserverController() {
if (!resize_observer_controller_)
resize_observer_controller_ = new ResizeObserverController();
return *resize_observer_controller_;
}
static void RunAddConsoleMessageTask(MessageSource source,
MessageLevel level,
const String& message,
ExecutionContext* context) {
ConsoleMessage* console_message =
ConsoleMessage::Create(source, level, message);
context->AddConsoleMessage(console_message);
}
void Document::AddConsoleMessage(ConsoleMessage* console_message) {
if (!IsContextThread()) {
PostCrossThreadTask(
*GetTaskRunner(TaskType::kUnthrottled), FROM_HERE,
CrossThreadBind(&RunAddConsoleMessageTask, console_message->Source(),
console_message->Level(), console_message->Message(),
WrapCrossThreadPersistent(this)));
return;
}
if (!frame_)
return;
if (console_message->Location()->IsUnknown()) {
// TODO(dgozman): capture correct location at call places instead.
unsigned line_number = 0;
if (!IsInDocumentWrite() && GetScriptableDocumentParser()) {
ScriptableDocumentParser* parser = GetScriptableDocumentParser();
if (parser->IsParsingAtLineNumber())
line_number = parser->LineNumber().OneBasedInt();
}
Vector<DOMNodeId> nodes(console_message->Nodes());
console_message = ConsoleMessage::Create(
console_message->Source(), console_message->Level(),
console_message->Message(),
SourceLocation::Create(Url().GetString(), line_number, 0, nullptr));
console_message->SetNodes(frame_, std::move(nodes));
}
frame_->Console().AddMessage(console_message);
}
void Document::TasksWerePaused() {
GetScriptRunner()->Suspend();
if (parser_)
parser_->PauseScheduledTasks();
if (scripted_animation_controller_)
scripted_animation_controller_->Pause();
}
void Document::TasksWereUnpaused() {
GetScriptRunner()->Resume();
if (parser_)
parser_->UnpauseScheduledTasks();
if (scripted_animation_controller_)
scripted_animation_controller_->Unpause();
MutationObserver::ResumeSuspendedObservers();
if (dom_window_)
DOMWindowPerformance::performance(*dom_window_)->ResumeSuspendedObservers();
}
bool Document::TasksNeedPause() {
Page* page = GetPage();
return page && page->Paused();
}
void Document::AddToTopLayer(Element* element, const Element* before) {
if (element->IsInTopLayer())
return;
DCHECK(!top_layer_elements_.Contains(element));
DCHECK(!before || top_layer_elements_.Contains(before));
if (before) {
size_t before_position = top_layer_elements_.Find(before);
top_layer_elements_.insert(before_position, element);
} else {
top_layer_elements_.push_back(element);
}
element->SetIsInTopLayer(true);
}
void Document::RemoveFromTopLayer(Element* element) {
if (!element->IsInTopLayer())
return;
size_t position = top_layer_elements_.Find(element);
DCHECK_NE(position, kNotFound);
top_layer_elements_.EraseAt(position);
element->SetIsInTopLayer(false);
}
HTMLDialogElement* Document::ActiveModalDialog() const {
for (auto it = top_layer_elements_.rbegin(); it != top_layer_elements_.rend();
++it) {
if (auto* dialog = ToHTMLDialogElementOrNull(*it))
return dialog;
}
return nullptr;
}
void Document::exitPointerLock() {
if (!GetPage())
return;
if (Element* target = GetPage()->GetPointerLockController().GetElement()) {
if (target->GetDocument() != this)
return;
GetPage()->GetPointerLockController().RequestPointerUnlock();
}
}
Element* Document::PointerLockElement() const {
if (!GetPage() || GetPage()->GetPointerLockController().LockPending())
return nullptr;
if (Element* element = GetPage()->GetPointerLockController().GetElement()) {
if (element->GetDocument() == this)
return element;
}
return nullptr;
}
void Document::SuppressLoadEvent() {
if (!LoadEventFinished())
load_event_progress_ = kLoadEventCompleted;
}
void Document::DecrementLoadEventDelayCount() {
DCHECK(load_event_delay_count_);
--load_event_delay_count_;
if (!load_event_delay_count_)
CheckLoadEventSoon();
}
void Document::DecrementLoadEventDelayCountAndCheckLoadEvent() {
DCHECK(load_event_delay_count_);
--load_event_delay_count_;
if (!load_event_delay_count_)
CheckCompleted();
}
void Document::CheckLoadEventSoon() {
if (GetFrame() && !load_event_delay_timer_.IsActive())
load_event_delay_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
bool Document::IsDelayingLoadEvent() {
// Always delay load events until after garbage collection.
// This way we don't have to explicitly delay load events via
// incrementLoadEventDelayCount and decrementLoadEventDelayCount in
// Node destructors.
if (ThreadState::Current()->SweepForbidden()) {
if (!load_event_delay_count_)
CheckLoadEventSoon();
return true;
}
return load_event_delay_count_;
}
void Document::LoadEventDelayTimerFired(TimerBase*) {
CheckCompleted();
}
void Document::LoadPluginsSoon() {
// FIXME: Remove this timer once we don't need to compute layout to load
// plugins.
if (!plugin_loading_timer_.IsActive())
plugin_loading_timer_.StartOneShot(TimeDelta(), FROM_HERE);
}
void Document::PluginLoadingTimerFired(TimerBase*) {
UpdateStyleAndLayout();
}
ScriptedAnimationController& Document::EnsureScriptedAnimationController() {
if (!scripted_animation_controller_) {
scripted_animation_controller_ = ScriptedAnimationController::Create(this);
// We need to make sure that we don't start up the animation controller on a
// background tab, for example.
if (!GetPage())
scripted_animation_controller_->Pause();
}
return *scripted_animation_controller_;
}
int Document::RequestAnimationFrame(
FrameRequestCallbackCollection::FrameCallback* callback) {
return EnsureScriptedAnimationController().RegisterCallback(callback);
}
void Document::CancelAnimationFrame(int id) {
if (!scripted_animation_controller_)
return;
scripted_animation_controller_->CancelCallback(id);
}
void Document::ServiceScriptedAnimations(
double monotonic_animation_start_time) {
if (!scripted_animation_controller_)
return;
scripted_animation_controller_->ServiceScriptedAnimations(
monotonic_animation_start_time);
}
ScriptedIdleTaskController& Document::EnsureScriptedIdleTaskController() {
if (!scripted_idle_task_controller_)
scripted_idle_task_controller_ = ScriptedIdleTaskController::Create(this);
return *scripted_idle_task_controller_;
}
int Document::RequestIdleCallback(
ScriptedIdleTaskController::IdleTask* idle_task,
const IdleRequestOptions& options) {
return EnsureScriptedIdleTaskController().RegisterCallback(idle_task,
options);
}
void Document::CancelIdleCallback(int id) {
if (!scripted_idle_task_controller_)
return;
scripted_idle_task_controller_->CancelCallback(id);
}
Touch* Document::createTouch(DOMWindow* window,
EventTarget* target,
int identifier,
double page_x,
double page_y,
double screen_x,
double screen_y,
double radius_x,
double radius_y,
float rotation_angle,
float force) const {
// Match behavior from when these types were integers, and avoid surprises
// from someone explicitly
// passing Infinity/NaN.
if (!std::isfinite(page_x))
page_x = 0;
if (!std::isfinite(page_y))
page_y = 0;
if (!std::isfinite(screen_x))
screen_x = 0;
if (!std::isfinite(screen_y))
screen_y = 0;
if (!std::isfinite(radius_x))
radius_x = 0;
if (!std::isfinite(radius_y))
radius_y = 0;
if (!std::isfinite(rotation_angle))
rotation_angle = 0;
if (!std::isfinite(force))
force = 0;
if (radius_x || radius_y || rotation_angle || force) {
UseCounter::Count(*this,
WebFeature::kDocumentCreateTouchMoreThanSevenArguments);
}
// FIXME: It's not clear from the documentation at
// http://developer.apple.com/library/safari/#documentation/UserExperience/Reference/DocumentAdditionsReference/DocumentAdditions/DocumentAdditions.html
// when this method should throw and nor is it by inspection of iOS behavior.
// It would be nice to verify any cases where it throws under iOS and
// implement them here. See https://bugs.webkit.org/show_bug.cgi?id=47819
LocalFrame* frame = window && window->IsLocalDOMWindow()
? blink::ToLocalDOMWindow(window)->GetFrame()
: GetFrame();
return Touch::Create(
frame, target, identifier, FloatPoint(screen_x, screen_y),
FloatPoint(page_x, page_y), FloatSize(radius_x, radius_y), rotation_angle,
force, String());
}
TouchList* Document::createTouchList(HeapVector<Member<Touch>>& touches) const {
return TouchList::Adopt(touches);
}
DocumentLoader* Document::Loader() const {
if (!frame_)
return nullptr;
if (frame_->GetDocument() != this)
return nullptr;
return frame_->Loader().GetDocumentLoader();
}
Node* EventTargetNodeForDocument(Document* doc) {
if (!doc)
return nullptr;
Node* node = doc->FocusedElement();
if (!node && doc->IsPluginDocument()) {
PluginDocument* plugin_document = ToPluginDocument(doc);
node = plugin_document->PluginNode();
}
if (!node && doc->IsHTMLDocument())
node = doc->body();
if (!node)
node = doc->documentElement();
return node;
}
void Document::AdjustFloatQuadsForScrollAndAbsoluteZoom(
Vector<FloatQuad>& quads,
const LayoutObject& layout_object) const {
if (!View())
return;
LayoutRect visible_content_rect(View()->VisibleContentRect());
for (size_t i = 0; i < quads.size(); ++i) {
quads[i].Move(-FloatSize(visible_content_rect.X().ToFloat(),
visible_content_rect.Y().ToFloat()));
AdjustForAbsoluteZoom::AdjustFloatQuad(quads[i], layout_object);
}
}
void Document::AdjustFloatRectForScrollAndAbsoluteZoom(
FloatRect& rect,
const LayoutObject& layout_object) const {
if (!View())
return;
LayoutRect visible_content_rect(View()->VisibleContentRect());
rect.Move(-FloatSize(visible_content_rect.X().ToFloat(),
visible_content_rect.Y().ToFloat()));
AdjustForAbsoluteZoom::AdjustFloatRect(rect, layout_object);
}
void Document::SetThreadedParsingEnabledForTesting(bool enabled) {
g_threaded_parsing_enabled_for_testing = enabled;
}
bool Document::ThreadedParsingEnabledForTesting() {
return g_threaded_parsing_enabled_for_testing;
}
SnapCoordinator* Document::GetSnapCoordinator() {
if (RuntimeEnabledFeatures::CSSScrollSnapPointsEnabled() &&
!snap_coordinator_)
snap_coordinator_ = SnapCoordinator::Create();
return snap_coordinator_.Get();
}
void Document::SetContextFeatures(ContextFeatures& features) {
context_features_ = &features;
}
// TODO(mustaq) |request| parameter maybe a misuse of HitTestRequest in
// updateHoverActiveState() since the function doesn't bother with hit-testing.
void Document::UpdateHoverActiveState(const HitTestRequest& request,
Element* inner_element) {
DCHECK(!request.ReadOnly());
if (request.Active() && frame_)
frame_->GetEventHandler().NotifyElementActivated();
Element* inner_element_in_document = inner_element;
while (inner_element_in_document &&
inner_element_in_document->GetDocument() != this) {
inner_element_in_document->GetDocument().UpdateHoverActiveState(
request, inner_element_in_document);
inner_element_in_document =
inner_element_in_document->GetDocument().LocalOwner();
}
UpdateDistribution();
UpdateActiveState(request, inner_element_in_document);
UpdateHoverState(request, inner_element_in_document);
}
void Document::UpdateActiveState(const HitTestRequest& request,
Element* inner_element_in_document) {
Element* old_active_element = GetActiveElement();
if (old_active_element && !request.Active()) {
// The oldActiveElement layoutObject is null, dropped on :active by setting
// display: none, for instance. We still need to clear the ActiveChain as
// the mouse is released.
for (Element* element = old_active_element; element;
element = FlatTreeTraversal::ParentElement(*element)) {
element->SetActive(false);
user_action_elements_.SetInActiveChain(element, false);
}
SetActiveElement(nullptr);
} else {
Element* new_active_element = inner_element_in_document;
if (!old_active_element && new_active_element &&
!new_active_element->IsDisabledFormControl() && request.Active() &&
!request.TouchMove()) {
// We are setting the :active chain and freezing it. If future moves
// happen, they will need to reference this chain.
for (Element* element = new_active_element; element;
element = FlatTreeTraversal::ParentElement(*element)) {
user_action_elements_.SetInActiveChain(element, true);
}
SetActiveElement(new_active_element);
}
}
// If the mouse has just been pressed, set :active on the chain. Those (and
// only those) nodes should remain :active until the mouse is released.
bool allow_active_changes = !old_active_element && GetActiveElement();
if (!allow_active_changes)
return;
// If the mouse is down and if this is a mouse move event, we want to restrict
// changes in :active to only apply to elements that are in the :active
// chain that we froze at the time the mouse went down.
bool must_be_in_active_chain = request.Active() && request.Move();
Element* new_element =
SkipDisplayNoneAncestors(inner_element_in_document);
// Now set the active state for our new object up to the root.
for (Element* curr = new_element; curr;
curr = FlatTreeTraversal::ParentElement(*curr)) {
if (!must_be_in_active_chain || curr->InActiveChain())
curr->SetActive(true);
}
}
void Document::UpdateHoverState(const HitTestRequest& request,
Element* inner_element_in_document) {
Element* old_hover_element = HoverElement();
// The passed in innerElement may not be a result of a hit test for the
// current up-to-date flat/layout tree. That means the element may be
// display:none at this point. Skip up the ancestor chain until we reach an
// element with a layoutObject or a display:contents element.
Element* new_hover_element =
SkipDisplayNoneAncestors(inner_element_in_document);
// Update our current hover element.
SetHoverElement(new_hover_element);
if (old_hover_element == new_hover_element)
return;
Node* ancestor_element = nullptr;
if (old_hover_element && old_hover_element->isConnected() &&
new_hover_element) {
Node* ancestor = FlatTreeTraversal::CommonAncestor(*old_hover_element,
*new_hover_element);
if (ancestor && ancestor->IsElementNode())
ancestor_element = ToElement(ancestor);
}
HeapVector<Member<Element>, 32> elements_to_remove_from_chain;
HeapVector<Member<Element>, 32> elements_to_add_to_hover_chain;
// The old hover path only needs to be cleared up to (and not including) the
// common ancestor;
//
// FIXME(ecobos@igalia.com): oldHoverElement may be disconnected from the
// tree already.
if (old_hover_element && old_hover_element->isConnected()) {
for (Element* curr = old_hover_element; curr && curr != ancestor_element;
curr = FlatTreeTraversal::ParentElement(*curr)) {
elements_to_remove_from_chain.push_back(curr);
}
}
// Now set the hover state for our new object up to the root.
for (Element* curr = new_hover_element; curr;
curr = FlatTreeTraversal::ParentElement(*curr)) {
elements_to_add_to_hover_chain.push_back(curr);
}
for (Element* element : elements_to_remove_from_chain)
element->SetHovered(false);
bool saw_common_ancestor = false;
for (Element* element : elements_to_add_to_hover_chain) {
if (element == ancestor_element)
saw_common_ancestor = true;
if (!saw_common_ancestor || element == hover_element_)
element->SetHovered(true);
}
}
bool Document::HaveScriptBlockingStylesheetsLoaded() const {
return style_engine_->HaveScriptBlockingStylesheetsLoaded();
}
bool Document::HaveRenderBlockingStylesheetsLoaded() const {
if (RuntimeEnabledFeatures::CSSInBodyDoesNotBlockPaintEnabled())
return style_engine_->HaveRenderBlockingStylesheetsLoaded();
return style_engine_->HaveScriptBlockingStylesheetsLoaded();
}
Locale& Document::GetCachedLocale(const AtomicString& locale) {
AtomicString locale_key = locale;
if (locale.IsEmpty() ||
!RuntimeEnabledFeatures::LangAttributeAwareFormControlUIEnabled())
return Locale::DefaultLocale();
LocaleIdentifierToLocaleMap::AddResult result =
locale_cache_.insert(locale_key, nullptr);
if (result.is_new_entry)
result.stored_value->value = Locale::Create(locale_key);
return *(result.stored_value->value);
}
AnimationClock& Document::GetAnimationClock() {
DCHECK(GetPage());
return GetPage()->Animator().Clock();
}
Document& Document::EnsureTemplateDocument() {
if (IsTemplateDocument())
return *this;
if (template_document_)
return *template_document_;
if (IsHTMLDocument()) {
template_document_ =
HTMLDocument::Create(DocumentInit::Create()
.WithContextDocument(ContextDocument())
.WithURL(BlankURL())
.WithNewRegistrationContext());
} else {
template_document_ =
Document::Create(DocumentInit::Create().WithURL(BlankURL()));
}
template_document_->template_document_host_ = this; // balanced in dtor.
return *template_document_.Get();
}
void Document::DidAssociateFormControl(Element* element) {
if (!GetFrame() || !GetFrame()->GetPage() || !HasFinishedParsing())
return;
// We add a slight delay because this could be called rapidly.
if (!did_associate_form_controls_timer_.IsActive()) {
did_associate_form_controls_timer_.StartOneShot(
TimeDelta::FromMilliseconds(300), FROM_HERE);
}
}
void Document::DidAssociateFormControlsTimerFired(TimerBase* timer) {
DCHECK_EQ(timer, &did_associate_form_controls_timer_);
if (!GetFrame() || !GetFrame()->GetPage())
return;
GetFrame()->GetPage()->GetChromeClient().DidAssociateFormControlsAfterLoad(
GetFrame());
}
float Document::DevicePixelRatio() const {
return frame_ ? frame_->DevicePixelRatio() : 1.0;
}
TextAutosizer* Document::GetTextAutosizer() {
if (!text_autosizer_)
text_autosizer_ = TextAutosizer::Create(this);
return text_autosizer_.Get();
}
void Document::SetAutofocusElement(Element* element) {
if (!element) {
autofocus_element_ = nullptr;
return;
}
if (has_autofocused_)
return;
has_autofocused_ = true;
DCHECK(!autofocus_element_);
autofocus_element_ = element;
GetTaskRunner(TaskType::kUserInteraction)
->PostTask(FROM_HERE,
WTF::Bind(&RunAutofocusTask, WrapWeakPersistent(this)));
}
Element* Document::ActiveElement() const {
if (Element* element = AdjustedFocusedElement())
return element;
return body();
}
bool Document::hasFocus() const {
return GetPage() && GetPage()->GetFocusController().IsDocumentFocused(*this);
}
const AtomicString& Document::BodyAttributeValue(
const QualifiedName& name) const {
if (auto* bodyElement = body())
return bodyElement->FastGetAttribute(name);
return g_null_atom;
}
void Document::SetBodyAttribute(const QualifiedName& name,
const AtomicString& value) {
if (auto* bodyElement = body()) {
// FIXME: This check is apparently for benchmarks that set the same value
// repeatedly. It's not clear what benchmarks though, it's also not clear
// why we don't avoid causing a style recalc when setting the same value to
// a presentational attribute in the common case.
if (bodyElement->FastGetAttribute(name) != value)
bodyElement->setAttribute(name, value);
}
}
const AtomicString& Document::bgColor() const {
return BodyAttributeValue(bgcolorAttr);
}
void Document::setBgColor(const AtomicString& value) {
SetBodyAttribute(bgcolorAttr, value);
}
const AtomicString& Document::fgColor() const {
return BodyAttributeValue(textAttr);
}
void Document::setFgColor(const AtomicString& value) {
SetBodyAttribute(textAttr, value);
}
const AtomicString& Document::alinkColor() const {
return BodyAttributeValue(alinkAttr);
}
void Document::setAlinkColor(const AtomicString& value) {
SetBodyAttribute(alinkAttr, value);
}
const AtomicString& Document::linkColor() const {
return BodyAttributeValue(linkAttr);
}
void Document::setLinkColor(const AtomicString& value) {
SetBodyAttribute(linkAttr, value);
}
const AtomicString& Document::vlinkColor() const {
return BodyAttributeValue(vlinkAttr);
}
void Document::setVlinkColor(const AtomicString& value) {
SetBodyAttribute(vlinkAttr, value);
}
template <unsigned type>
bool ShouldInvalidateNodeListCachesForAttr(
const LiveNodeListRegistry& node_lists,
const QualifiedName& attr_name) {
auto invalidation_type = static_cast<NodeListInvalidationType>(type);
if (node_lists.ContainsInvalidationType(invalidation_type) &&
LiveNodeListBase::ShouldInvalidateTypeOnAttributeChange(invalidation_type,
attr_name))
return true;
return ShouldInvalidateNodeListCachesForAttr<type + 1>(node_lists, attr_name);
}
template <>
bool ShouldInvalidateNodeListCachesForAttr<kNumNodeListInvalidationTypes>(
const LiveNodeListRegistry&,
const QualifiedName&) {
return false;
}
bool Document::ShouldInvalidateNodeListCaches(
const QualifiedName* attr_name) const {
if (attr_name) {
return ShouldInvalidateNodeListCachesForAttr<
kDoNotInvalidateOnAttributeChanges + 1>(node_lists_, *attr_name);
}
// If the invalidation is not for an attribute, invalidation is needed if
// there is any node list present (with any invalidation type).
return !node_lists_.IsEmpty();
}
void Document::InvalidateNodeListCaches(const QualifiedName* attr_name) {
for (const LiveNodeListBase* list : lists_invalidated_at_document_)
list->InvalidateCacheForAttribute(attr_name);
}
void Document::PlatformColorsChanged() {
if (!IsActive())
return;
GetStyleEngine().PlatformColorsChanged();
}
bool Document::IsSecureContext(String& error_message) const {
if (!IsSecureContext()) {
error_message = SecurityOrigin::IsPotentiallyTrustworthyErrorMessage();
return false;
}
return true;
}
bool Document::IsSecureContext() const {
bool is_secure = secure_context_state_ == SecureContextState::kSecure;
if (GetSandboxFlags() != kSandboxNone) {
UseCounter::Count(
*this, is_secure
? WebFeature::kSecureContextCheckForSandboxedOriginPassed
: WebFeature::kSecureContextCheckForSandboxedOriginFailed);
}
UseCounter::Count(*this, is_secure ? WebFeature::kSecureContextCheckPassed
: WebFeature::kSecureContextCheckFailed);
return is_secure;
}
void Document::DidEnforceInsecureRequestPolicy() {
if (!GetFrame())
return;
GetFrame()->Client()->DidEnforceInsecureRequestPolicy(
GetInsecureRequestPolicy());
}
void Document::DidEnforceInsecureNavigationsSet() {
if (!GetFrame())
return;
GetFrame()->Client()->DidEnforceInsecureNavigationsSet(
SecurityContext::SerializeInsecureNavigationSet(
*InsecureNavigationsToUpgrade()));
}
void Document::SetShadowCascadeOrder(ShadowCascadeOrder order) {
DCHECK_NE(order, ShadowCascadeOrder::kShadowCascadeNone);
if (order == shadow_cascade_order_)
return;
if (order == ShadowCascadeOrder::kShadowCascadeV0) {
may_contain_v0_shadow_ = true;
if (shadow_cascade_order_ == ShadowCascadeOrder::kShadowCascadeV1) {
// ::slotted() rules has to be moved to tree boundary rule sets.
style_engine_->V0ShadowAddedOnV1Document();
UseCounter::Count(*this, WebFeature::kMixedShadowRootV0AndV1);
}
}
// For V0 -> V1 upgrade, we need style recalculation for the whole document.
if (shadow_cascade_order_ == ShadowCascadeOrder::kShadowCascadeV0 &&
order == ShadowCascadeOrder::kShadowCascadeV1) {
SetNeedsStyleRecalc(
kSubtreeStyleChange,
StyleChangeReasonForTracing::Create(StyleChangeReason::kShadow));
UseCounter::Count(*this, WebFeature::kMixedShadowRootV0AndV1);
}
if (order > shadow_cascade_order_)
shadow_cascade_order_ = order;
}
PropertyRegistry* Document::GetPropertyRegistry() {
// TODO(timloh): When the flag is removed, return a reference instead.
if (!property_registry_ && RuntimeEnabledFeatures::CSSVariables2Enabled())
property_registry_ = PropertyRegistry::Create();
return property_registry_;
}
const PropertyRegistry* Document::GetPropertyRegistry() const {
return const_cast<Document*>(this)->GetPropertyRegistry();
}
void Document::IncrementPasswordCount() {
++password_count_;
if (IsSecureContext() || password_count_ != 1) {
// The browser process only cares about passwords on pages where the
// top-level URL is not secure. Secure contexts must have a top-level
// URL that is secure, so there is no need to send notifications for
// password fields in secure contexts.
//
// Also, only send a message on the first visible password field; the
// browser process doesn't care about the presence of additional
// password fields beyond that.
return;
}
SendSensitiveInputVisibility();
}
void Document::DecrementPasswordCount() {
DCHECK_GT(password_count_, 0u);
--password_count_;
if (IsSecureContext() || password_count_ > 0)
return;
SendSensitiveInputVisibility();
}
void Document::MaybeQueueSendDidEditFieldInInsecureContext() {
if (logged_field_edit_ || sensitive_input_edited_task_.IsActive() ||
IsSecureContext()) {
// Send a message on the first edit; the browser process doesn't care
// about the presence of additional edits.
//
// The browser process only cares about editing fields on pages where the
// top-level URL is not secure. Secure contexts must have a top-level URL
// that is secure, so there is no need to send notifications for editing
// in secure contexts.
return;
}
logged_field_edit_ = true;
sensitive_input_edited_task_ = PostCancellableTask(
*GetTaskRunner(TaskType::kUserInteraction), FROM_HERE,
WTF::Bind(&Document::SendDidEditFieldInInsecureContext,
WrapWeakPersistent(this)));
}
CoreProbeSink* Document::GetProbeSink() {
LocalFrame* frame = GetFrame();
if (!frame && TemplateDocumentHost())
frame = TemplateDocumentHost()->GetFrame();
return probe::ToCoreProbeSink(frame);
}
service_manager::InterfaceProvider* Document::GetInterfaceProvider() {
if (!GetFrame())
return nullptr;
return &GetFrame()->GetInterfaceProvider();
}
scoped_refptr<WebTaskRunner> Document::GetTaskRunner(TaskType type) {
DCHECK(IsMainThread());
if (ContextDocument() && ContextDocument()->GetFrame())
return ContextDocument()->GetFrame()->GetTaskRunner(type);
// In most cases, ContextDocument() will get us to a relevant Frame. In some
// cases, though, there isn't a good candidate (most commonly when either the
// passed-in document or ContextDocument() used to be attached to a Frame but
// has since been detached).
return Platform::Current()->CurrentThread()->GetWebTaskRunner();
}
Policy* Document::policy() {
if (!policy_)
policy_ = new DocumentPolicy(this);
return policy_.Get();
}
void Document::Trace(blink::Visitor* visitor) {
visitor->Trace(imports_controller_);
visitor->Trace(doc_type_);
visitor->Trace(implementation_);
visitor->Trace(autofocus_element_);
visitor->Trace(focused_element_);
visitor->Trace(sequential_focus_navigation_starting_point_);
visitor->Trace(hover_element_);
visitor->Trace(active_element_);
visitor->Trace(document_element_);
visitor->Trace(root_scroller_controller_);
visitor->Trace(title_element_);
visitor->Trace(ax_object_cache_);
visitor->Trace(markers_);
visitor->Trace(css_target_);
visitor->Trace(current_script_stack_);
visitor->Trace(script_runner_);
visitor->Trace(lists_invalidated_at_document_);
visitor->Trace(node_lists_);
visitor->Trace(top_layer_elements_);
visitor->Trace(elem_sheet_);
visitor->Trace(node_iterators_);
visitor->Trace(ranges_);
visitor->Trace(style_engine_);
visitor->Trace(form_controller_);
visitor->Trace(visited_link_state_);
visitor->Trace(frame_);
visitor->Trace(dom_window_);
visitor->Trace(fetcher_);
visitor->Trace(parser_);
visitor->Trace(context_features_);
visitor->Trace(style_sheet_list_);
visitor->Trace(document_timing_);
visitor->Trace(media_query_matcher_);
visitor->Trace(scripted_animation_controller_);
visitor->Trace(scripted_idle_task_controller_);
visitor->Trace(text_autosizer_);
visitor->Trace(registration_context_);
visitor->Trace(custom_element_microtask_run_queue_);
visitor->Trace(element_data_cache_);
visitor->Trace(use_elements_needing_update_);
visitor->Trace(timers_);
visitor->Trace(template_document_);
visitor->Trace(template_document_host_);
visitor->Trace(user_action_elements_);
visitor->Trace(svg_extensions_);
visitor->Trace(timeline_);
visitor->Trace(pending_animations_);
visitor->Trace(worklet_animation_controller_);
visitor->Trace(context_document_);
visitor->Trace(canvas_font_cache_);
visitor->Trace(intersection_observer_controller_);
visitor->Trace(snap_coordinator_);
visitor->Trace(resize_observer_controller_);
visitor->Trace(property_registry_);
visitor->Trace(network_state_observer_);
visitor->Trace(policy_);
Supplementable<Document>::Trace(visitor);
TreeScope::Trace(visitor);
ContainerNode::Trace(visitor);
ExecutionContext::Trace(visitor);
SecurityContext::Trace(visitor);
DocumentShutdownNotifier::Trace(visitor);
SynchronousMutationNotifier::Trace(visitor);
}
void Document::RecordDeferredLoadReason(WouldLoadReason reason) {
DCHECK(would_load_reason_ == WouldLoadReason::kInvalid ||
reason != WouldLoadReason::kCreated);
DCHECK(reason != WouldLoadReason::kInvalid);
DCHECK(GetFrame());
DCHECK(GetFrame()->IsCrossOriginSubframe());
if (reason <= would_load_reason_ ||
!GetFrame()->Loader().StateMachine()->CommittedFirstRealDocumentLoad())
return;
for (int i = static_cast<int>(would_load_reason_) + 1;
i <= static_cast<int>(reason); ++i)
RecordLoadReasonToHistogram(static_cast<WouldLoadReason>(i));
would_load_reason_ = reason;
}
void Document::RecordUkmOutliveTimeAfterShutdown(int outlive_time_count) {
if (!needs_to_record_ukm_outlive_time_)
return;
DCHECK(ukm_recorder_);
DCHECK(ukm_source_id_ != ukm::kInvalidSourceId);
ukm::builders::Document_OutliveTimeAfterShutdown(ukm_source_id_)
.SetGCCount(outlive_time_count)
.Record(ukm_recorder_.get());
}
void Document::TraceWrappers(const ScriptWrappableVisitor* visitor) const {
// node_lists_ are traced in their corresponding NodeListsNodeData, keeping
// them only alive for live nodes. Otherwise we would keep lists of dead
// nodes alive that have not yet been invalidated.
visitor->TraceWrappers(imports_controller_);
visitor->TraceWrappers(parser_);
visitor->TraceWrappers(implementation_);
visitor->TraceWrappers(style_sheet_list_);
visitor->TraceWrappers(style_engine_);
visitor->TraceWrappers(script_runner_);
visitor->TraceWrappers(scripted_animation_controller_);
visitor->TraceWrappers(scripted_idle_task_controller_);
visitor->TraceWrappers(intersection_observer_controller_);
ContainerNode::TraceWrappers(visitor);
ExecutionContext::TraceWrappers(visitor);
Supplementable<Document>::TraceWrappers(visitor);
}
template class CORE_TEMPLATE_EXPORT Supplement<Document>;
} // namespace blink
#ifndef NDEBUG
static WeakDocumentSet& liveDocumentSet() {
DEFINE_STATIC_LOCAL(WeakDocumentSet, set, ());
return set;
}
void showLiveDocumentInstances() {
WeakDocumentSet& set = liveDocumentSet();
fprintf(stderr, "There are %u documents currently alive:\n", set.size());
for (blink::Document* document : set)
fprintf(stderr, "- Document %p URL: %s\n", document,
document->Url().GetString().Utf8().data());
}
#endif