Bug 1856460, add support for HTMLDetailsElement.name, r=emilio
Differential Revision: https://phabricator.services.mozilla.com/D215847
This commit is contained in:
@@ -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,27 +43,35 @@ void HTMLDetailsElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
|
||||
const nsAttrValue* aOldValue,
|
||||
nsIPrincipal* aMaybeScriptedPrincipal,
|
||||
bool aNotify) {
|
||||
if (aNameSpaceID == kNameSpaceID_None && aName == nsGkAtoms::open) {
|
||||
bool wasOpen = !!aOldValue;
|
||||
bool isOpen = !!aValue;
|
||||
if (wasOpen != isOpen) {
|
||||
auto stringForState = [](bool aOpen) {
|
||||
return aOpen ? u"open"_ns : u"closed"_ns;
|
||||
};
|
||||
nsAutoString oldState;
|
||||
if (mToggleEventDispatcher) {
|
||||
oldState.Truncate();
|
||||
static_cast<ToggleEvent*>(mToggleEventDispatcher->mEvent.get())
|
||||
->GetOldState(oldState);
|
||||
mToggleEventDispatcher->Cancel();
|
||||
} else {
|
||||
oldState.Assign(stringForState(wasOpen));
|
||||
if (aNameSpaceID == kNameSpaceID_None) {
|
||||
if (aName == nsGkAtoms::open) {
|
||||
bool wasOpen = !!aOldValue;
|
||||
bool isOpen = !!aValue;
|
||||
if (wasOpen != isOpen) {
|
||||
auto stringForState = [](bool aOpen) {
|
||||
return aOpen ? u"open"_ns : u"closed"_ns;
|
||||
};
|
||||
nsAutoString oldState;
|
||||
if (mToggleEventDispatcher) {
|
||||
oldState.Truncate();
|
||||
static_cast<ToggleEvent*>(mToggleEventDispatcher->mEvent.get())
|
||||
->GetOldState(oldState);
|
||||
mToggleEventDispatcher->Cancel();
|
||||
} else {
|
||||
oldState.Assign(stringForState(wasOpen));
|
||||
}
|
||||
RefPtr<ToggleEvent> toggleEvent = CreateToggleEvent(
|
||||
u"toggle"_ns, oldState, stringForState(isOpen), Cancelable::eNo);
|
||||
mToggleEventDispatcher =
|
||||
new AsyncEventDispatcher(this, toggleEvent.forget());
|
||||
mToggleEventDispatcher->PostDOMEvent();
|
||||
|
||||
if (isOpen) {
|
||||
CloseOtherElementsIfNeeded();
|
||||
}
|
||||
}
|
||||
RefPtr<ToggleEvent> toggleEvent = CreateToggleEvent(
|
||||
u"toggle"_ns, oldState, stringForState(isOpen), Cancelable::eNo);
|
||||
mToggleEventDispatcher =
|
||||
new AsyncEventDispatcher(this, toggleEvent.forget());
|
||||
mToggleEventDispatcher->PostDOMEvent();
|
||||
} 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@
|
||||
interface HTMLDetailsElement : HTMLElement {
|
||||
[HTMLConstructor] constructor();
|
||||
|
||||
[CEReactions, SetterThrows, Pref="dom.details_group.enabled"]
|
||||
attribute DOMString name;
|
||||
|
||||
[CEReactions, SetterThrows]
|
||||
attribute boolean open;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user