From 8d72f610801bb29d2aec4cec1b9a5105a0f8bde1 Mon Sep 17 00:00:00 2001 From: Mason Freed Date: Mon, 20 Apr 2020 21:56:52 +0000 Subject: [PATCH] Clean up Element::attachShadow() variants This CL cleans up the variations of element::attachShadow(), refactoring the common subset back into attachShadow(). It also replaces the bool delegates_focus and manual_slotting parameters with enum versions that are common across declarative and imperative calls. This CL should not change any functionality. The spec text comes from my two PRs against DOM [1] and HTML [2]. [1] https://github.com/whatwg/dom/pull/858 [2] https://github.com/whatwg/html/pull/5465 Bug: 1042130 Change-Id: I3835b9d344d8005b6854909f287083cd984e832e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2155144 Commit-Queue: Mason Freed Auto-Submit: Mason Freed Reviewed-by: Kouhei Ueno Cr-Commit-Position: refs/heads/master@{#760704} --- .../blink/renderer/core/dom/element.cc | 153 +++++++++++------- third_party/blink/renderer/core/dom/element.h | 16 +- .../blink/renderer/core/dom/shadow_root.h | 4 - .../web_frame_serializer_sanitization_test.cc | 26 +-- 4 files changed, 117 insertions(+), 82 deletions(-) diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc index fc2583a91acbf9..75ee0f5cbc10d4 100644 --- a/third_party/blink/renderer/core/dom/element.cc +++ b/third_party/blink/renderer/core/dom/element.cc @@ -3513,29 +3513,6 @@ ShadowRoot& Element::CreateAndAttachShadowRoot(ShadowRootType type) { return *shadow_root; } -void Element::AttachDeclarativeShadowRoot(HTMLTemplateElement* template_element, - ShadowRootType type, - FocusDelegation focus_delegation, - SlotAssignmentMode slot_assignment) { - DCHECK(template_element); - DCHECK(type == ShadowRootType::kOpen || type == ShadowRootType::kClosed); - if (!CanAttachShadowRoot()) { - // TODO(1067488): Eventually, this should be a DOMException. - LOG(ERROR) << "Invalid shadow root host element"; - return; - } - if (GetShadowRoot()) { - // TODO(1067488): Eventually, this should be a DOMException. - LOG(ERROR) << "Shadow root already present!"; - return; - } - ShadowRoot* shadow_root = &AttachShadowRootInternal( - type, focus_delegation == FocusDelegation::kDelegateFocus, - slot_assignment == SlotAssignmentMode::kManual); - shadow_root->appendChild(template_element->DeclarativeShadowContent()); - template_element->remove(); -} - ShadowRoot* Element::GetShadowRoot() const { return HasRareData() ? GetElementRareData()->GetShadowRoot() : nullptr; } @@ -3644,6 +3621,7 @@ ElementInternals& Element::EnsureElementInternals() { } ShadowRoot* Element::createShadowRoot(ExceptionState& exception_state) { + DCHECK(RuntimeEnabledFeatures::ShadowDOMV0Enabled(&GetDocument())); if (ShadowRoot* root = GetShadowRoot()) { if (root->IsUserAgent()) { exception_state.ThrowDOMException( @@ -3677,6 +3655,15 @@ ShadowRoot* Element::createShadowRoot(ExceptionState& exception_state) { return &CreateShadowRootInternal(); } +ShadowRoot& Element::CreateShadowRootInternal() { + DCHECK(RuntimeEnabledFeatures::ShadowDOMV0Enabled(&GetDocument())); + DCHECK(!ClosedShadowRoot()); + DCHECK(AreAuthorShadowsAllowed()); + DCHECK(!AlwaysCreateUserAgentShadowRoot()); + GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV0); + return CreateAndAttachShadowRoot(ShadowRootType::V0); +} + bool Element::CanAttachShadowRoot() const { const AtomicString& tag_name = localName(); // Checking Is{V0}CustomElement() here is just an optimization @@ -3696,17 +3683,26 @@ bool Element::CanAttachShadowRoot() const { tag_name == html_names::kSpanTag; } -ShadowRoot* Element::attachShadow(const ShadowRootInit* shadow_root_init_dict, - ExceptionState& exception_state) { - DCHECK(shadow_root_init_dict->hasMode()); +const char* Element::ErrorMessageForAttachShadow() const { + // https://dom.spec.whatwg.org/#concept-attach-a-shadow-root + // 1. If shadow host’s namespace is not the HTML namespace, then throw a + // "NotSupportedError" DOMException. + // 2. If shadow host’s local name is not a valid custom element name, + // "article", "aside", "blockquote", "body", "div", "footer", "h1", "h2", + // "h3", "h4", "h5", "h6", "header", "main", "nav", "p", "section", or "span", + // then throw a "NotSupportedError" DOMException. if (!CanAttachShadowRoot()) { - exception_state.ThrowDOMException( - DOMExceptionCode::kNotSupportedError, - "This element does not support attachShadow"); - return nullptr; + return "This element does not support attachShadow"; } - // Checking IsCustomElement() here is just an optimization because + // 3. If shadow host’s local name is a valid custom element name, or shadow + // host’s is value is not null, then: + // 3.1 Let definition be the result of looking up a custom element + // definition given shadow host’s node document, its namespace, its local + // name, and its is value. + // 3.2 If definition is not null and definition’s + // disable shadow is true, then throw a "NotSupportedError" DOMException. + // Note: Checking IsCustomElement() is just an optimization because // IsValidName() is not cheap. if (IsCustomElement() && (CustomElement::IsValidName(localName()) || !IsValue().IsNull())) { @@ -3716,45 +3712,75 @@ ShadowRoot* Element::attachShadow(const ShadowRootInit* shadow_root_init_dict, : IsValue()) : nullptr; if (definition && definition->DisableShadow()) { - exception_state.ThrowDOMException( - DOMExceptionCode::kNotSupportedError, - "attachShadow() is disabled by disabledFeatures static field."); - return nullptr; + return "attachShadow() is disabled by disabledFeatures static field."; } } + // 4. If shadow host has a non-null shadow root whose is declarative shadow + // root property is false, then throw an "NotSupportedError" DOMException. + // 5. TODO(masonfreed): If shadow host has a non-null shadow root whose is + // declarative shadow root property is true, then remove all of shadow root’s + // children, in tree order. Return shadow host’s shadow root. if (GetShadowRoot()) { - exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError, - "Shadow root cannot be created on a host " - "which already hosts a shadow tree."); - return nullptr; + return "Shadow root cannot be created on a host " + "which already hosts a shadow tree."; } + return nullptr; +} +ShadowRoot* Element::attachShadow(const ShadowRootInit* shadow_root_init_dict, + ExceptionState& exception_state) { + DCHECK(shadow_root_init_dict->hasMode()); ShadowRootType type = shadow_root_init_dict->mode() == "open" ? ShadowRootType::kOpen : ShadowRootType::kClosed; - if (type == ShadowRootType::kOpen) UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowOpen); else UseCounter::Count(GetDocument(), WebFeature::kElementAttachShadowClosed); - DCHECK(!shadow_root_init_dict->hasMode() || !GetShadowRoot()); - bool delegates_focus = shadow_root_init_dict->hasDelegatesFocus() && - shadow_root_init_dict->delegatesFocus(); - bool manual_slotting = false; - if (shadow_root_init_dict->hasSlotAssignment()) { - manual_slotting = (shadow_root_init_dict->slotAssignment() == "manual"); + auto focus_delegation = (shadow_root_init_dict->hasDelegatesFocus() && + shadow_root_init_dict->delegatesFocus()) + ? FocusDelegation::kDelegateFocus + : FocusDelegation::kNone; + auto slot_assignment = (shadow_root_init_dict->hasSlotAssignment() && + shadow_root_init_dict->slotAssignment() == "manual") + ? SlotAssignmentMode::kManual + : SlotAssignmentMode::kAuto; + + if (const char* error_message = ErrorMessageForAttachShadow()) { + exception_state.ThrowDOMException(DOMExceptionCode::kNotSupportedError, + error_message); + return nullptr; } - return &AttachShadowRootInternal(type, delegates_focus, manual_slotting); + return &AttachShadowRootInternal(type, focus_delegation, slot_assignment); } -ShadowRoot& Element::CreateShadowRootInternal() { - DCHECK(!ClosedShadowRoot()); - DCHECK(AreAuthorShadowsAllowed()); - DCHECK(!AlwaysCreateUserAgentShadowRoot()); - GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV0); - return CreateAndAttachShadowRoot(ShadowRootType::V0); +void Element::AttachDeclarativeShadowRoot(HTMLTemplateElement* template_element, + ShadowRootType type, + FocusDelegation focus_delegation, + SlotAssignmentMode slot_assignment) { + DCHECK(template_element); + DCHECK(type == ShadowRootType::kOpen || type == ShadowRootType::kClosed); + + // 12. Run attach a shadow root with shadow host equal to declarative shadow + // host element, mode equal to declarative shadow mode, and delegates focus + // equal to declarative shadow delegates focus. If an exception was thrown by + // attach a shadow root, catch it, and report the exception. + if (const char* error_message = ErrorMessageForAttachShadow()) { + // TODO(1067488): Fire this exception at Window. + LOG(ERROR) << error_message; + return; + } + ShadowRoot& shadow_root = + AttachShadowRootInternal(type, focus_delegation, slot_assignment); + // 13.1. TODO(masonfreed): Set declarative shadow host element's shadow host's + // is declarative shadow root property to true. + // 13.2. Append the declarative template element's DocumentFragment to the + // newly-created shadow root. + shadow_root.appendChild(template_element->DeclarativeShadowContent()); + // 13.3. Remove the declarative template element from the document. + template_element->remove(); } ShadowRoot& Element::CreateUserAgentShadowRoot() { @@ -3762,9 +3788,10 @@ ShadowRoot& Element::CreateUserAgentShadowRoot() { return CreateAndAttachShadowRoot(ShadowRootType::kUserAgent); } -ShadowRoot& Element::AttachShadowRootInternal(ShadowRootType type, - bool delegates_focus, - bool manual_slotting) { +ShadowRoot& Element::AttachShadowRootInternal( + ShadowRootType type, + FocusDelegation focus_delegation, + SlotAssignmentMode slot_assignment_mode) { // SVG is a special case for using this API to create a closed shadow // root. DCHECK(CanAttachShadowRoot() || IsA(*this)); @@ -3773,11 +3800,17 @@ ShadowRoot& Element::AttachShadowRootInternal(ShadowRootType type, DCHECK(!AlwaysCreateUserAgentShadowRoot()); GetDocument().SetShadowCascadeOrder(ShadowCascadeOrder::kShadowCascadeV1); + + // 6. Let shadow be a new shadow root whose node document is shadow host’s + // node document, host is shadow host, and mode is mode. + // 9. Set shadow host’s shadow root to shadow. ShadowRoot& shadow_root = CreateAndAttachShadowRoot(type); - shadow_root.SetDelegatesFocus(delegates_focus); - shadow_root.SetSlotAssignmentMode(manual_slotting - ? SlotAssignmentMode::kManual - : SlotAssignmentMode::kAuto); + // 7. Set shadow’s delegates focus to delegates focus. + // 8. TODO(masonfreed): Set shadow’s is declarative shadow root property to + // false. + shadow_root.SetDelegatesFocus(focus_delegation == + FocusDelegation::kDelegateFocus); + shadow_root.SetSlotAssignmentMode(slot_assignment_mode); return shadow_root; } diff --git a/third_party/blink/renderer/core/dom/element.h b/third_party/blink/renderer/core/dom/element.h index 541b0321bd19b7..f0be5a969282d4 100644 --- a/third_party/blink/renderer/core/dom/element.h +++ b/third_party/blink/renderer/core/dom/element.h @@ -113,8 +113,9 @@ enum class ElementFlags { }; enum class ShadowRootType; -enum class FocusDelegation; -enum class SlotAssignmentMode; + +enum class SlotAssignmentMode { kManual, kAuto }; +enum class FocusDelegation { kNone, kDelegateFocus }; enum class SelectionBehaviorOnFocus { kReset, @@ -565,6 +566,7 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable { // throws an exception. Multiple shadow roots are allowed only when // createShadowRoot() is used without any parameters from JavaScript. ShadowRoot* createShadowRoot(ExceptionState&); + ShadowRoot* attachShadow(const ShadowRootInit*, ExceptionState&); void AttachDeclarativeShadowRoot(HTMLTemplateElement*, @@ -576,9 +578,10 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable { return CreateShadowRootInternal(); } ShadowRoot& CreateUserAgentShadowRoot(); - ShadowRoot& AttachShadowRootInternal(ShadowRootType, - bool delegates_focus = false, - bool manual_slotting = false); + ShadowRoot& AttachShadowRootInternal( + ShadowRootType, + FocusDelegation focus_delegation = FocusDelegation::kNone, + SlotAssignmentMode slot_assignment_mode = SlotAssignmentMode::kAuto); // Returns the shadow root attached to this element if it is a shadow host. ShadowRoot* GetShadowRoot() const; @@ -1031,8 +1034,9 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable { bool IsDocumentNode() const = delete; // This will catch anyone doing an unnecessary check. - bool CanAttachShadowRoot() const; ShadowRoot& CreateShadowRootInternal(); + bool CanAttachShadowRoot() const; + const char* ErrorMessageForAttachShadow() const; void StyleAttributeChanged(const AtomicString& new_style_string, AttributeModificationReason); diff --git a/third_party/blink/renderer/core/dom/shadow_root.h b/third_party/blink/renderer/core/dom/shadow_root.h index 7a0290d0790f98..5182ce2ccb930c 100644 --- a/third_party/blink/renderer/core/dom/shadow_root.h +++ b/third_party/blink/renderer/core/dom/shadow_root.h @@ -46,10 +46,6 @@ class WhitespaceAttacher; enum class ShadowRootType { V0, kOpen, kClosed, kUserAgent }; -enum class SlotAssignmentMode { kManual, kAuto }; - -enum class FocusDelegation { kNone, kDelegateFocus }; - class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope { DEFINE_WRAPPERTYPEINFO(); USING_GARBAGE_COLLECTED_MIXIN(ShadowRoot); diff --git a/third_party/blink/renderer/core/exported/web_frame_serializer_sanitization_test.cc b/third_party/blink/renderer/core/exported/web_frame_serializer_sanitization_test.cc index 7417837c7a2b12..224132811f2fe7 100644 --- a/third_party/blink/renderer/core/exported/web_frame_serializer_sanitization_test.cc +++ b/third_party/blink/renderer/core/exported/web_frame_serializer_sanitization_test.cc @@ -94,21 +94,23 @@ class WebFrameSerializerSanitizationTest : public testing::Test { test::RunPendingTasks(); } - ShadowRoot* SetShadowContent(TreeScope& scope, - const char* host, - ShadowRootType shadow_type, - const char* shadow_content, - bool delegates_focus = false) { + ShadowRoot* SetShadowContent( + TreeScope& scope, + const char* host, + ShadowRootType shadow_type, + const char* shadow_content, + FocusDelegation focus_delegation = FocusDelegation::kNone) { Element* host_element = scope.getElementById(AtomicString::FromUTF8(host)); ShadowRoot* shadow_root; if (shadow_type == ShadowRootType::V0) { - DCHECK(!delegates_focus); + DCHECK_EQ(focus_delegation, FocusDelegation::kNone); shadow_root = &host_element->CreateV0ShadowRootForTesting(); } else { - shadow_root = - &host_element->AttachShadowRootInternal(shadow_type, delegates_focus); + shadow_root = &host_element->AttachShadowRootInternal(shadow_type, + focus_delegation); } - shadow_root->SetDelegatesFocus(delegates_focus); + shadow_root->SetDelegatesFocus(focus_delegation == + FocusDelegation::kDelegateFocus); shadow_root->setInnerHTML(String::FromUTF8(shadow_content), ASSERT_NO_EXCEPTION); scope.GetDocument().View()->UpdateAllLifecyclePhases( @@ -337,9 +339,9 @@ TEST_F(WebFrameSerializerSanitizationTest, ShadowDOM) { LoadFrame("http://www.test.com", "shadow_dom.html", "text/html"); Document* document = MainFrameImpl()->GetFrame()->GetDocument(); SetShadowContent(*document, "h1", ShadowRootType::V0, "V0 shadow"); - ShadowRoot* shadowRoot = - SetShadowContent(*document, "h2", ShadowRootType::kOpen, - "Parent shadow\n

Foo

", true); + ShadowRoot* shadowRoot = SetShadowContent( + *document, "h2", ShadowRootType::kOpen, + "Parent shadow\n

Foo

", FocusDelegation::kDelegateFocus); SetShadowContent(*shadowRoot, "h3", ShadowRootType::kClosed, "Nested shadow"); String mhtml = WebFrameSerializerTestHelper::GenerateMHTML(MainFrameImpl());