Skip to content

Commit

Permalink
[EditContext] Limit to valid element types
Browse files Browse the repository at this point in the history
In w3c/edit-context#19 the Editing WG
resolved that EditContext would support the same element types as
shadow root, plus `<canvas>`.

Update the implementation accordingly. See also the updated
spec at [2].

[1] w3c/edit-context#19
[2] https://w3c.github.io/edit-context/#extensions-to-the-htmlelement-interface

Bug: 999184
Change-Id: I2a8a040a279e14e9b98ece3370acdbdd8c3597ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4617146
Reviewed-by: Koji Ishii <kojii@chromium.org>
Reviewed-by: Alex Keng <shihken@microsoft.com>
Commit-Queue: Dan Clark <daniec@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1158908}
  • Loading branch information
dandclark authored and Chromium LUCI CQ committed Jun 16, 2023
1 parent 3ed8b1b commit 1af1553
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 22 deletions.
60 changes: 45 additions & 15 deletions third_party/blink/renderer/core/dom/element.cc
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,31 @@ bool IsGuaranteedToEnterNGBlockNodeLayout(const LayoutObject& layout_object) {
return true;
}

bool IsValidShadowHostName(const QualifiedName& tag_name) {
DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, shadow_root_tags,
({
html_names::kArticleTag,
html_names::kAsideTag,
html_names::kBlockquoteTag,
html_names::kBodyTag,
html_names::kDivTag,
html_names::kFooterTag,
html_names::kH1Tag,
html_names::kH2Tag,
html_names::kH3Tag,
html_names::kH4Tag,
html_names::kH5Tag,
html_names::kH6Tag,
html_names::kHeaderTag,
html_names::kNavTag,
html_names::kMainTag,
html_names::kPTag,
html_names::kSectionTag,
html_names::kSpanTag,
}));
return shadow_root_tags.Contains(tag_name);
}

} // namespace

