0

Adjust getHTML behavior to match recent consensus

This includes two changes:

1. Rename includeShadowRoots to serializableShadowRoots
2. Do not throw an exception if serializableShadowRoots is false
   but shadowRoots is non-empty.

I also added IDL defaults for both parameters to clean up the C++.
I also added a test of the use counter for getHTML().

Bug: 41490936
Change-Id: Ieedd81b5165d9652c3a7b68dea4a4fa5fb088560
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5304121
Auto-Submit: Mason Freed <masonf@chromium.org>
Commit-Queue: Mason Freed <masonf@chromium.org>
Reviewed-by: Di Zhang <dizhangg@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1262930}
This commit is contained in:
Mason Freed
2024-02-20 21:54:16 +00:00
committed by Chromium LUCI CQ
parent 0cd121fecf
commit 9ab453c8e7
5 changed files with 38 additions and 32 deletions
third_party/blink
renderer
web_tests
editing
external
wpt
shadow-dom
shadow-dom

@ -1730,22 +1730,16 @@ String ContainerNode::getInnerHTML(const GetInnerHTMLOptions* options) const {
String ContainerNode::getHTML(const GetHTMLOptions* options,
ExceptionState& exception_state) const {
CHECK(RuntimeEnabledFeatures::ElementGetHTMLEnabled());
DCHECK(options && options->hasSerializableShadowRoots())
<< "Should have IDL default";
DCHECK(options->hasShadowRoots()) << "Should have IDL default";
DCHECK(IsShadowRoot() || IsElementNode());
if (options->hasIncludeShadowRoots() && !options->includeShadowRoots() &&
options->hasShadowRoots() && !options->shadowRoots().empty()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"If includeShadowRoots is false, then shadowRoots must be empty.");
return "";
}
ShadowRootInclusion shadow_root_inclusion{
options->hasIncludeShadowRoots() && options->includeShadowRoots()
options->serializableShadowRoots()
? ShadowRootInclusion::Behavior::kIncludeAllSerializableShadowRoots
: ShadowRootInclusion::Behavior::kOnlyProvidedShadowRoots};
if (options->hasShadowRoots()) {
for (auto& shadow_root : options->shadowRoots()) {
shadow_root_inclusion.include_shadow_roots.insert(shadow_root);
}
for (auto& shadow_root : options->shadowRoots()) {
shadow_root_inclusion.include_shadow_roots.insert(shadow_root);
}
return CreateMarkup(this, kChildrenOnly, kDoNotResolveURLs,
shadow_root_inclusion);

@ -3,6 +3,6 @@
// found in the LICENSE file.
dictionary GetHTMLOptions {
boolean includeShadowRoots;
sequence<ShadowRoot> shadowRoots;
boolean serializableShadowRoots = false;
sequence<ShadowRoot> shadowRoots = [];
};

@ -46,7 +46,7 @@ function testPaste(originalMarkupOrElement, expected, descr) {
nodeDest.focus();
assert_true(document.execCommand("SelectAll", false));
assert_true(document.execCommand("Paste", false));
confirmedMarkup = nodeDest.getInnerHTML({includeShadowRoots: true});
confirmedMarkup = nodeDest.getHTML({serializableShadowRoots: true});
nodeDest.remove();
assert_equals(confirmedMarkup, expected);
@ -71,10 +71,10 @@ testPaste("<div><b><i><span style=\"font-weight: normal\">plain text<b><i>bold i
testPaste("<span>foo</span><template>Hello</template><span>bar</span>", "foobar");
// Note that an empty <div> is left in the resulting pasted text, which isn't ideal:
testPaste("<span>foo</span><div><template>Hello</template></div><span>bar</span>", "foo<div></div>bar");
// Contained shadow DOM is stripped, but content is retained (streaming DSD API, shadowrootmode attribute):
testPaste("<span>foo</span><div><template shadowrootmode='open'>Hello</template></div><span>bar</span>", "foo<div>Hello</div>bar");
testPaste("<span>foo</span><div><template shadowrootmode='open'><slot></slot>DEF</template>ABC</div><span>bar</span>", "foo<div><slot>ABC</slot>DEF</div>bar");
// Make sure non-declarative <template shadowrootmode> doesn't form a shadow root on paste:
// Contained shadow DOM is stripped, but content is retained:
testPaste("<span>foo</span><div><template shadowrootmode='open' serializable>Hello</template></div><span>bar</span>", "foo<div>Hello</div>bar");
testPaste("<span>foo</span><div><template shadowrootmode='open' serializable><slot></slot>DEF</template>ABC</div><span>bar</span>", "foo<div><slot>ABC</slot>DEF</div>bar");
// Make sure regular (non-DSD) template doesn't form a shadow root on paste:
const nodeWithTemplate = createEditable("<span>foo</span><div>ABC<template>Hello</template></div><span>bar</span>");
nodeWithTemplate.querySelector('template').setAttribute('shadowrootmode','open');
testPaste(nodeWithTemplate, "foo<div>ABC</div>bar");

@ -69,29 +69,28 @@ function testElementType(allowsShadowDom, elementType, runGetHTMLOnShadowRoot, d
const emptyElement = `<${elementType}></${elementType}>`;
if (isOpen) {
if (expectedSerializable) {
assert_equals(wrapper.getHTML({includeShadowRoots: true}), correctHtml);
assert_equals(wrapper.getHTML({serializableShadowRoots: true}), correctHtml);
} else {
assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement);
assert_equals(wrapper.getHTML({serializableShadowRoots: true}), emptyElement);
}
} else {
// Closed shadow roots should not be returned unless shadowRoots specifically contains the shadow root:
assert_equals(wrapper.getHTML({includeShadowRoots: true}), emptyElement);
assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: []}), emptyElement);
assert_equals(wrapper.getHTML({serializableShadowRoots: true}), emptyElement);
assert_equals(wrapper.getHTML({serializableShadowRoots: true, shadowRoots: []}), emptyElement);
}
// If we provide the shadow root, serialize it, regardless of includeShadowRoots.
assert_equals(wrapper.getHTML({includeShadowRoots: true, shadowRoots: [shadowRoot]}),correctHtml);
// If we provide the shadow root, serialize it, regardless of serializableShadowRoots.
assert_equals(wrapper.getHTML({serializableShadowRoots: true, shadowRoots: [shadowRoot]}),correctHtml);
assert_equals(wrapper.getHTML({serializableShadowRoots: false, shadowRoots: [shadowRoot]}),correctHtml);
assert_equals(wrapper.getHTML({shadowRoots: [shadowRoot]}),correctHtml);
// This should always throw - includeShadowRoots false, but we've provided roots.
assert_throws_dom("NotSupportedError",() => wrapper.getHTML({includeShadowRoots: false, shadowRoots: [shadowRoot]}));
} else {
// For non-shadow hosts, getHTML() should also match .innerHTML
assert_equals(wrapper.getHTML({includeShadowRoots: true}),wrapper.innerHTML);
assert_equals(wrapper.getHTML({serializableShadowRoots: true}),wrapper.innerHTML);
}
// Either way, make sure getHTML({includeShadowRoots: false}) matches .innerHTML
assert_equals(wrapper.getHTML({includeShadowRoots: false}),wrapper.innerHTML,'getHTML() with includeShadowRoots false should return the same as .innerHTML');
// ...and that the default for includeShadowRoots is false.
assert_equals(wrapper.getHTML(),wrapper.innerHTML,'The default for includeShadowRoots should be false');
// Either way, make sure getHTML({serializableShadowRoots: false}) matches .innerHTML
assert_equals(wrapper.getHTML({serializableShadowRoots: false}),wrapper.innerHTML,'getHTML() with serializableShadowRoots false should return the same as .innerHTML');
// ...and that the default for serializableShadowRoots is false.
assert_equals(wrapper.getHTML(),wrapper.innerHTML,'The default for serializableShadowRoots should be false');
}, `${runGetHTMLOnShadowRoot ? 'ShadowRoot' : 'Element'}.getHTML() on <${elementType}>${allowsShadowDom ? `, with ${declarativeShadowDom ? 'declarative' : 'imperative'} shadow, mode=${mode}, delegatesFocus=${delegatesFocus}, serializable=${serializable}, clonable=${clonable}.` : ''}`);
}

@ -0,0 +1,13 @@
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body>
<script>
test(() => {
const kElementGetHTML = 4781; // From web_feature.mojom
assert_false(internals.isUseCounted(document, kElementGetHTML));
document.body.getHTML({serializableShadowRoots: true});
assert_true(internals.isUseCounted(document, kElementGetHTML));
}, 'Use of getHTML is use counted.');
</script>