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 @@ ...@@ -27,6 +27,7 @@
#include "third_party/blink/renderer/core/dom/shadow_root.h" #include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/public/platform/platform.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/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/resolver/style_resolver.h"
#include "third_party/blink/renderer/core/css/style_change_reason.h" #include "third_party/blink/renderer/core/css/style_change_reason.h"
...@@ -117,6 +118,21 @@ String ShadowRoot::innerHTML() const { ...@@ -117,6 +118,21 @@ String ShadowRoot::innerHTML() const {
return CreateMarkup(this, kChildrenOnly); 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, void ShadowRoot::SetInnerHTMLInternal(const String& html,
const SetInnerHTMLOptions* options, const SetInnerHTMLOptions* options,
ExceptionState& exception_state) { ExceptionState& exception_state) {
......
...@@ -41,6 +41,7 @@ namespace blink { ...@@ -41,6 +41,7 @@ namespace blink {
class Document; class Document;
class ExceptionState; class ExceptionState;
class SetInnerHTMLOptions; class SetInnerHTMLOptions;
class GetInnerHTMLOptions;
class ShadowRootV0; class ShadowRootV0;
class SlotAssignment; class SlotAssignment;
class WhitespaceAttacher; class WhitespaceAttacher;
...@@ -138,6 +139,7 @@ class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope { ...@@ -138,6 +139,7 @@ class CORE_EXPORT ShadowRoot final : public DocumentFragment, public TreeScope {
Element* ActiveElement() const; Element* ActiveElement() const;
String innerHTML() const; String innerHTML() const;
String getInnerHTML(const GetInnerHTMLOptions* options) const;
void setInnerHTML(const String&, ExceptionState& = ASSERT_NO_EXCEPTION); void setInnerHTML(const String&, ExceptionState& = ASSERT_NO_EXCEPTION);
void setInnerHTMLWithOptions(const String&, void setInnerHTMLWithOptions(const String&,
const SetInnerHTMLOptions*, const SetInnerHTMLOptions*,
......
...@@ -36,9 +36,9 @@ interface ShadowRoot : DocumentFragment { ...@@ -36,9 +36,9 @@ interface ShadowRoot : DocumentFragment {
readonly attribute boolean delegatesFocus; readonly attribute boolean delegatesFocus;
[RuntimeEnabled=ManualSlotting] readonly attribute SlotAssignmentMode slotAssignment; [RuntimeEnabled=ManualSlotting] readonly attribute SlotAssignmentMode slotAssignment;
// Declarative Shadow DOM setInnerHTML function. // Declarative Shadow DOM setInnerHTML/getInnerHTML() functions.
// TODO(crbug.com/1147752): Add getInnerHTML() here also. [Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RuntimeCallStatsCounter=ElementSetInnerHTML, RaisesException, ImplementedAs=setInnerHTMLWithOptions] void setInnerHTML(HTMLString html, optional SetInnerHTMLOptions options = {});
[Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RaisesException, ImplementedAs=setInnerHTMLWithOptions] void setInnerHTML(HTMLString html, optional SetInnerHTMLOptions options = {}); [Affects=Nothing, RuntimeEnabled=DeclarativeShadowDOM, RuntimeCallStatsCounter=ElementGetInnerHTML] HTMLString getInnerHTML(optional GetInnerHTMLOptions options = {});
}; };
ShadowRoot includes DocumentOrShadowRoot; ShadowRoot includes DocumentOrShadowRoot;
...@@ -9,12 +9,20 @@ ...@@ -9,12 +9,20 @@
<body> <body>
<script> <script>
function testElementType(allowsShadowDom, elementType, mode, delegatesFocus) { function testElementType(allowsShadowDom, elementType, applyToShadow, mode, delegatesFocus) {
var t = test(t => { const t = test(t => {
// Create and attach element // Create and attach element
const wrapper = document.createElement('div'); let wrapper;
t.add_cleanup(function() { wrapper.remove(); }); if (applyToShadow) {
document.body.appendChild(wrapper); 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); const element = document.createElement(elementType);
wrapper.appendChild(element); wrapper.appendChild(element);
...@@ -44,7 +52,7 @@ function testElementType(allowsShadowDom, elementType, mode, delegatesFocus) { ...@@ -44,7 +52,7 @@ function testElementType(allowsShadowDom, elementType, mode, delegatesFocus) {
// Either way, make sure getInnerHTML({includeShadowRoots: false}) matches .innerHTML // 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'); 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() { function runAllTests() {
...@@ -52,14 +60,16 @@ function runAllTests() { ...@@ -52,14 +60,16 @@ function runAllTests() {
const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS; const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS;
for (const elementName of allElements) { for (const elementName of allElements) {
const allowsShadowDom = safelisted.includes(elementName); const allowsShadowDom = safelisted.includes(elementName);
if (allowsShadowDom) { for (const applyToShadow of [false, true]) {
for (const delegatesFocus of [false, true]) { if (allowsShadowDom) {
for (const mode of ['open', 'closed']) { for (const delegatesFocus of [false, true]) {
testElementType(true, elementName, mode, delegatesFocus); 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 @@ ...@@ -8,13 +8,20 @@
<body> <body>
<script> <script>
function testElementType(allowsShadowDom, elementType) { function testElementType(allowsShadowDom, elementType, applyToShadow) {
var t = test(t => { const t = test(t => {
// Create and attach element // Create and attach element
const wrapper = document.createElement('div'); let wrapper;
t.add_cleanup(function() { wrapper.remove(); }); if (applyToShadow) {
document.body.appendChild(wrapper); 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}>`; const html = `<${elementType}><template shadowroot="open"><slot></slot></template><span></span></${elementType}>`;
wrapper.setInnerHTML(html, {allowShadowRoot: true}); wrapper.setInnerHTML(html, {allowShadowRoot: true});
if (allowsShadowDom) { if (allowsShadowDom) {
...@@ -24,14 +31,16 @@ function testElementType(allowsShadowDom, elementType) { ...@@ -24,14 +31,16 @@ function testElementType(allowsShadowDom, elementType) {
const leftover = wrapper.firstElementChild.firstElementChild; const leftover = wrapper.firstElementChild.firstElementChild;
assert_true(wrapper.firstElementChild.childElementCount == 0 || leftover instanceof HTMLTemplateElement,'Template should be left over (or no children)'); 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() { function runAllTests() {
const allElements = [...HTML5_ELEMENT_NAMES, 'htmlunknown'].filter(item => item !== 'body'); const allElements = [...HTML5_ELEMENT_NAMES, 'htmlunknown'].filter(item => item !== 'body');
const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS; const safelisted = ATTACHSHADOW_SAFELISTED_ELEMENTS;
for (const elementName of allElements) { 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 ...@@ -8031,6 +8031,7 @@ interface ShadowRoot : DocumentFragment
method elementFromPoint method elementFromPoint
method elementsFromPoint method elementsFromPoint
method getAnimations method getAnimations
method getInnerHTML
method getSelection method getSelection
method setInnerHTML method setInnerHTML
setter adoptedStyleSheets 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