Element::Element(const QualifiedName& tag_name,
Expand Down Expand Up @@ -4085,7 +4110,21 @@ EditContext* Element::editContext() const {
return HasRareData() ? GetElementRareData()->GetEditContext() : nullptr;
}

void Element::setEditContext(EditContext* edit_context) {
void Element::setEditContext(EditContext* edit_context,
ExceptionState& exception_state) {
// https://w3c.github.io/edit-context/#extensions-to-the-htmlelement-interface
// 1. If this's local name is neither a valid shadow host name nor "canvas",
// then throw a "NotSupportedError" DOMException.
const QualifiedName& tag_name = TagQName();
if (!(IsCustomElement() &&
CustomElement::IsValidName(tag_name.LocalName())) &&
!IsValidShadowHostName(tag_name) && tag_name != html_names::kCanvasTag) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"This element does not support EditContext");
return;
}

// If an element is in focus when being attached to a new EditContext,
// its old EditContext, if it has any, will get blurred,
// and the new EditContext will automatically get focused.
Expand Down Expand Up @@ -4352,22 +4391,13 @@ const ElementInternals* Element::GetElementInternals() const {
}

bool Element::CanAttachShadowRoot() const {
const AtomicString& tag_name = localName();
const QualifiedName& tag_name = TagQName();
// Checking IsCustomElement() here is just an optimization
// because IsValidName is not cheap.
return (IsCustomElement() && CustomElement::IsValidName(tag_name)) ||
tag_name == html_names::kArticleTag ||
tag_name == html_names::kAsideTag ||
tag_name == html_names::kBlockquoteTag ||
tag_name == html_names::kBodyTag || tag_name == html_names::kDivTag ||
tag_name == html_names::kFooterTag || tag_name == html_names::kH1Tag ||
tag_name == html_names::kH2Tag || tag_name == html_names::kH3Tag ||
tag_name == html_names::kH4Tag || tag_name == html_names::kH5Tag ||
tag_name == html_names::kH6Tag || tag_name == html_names::kHeaderTag ||
tag_name == html_names::kNavTag || tag_name == html_names::kMainTag ||
tag_name == html_names::kPTag || tag_name == html_names::kSectionTag ||
tag_name == html_names::kSelectmenuTag ||
tag_name == html_names::kSpanTag;
return (IsCustomElement() &&
CustomElement::IsValidName(tag_name.LocalName())) ||
IsValidShadowHostName(tag_name) ||
tag_name == html_names::kSelectmenuTag;
}

const char* Element::ErrorMessageForAttachShadow() const {
Expand Down
2 changes: 1 addition & 1 deletion third_party/blink/renderer/core/dom/element.h
Original file line number Diff line number Diff line change
Expand Up @@ -1048,7 +1048,7 @@ class CORE_EXPORT Element : public ContainerNode, public Animatable {
void setTabIndex(int);
int tabIndex() const;

void setEditContext(EditContext* editContext);
void setEditContext(EditContext* editContext, ExceptionState&);
EditContext* editContext() const;

// Helpers for V8DOMActivityLogger::logEvent. They call logEvent only if
Expand Down
2 changes: 1 addition & 1 deletion third_party/blink/renderer/core/html/html_element.idl
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@

// EditContext
// https://w3c.github.io/edit-context/
[RuntimeEnabled=EditContext] attribute EditContext? editContext;
[RuntimeEnabled=EditContext, RaisesException=Setter] attribute EditContext? editContext;

// HTMLElement includes ElementContentEditable
// https://html.spec.whatwg.org/C/#contenteditable
Expand Down
11 changes: 6 additions & 5 deletions third_party/blink/web_tests/editing/input/edit-context.html
Original file line number Diff line number Diff line change
Expand Up @@ -696,18 +696,19 @@
const child = document.createElement("iframe");
document.body.appendChild(child);
const childDocument = child.contentDocument;
const textarea = childDocument.createElement('textarea');
childDocument.body.appendChild(textarea);
textarea.addEventListener("focusin", e => {
const editable = childDocument.createElement('div');
editable.contentEditable = true;
childDocument.body.appendChild(editable);
editable.addEventListener("focusin", e => {
const childEditContext = new EditContext();
textarea.editContext = childEditContext;
editable.editContext = childEditContext;
childEditContext.addEventListener("textupdate", e => {
child.remove();
});
childEditContext.addEventListener("textformatupdate", e => {
});
});
textarea.focus();
editable.focus();
child.contentWindow.focus();
child.contentWindow.textInputController.setComposition("bar");
}, 'Testing EditContext Iframe Document Delete');
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<!DOCTYPE html>
<html>
<head>
<title>EditContext: The HTMLElement.editContext property</title>
<meta name="author" title="Dan Clark" href="mailto:daniec@microsoft.com">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src='../../html/resources/common.js'></script>
</head>
<body>
<script>

test(function () {
assert_true('editContext' in HTMLElement.prototype, 'Element.prototype.editContext must exist');
assert_equals(typeof(document.createElement('div').editContext), 'object', 'An instance of div must have editContext which is an object');
}, 'Check the existence of HTMLElement.editContext');

test(function () {
assert_false('editContext' in Node.prototype, 'Node.prototype.editContext must not exist');
assert_false('editContext' in Element.prototype, 'Element.prototype.editContext must not exist');
assert_false('editContext' in CharacterData.prototype, 'CharacterData.prototype.editContext must not exist');
assert_false('editContext' in Comment.prototype, 'Comment.prototype.editContext must not exist');
assert_equals(typeof(document.createComment('').editContext), 'undefined', 'An instance of comment must not have editContext');
assert_false('editContext' in Document.prototype, 'Document.prototype.editContext must not exist');
assert_equals(typeof(document.editContext), 'undefined', 'An instance of document must not have editContext which is a function');
assert_false('editContext' in DocumentFragment.prototype, 'DocumentFragment.prototype.editContext must not exist');
assert_equals(typeof((new DOMParser()).parseFromString('', 'text/html').editContext), 'undefined', 'An instance of document must not have editContext which is a function');
assert_false('editContext' in Text.prototype, 'Text.prototype.editContext must not exist');
assert_equals(typeof(document.createTextNode('').editContext), 'undefined', 'An instance of text node must not have editContext');
}, 'Nodes other than Element should not have editContext');

test(function () {
assert_throws_js(TypeError, function () {
document.createElement('div').editContext = "hello";
}, 'editContext must throw a TypeError when set to a string');

assert_throws_js(TypeError, function () {
document.createElement('div').editContext = 42;
}, 'editContext must throw a TypeError when set to a number');

assert_throws_js(TypeError, function () {
document.createElement('div').editContext = document.createElement('span');
}, 'editContext must throw a TypeError when set to a node');
}, 'HTMLElement.editContext must throw a TypeError if set to something other than an EditContext');

test(function () {
const EDIT_CONTEXT_ALLOWED_ELEMENTS = HTML5_SHADOW_ALLOWED_ELEMENTS.concat(['canvas']);
for (const elementName of EDIT_CONTEXT_ALLOWED_ELEMENTS) {
const element = document.createElement(elementName);
const ec = new EditContext();
element.editContext = ec;
assert_equals(element.editContext, ec, 'Getting HTMLElement.editContext should yield the same EditContext instance');
}
}, 'HTMLElement.editContext can be set on the shadow root elements plus canvas.');

test(function () {
// EditContext shares all of the shadow root disallowed elements except for canvas.
const EDIT_CONTEXT_DISALLOWED_ELEMENTS = HTML5_SHADOW_DISALLOWED_ELEMENTS.toSpliced(HTML5_SHADOW_DISALLOWED_ELEMENTS.indexOf('canvas'), 1);
for (const elementName of EDIT_CONTEXT_DISALLOWED_ELEMENTS) {
const element = document.createElement(elementName);
const ec = new EditContext();
assert_throws_dom('NotSupportedError', () => {
element.editContext = ec;
}, `Setting editContext on <${elementName}> must throw.`);
assert_equals(element.editContext, null);
}
}, 'Setting HTMLElement.editContext must throw a NotSupportedError for disallowed elements');

</script>
</body>
</html>

0 comments on commit 1af1553

Please sign in to comment.