Bug 1856460, add support for HTMLDetailsElement.name, r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D215847
This commit is contained in:
Olli Pettay
2024-07-08 20:39:44 +00:00
parent 90d172460b
commit acecfeab93
7 changed files with 136 additions and 79 deletions

View File

@@ -10,6 +10,7 @@
#include "mozilla/dom/HTMLSummaryElement.h"
#include "mozilla/dom/ShadowRoot.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_dom.h"
#include "nsContentUtils.h"
#include "nsTextNode.h"
@@ -42,7 +43,8 @@ void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
const nsAttrValue* aOldValue,
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) {
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
if (aNameSpaceID == kNameSpaceID_None) {
if (aName == nsGkAtoms::open) {
bool wasOpen = !!aOldValue;
bool isOpen = !!aValue;
if (wasOpen != isOpen) {
@@ -63,6 +65,13 @@ void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
mToggleEventDispatcher =
new AsyncEventDispatcher(this, toggleEvent.forget());
mToggleEventDispatcher->PostDOMEvent();
if (isOpen) {
CloseOtherElementsIfNeeded();
}
}
} else if (aName == nsGkAtoms::name) {
CloseElementIfNeeded();
}
}
@@ -70,6 +79,16 @@ void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
}
nsresult HTMLDetailsElement::BindToTree(BindContext& aContext,
nsINode& aParent) {
nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
NS_ENSURE_SUCCESS(rv, rv);
CloseElementIfNeeded();
return NS_OK;
}
void HTMLDetailsElement::SetupShadowTree() {
const bool kNotify = false;
AttachAndSetUAShadowRoot(NotifyUAWidgetSetup::No);
@@ -175,4 +194,77 @@ bool HTMLDetailsElement::HandleInvokeInternal(Element* aInvoker,
return false;
}
void HTMLDetailsElement::CloseElementIfNeeded() {
if (!StaticPrefs::dom_details_group_enabled()) {
return;
}
if (!Open()) {
return;
}
if (!HasName()) {
return;
}
RefPtr<nsAtom> name = GetParsedAttr(nsGkAtoms::name)->GetAsAtom();
RefPtr<Document> doc = OwnerDoc();
bool oldFlag = doc->FireMutationEvents();
doc->SetFireMutationEvents(false);
nsINode* root = SubtreeRoot();
for (nsINode* cur = root; cur; cur = cur->GetNextNode(root)) {
if (!cur->HasName()) {
continue;
}
if (auto* other = HTMLDetailsElement::FromNode(cur)) {
if (other != this && other->Open() &&
other->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, name,
eCaseMatters)) {
SetOpen(false, IgnoreErrors());
break;
}
}
}
doc->SetFireMutationEvents(oldFlag);
}
void HTMLDetailsElement::CloseOtherElementsIfNeeded() {
if (!StaticPrefs::dom_details_group_enabled()) {
return;
}
MOZ_ASSERT(Open());
if (!HasName()) {
return;
}
RefPtr<nsAtom> name = GetParsedAttr(nsGkAtoms::name)->GetAsAtom();
RefPtr<Document> doc = OwnerDoc();
bool oldFlag = doc->FireMutationEvents();
doc->SetFireMutationEvents(false);
nsINode* root = SubtreeRoot();
for (nsINode* cur = root; cur; cur = cur->GetNextNode(root)) {
if (!cur->HasName()) {
continue;
}
if (auto* other = HTMLDetailsElement::FromNode(cur)) {
if (other != this && other->Open() &&
other->AttrValueIs(kNameSpaceID_None, nsGkAtoms::name, name,
eCaseMatters)) {
RefPtr<HTMLDetailsElement> otherDetails = other;
otherDetails->SetOpen(false, IgnoreErrors());
break;
}
}
}
doc->SetFireMutationEvents(oldFlag);
}
} // namespace mozilla::dom

View File

