Commit 62767f8a authored by Mason Freed's avatar Mason Freed Committed by Commit Bot

Add ShadowRoot.getInnerHTML()

The getInnerHTML API is relatively new, and was not added to
the ShadowRoot object. This CL fixes that gap, and adds
testing for both getInnerHTML and setInnerHTML on ShadowRoot.

Bug: 1042130
Change-Id: Ibae20d7c0e9f44b92932499234e493ba798e06b0
Fixed: 1147752
Cq-Do-Not-Cancel-Tryjobs: true
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2536372
Commit-Queue: Mason Freed <masonfreed@chromium.org>
Auto-Submit: Mason Freed <masonfreed@chromium.org>
Reviewed-by: default avatarKouhei Ueno <kouhei@chromium.org>
Cr-Commit-Position: refs/heads/master@{#827377}
parent 64cb0f0a
......@@ -27,6 +27,7 @@
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_get_inner_html_options.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_set_inner_html_options.h"
#include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h"
......@@ -117,6 +118,21 @@ String ShadowRoot::innerHTML() const {
return CreateMarkup(this, kChildrenOnly);
}
String ShadowRoot::getInnerHTML(const GetInnerHTMLOptions* options) const {
DCHECK(RuntimeEnabledFeatures::DeclarativeShadowDOMEnabled(
GetExecutionContext()));
ClosedRootsSet include_closed_roots;
if (options->hasClosedRoots()) {
for (auto& shadow_root : options->closedRoots()) {
include_closed_roots.insert(shadow_root);
}
}
return CreateMarkup(
this, kChildrenOnly, kDoNotResolveURLs,
options->includeShadowRoots() ? kIncludeShadowRoots : kNoShadowRoots,
include_closed_roots);
}
void ShadowRoot::SetInnerHTMLInternal(const String& html,
const SetInnerHTMLOptions* options,
ExceptionState& exception_state) {
......
......@@ -41,6 +41,7 @@ namespace blink {
class Document;
class ExceptionState;
class SetInnerHTMLOptions;
class GetInnerHTMLOptions;
class ShadowRootV0;
class SlotAssignment;
class WhitespaceAttacher;
......@@ -138,6 +139,7 @@ class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope {
Element* ActiveElement() const;
String innerHTML() const;
String getInnerHTML(const GetInnerHTMLOptions* options) const;
void setInnerHTML(const String&, ExceptionState& = ASSERT_NO_EXCEPTION);
void setInnerHTMLWithOptions(const String&,
const SetInnerHTMLOptions*,
......
......@@ -36,9 +36,9 @@ interface ShadowRoot : DocumentFragment {
readonly attribute boolean delegatesFocus;
[RuntimeEnabled=ManualSlotting] readonly attribute SlotAssignmentMode slotAssignment;
// Declarative Shadow DOM setInnerHTML function.
// TODO(crbug.com/1147752): Add getInnerHTML() here also.
[Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RaisesException, ImplementedAs=setInnerHTMLWithOptions] void setInnerHTML(HTMLString html, optional SetInnerHTMLOptions options = {});
// Declarative Shadow DOM setInnerHTML/getInnerHTML() functions.
[Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RuntimeCallStatsCounter=ElementSetInnerHTML, RaisesException, ImplementedAs=setInnerHTMLWithOptions] void setInnerHTML(HTMLString html, optional SetInnerHTMLOptions options = {});
[Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RuntimeCallStatsCounter=ElementGetInnerHTML] HTMLString getInnerHTML(optional GetInnerHTMLOptions options = {});
};
ShadowRoot includes DocumentOrShadowRoot;
......@@ -9,12 +9,20 @@
<body>
<script>
function testElementType(allowsShadowDom, elementType, mode, delegatesFocus) {
var t = test(t => {
function testElementType(allowsShadowDom, elementType, applyToShadow, mode, delegatesFocus) {
const t = test(t => {
// Create and attach element
const wrapper = document.createElement('div');
t.add_cleanup(function() { wrapper.remove(); });
document.body.appendChild(wrapper);
let wrapper;
if (applyToShadow) {
const host = document.createElement('div');
t.add_cleanup(function() { host.remove(); });
document.body.appendChild(host);
wrapper = host.attachShadow({mode: 'open'});
} else {
wrapper = document.createElement('div');
t.add_cleanup(function() { wrapper.remove(); });
document.body.appendChild(wrapper);
}
const element = document.createElement(elementType);
wrapper.appendChild(element);
......@@ -44,7 +52,7 @@ function testElementType(allowsShadowDom, elementType, mode, delegatesFocus) {
// Either way, make sure getInnerHTML({includeShadowRoots: false}) matches .innerHTML
assert_equals(wrapper.getInnerHTML({includeShadowRoots: false}),wrapper.innerHTML,'getInnerHTML() with includeShadowRoots false should return the same as .innerHTML');
}, `getInnerHTML() on <${elementType}>${allowsShadowDom ? `, with mode=${mode}, delegatesFocus=${delegatesFocus}.` : ''}`);
}, `${applyToShadow ? 'ShadowRoot' : 'Element'}.getInnerHTML() on <${elementType}>${allowsShadowDom ? `, with mode=${mode}, delegatesFocus=${delegatesFocus}.` : ''}`);
}
function runAllTests() {
......@@ -52,14 +60,16 @@ function runAllTests() {
const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS;
for (const elementName of allElements) {
const allowsShadowDom = safelisted.includes(elementName);
if (allowsShadowDom) {
for (const delegatesFocus of [false, true]) {
for (const mode of ['open', 'closed']) {
testElementType(true, elementName, mode, delegatesFocus);
for (const applyToShadow of [false, true]) {
if (allowsShadowDom) {
for (const delegatesFocus of [false, true]) {
for (const mode of ['open', 'closed']) {
testElementType(true, elementName, applyToShadow, mode, delegatesFocus);
}
}
} else {
testElementType(false, elementName, applyToShadow);
}
} else {
testElementType(false, elementName);
}
}
}
......
......@@ -8,13 +8,20 @@
<body>
<script>
function testElementType(allowsShadowDom, elementType) {
var t = test(t => {
function testElementType(allowsShadowDom, elementType, applyToShadow) {
const t = test(t => {
// Create and attach element
const wrapper = document.createElement('div');
t.add_cleanup(function() { wrapper.remove(); });
document.body.appendChild(wrapper);
let wrapper;
if (applyToShadow) {
const host = document.createElement('div');
t.add_cleanup(function() { host.remove(); });
document.body.appendChild(host);
wrapper = host.attachShadow({mode: 'open'});
} else {
wrapper = document.createElement('div');
t.add_cleanup(function() { wrapper.remove(); });
document.body.appendChild(wrapper);
}
const html = `<${elementType}><template shadowroot="open"><slot></slot></template><span></span></${elementType}>`;
wrapper.setInnerHTML(html, {allowShadowRoot: true});
if (allowsShadowDom) {
......@@ -24,14 +31,16 @@ function testElementType(allowsShadowDom, elementType) {
const leftover = wrapper.firstElementChild.firstElementChild;
assert_true(wrapper.firstElementChild.childElementCount == 0 || leftover instanceof HTMLTemplateElement,'Template should be left over (or no children)');
}
}, `setInnerHTML() on <${elementType}>${allowsShadowDom ? `, with declarative Shadow DOM.` : ''}`);
}, `${applyToShadow ? 'ShadowRoot' : 'Element'}.setInnerHTML() on <${elementType}>${allowsShadowDom ? `, with declarative Shadow DOM.` : ''}`);
}
function runAllTests() {
const allElements = [...HTML5_ELEMENT_NAMES, 'htmlunknown'].filter(item => item !== 'body');
const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS;
for (const elementName of allElements) {
testElementType(safelisted.includes(elementName), elementName);
for (const applyToShadow of [false, true]) {
testElementType(safelisted.includes(elementName), elementName, applyToShadow);
}
}
}
......
......@@ -8031,6 +8031,7 @@ interface ShadowRoot : DocumentFragment
method elementFromPoint
method elementsFromPoint
method getAnimations
method getInnerHTML
method getSelection
method setInnerHTML
setter adoptedStyleSheets
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment