Skip to content

Commit

Permalink
Clean up Element::attachShadow() variants
Browse files Browse the repository at this point in the history
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] whatwg/dom#858
[2] whatwg/html#5465

Bug: 1042130
Change-Id: I3835b9d344d8005b6854909f287083cd984e832e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2155144
Commit-Queue: Mason Freed <masonfreed@chromium.org>
Auto-Submit: Mason Freed <masonfreed@chromium.org>
Reviewed-by: Kouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#760704}
  • Loading branch information
mfreed7 authored and Commit Bot committed Apr 20, 2020
1 parent 07ba894 commit 8d72f61
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 82 deletions.
153 changes: 93 additions & 60 deletions third_party/blink/renderer/core/dom/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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
Expand All @@ -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())) {
Expand All @@ -3716,55 +3712,86 @@ 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() {
DCHECK(!GetShadowRoot());
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 <use> is a special case for using this API to create a closed shadow
// root.
DCHECK(CanAttachShadowRoot() || IsA<SVGUseElement>(*this));
Expand All @@ -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;
}

Expand Down
16 changes: 10 additions & 6 deletions third_party/blink/renderer/core/dom/element.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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*,
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 0 additions & 4 deletions third_party/blink/renderer/core/dom/shadow_root.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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<p id=\"h3\">Foo</p>", true);
ShadowRoot* shadowRoot = SetShadowContent(
*document, "h2", ShadowRootType::kOpen,
"Parent shadow\n<p id=\"h3\">Foo</p>", FocusDelegation::kDelegateFocus);
SetShadowContent(*shadowRoot, "h3", ShadowRootType::kClosed, "Nested shadow");
String mhtml = WebFrameSerializerTestHelper::GenerateMHTML(MainFrameImpl());

Expand Down

0 comments on commit 8d72f61

Please sign in to comment.