@@ -37,9 +37,18 @@ class HTMLDetailsElement final : public nsGenericHTMLElement {
nsIPrincipal* aMaybeScriptedPrincipal,
bool aNotify) override;
nsresult BindToTree(BindContext&, nsINode& aParent) override;
bool IsInteractiveHTMLContent() const override { return true; }
// HTMLDetailsElement WebIDL
void SetName(const nsAString& aName, ErrorResult& aRv) {
SetHTMLAttr(nsGkAtoms::name, aName, aRv);
}
void GetName(nsAString& aName) { GetHTMLAttr(nsGkAtoms::name, aName); }
bool Open() const { return GetBoolAttr(nsGkAtoms::open); }
void SetOpen(bool aOpen, ErrorResult& aError) {
@@ -59,6 +68,12 @@ class HTMLDetailsElement final : public nsGenericHTMLElement {
virtual ~HTMLDetailsElement();
void SetupShadowTree();
// https://html.spec.whatwg.org/#ensure-details-exclusivity-by-closing-the-given-element-if-needed
void CloseElementIfNeeded();
// https://html.spec.whatwg.org/#ensure-details-exclusivity-by-closing-other-elements-if-needed
void CloseOtherElementsIfNeeded();
JSObject* WrapNode(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) override;

View File

@@ -15,6 +15,9 @@
interface HTMLDetailsElement : HTMLElement {
[HTMLConstructor] constructor();
[CEReactions, SetterThrows, Pref="dom.details_group.enabled"]
attribute DOMString name;
[CEReactions, SetterThrows]
attribute boolean open;
};

View File

@@ -2343,6 +2343,11 @@
value: true
mirror: always
- name: dom.details_group.enabled
type: bool
value: @IS_NIGHTLY_BUILD@
mirror: always
# Only propagate the open window click permission if the setTimeout() is equal
# to or less than this value.
- name: dom.disable_open_click_delay

View File

@@ -864,12 +864,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
[HTMLElement interface: document.createElement("noscript") must inherit property "onbeforematch" with the proper type]
expected: FAIL
[HTMLDetailsElement interface: attribute name]
expected: FAIL
[HTMLDetailsElement interface: document.createElement("details") must inherit property "name" with the proper type]
expected: FAIL
[HTMLBodyElement interface: attribute onpagereveal]
expected: FAIL

View File

@@ -1,49 +1 @@
[name-attribute.html]
expected: TIMEOUT
[basic handling of mutually exclusive details]
expected: FAIL
[more complex handling of mutually exclusive details]
expected: FAIL
[mutually exclusive details across multiple names and multiple tree scopes]
expected: FAIL
[mutation event and toggle event order]
expected: FAIL
[interaction of open attribute changes with mutation events]
expected: FAIL
[exclusivity enforcement with attachment scenario connected]
expected: FAIL
[exclusivity enforcement with attachment scenario disconnected]
expected: FAIL
[exclusivity enforcement with attachment scenario shadow]
expected: FAIL
[exclusivity enforcement with attachment scenario shadow-in-disconnected]
expected: FAIL
[exclusivity enforcement with attachment scenario template-in-disconnected]
expected: FAIL
[exclusivity enforcement with attachment scenario connected-in-xhr-response]
expected: FAIL
[exclusivity enforcement with attachment scenario connected-in-implementation-create-document]
expected: FAIL
[exclusivity enforcement with attachment scenario connected-in-template]
expected: FAIL
[handling of name attribute changes]
expected: FAIL
[closing as a result of parsing doesn't depend on attribute order]
expected: FAIL
[handling of insertion of elements into group]
expected: TIMEOUT
prefs: [dom.details_group.enabled:true]

View File

@@ -194,10 +194,6 @@ promise_test(async t => {
let received_ids = [];
let listener = event => {
received_ids.push(event.target.id);
let i = 0;
for (let element of elements) {
element.setAttribute("name", `b${i++}`);
}
};
for (let element of elements) {
element.addEventListener("DOMSubtreeModified", listener);