author | Emilio Cobos Álvarez <emilio@crisal.io> |
Tue, 13 Mar 2018 15:27:50 +0100 | |
changeset 408056 | 6b06d04460074d2d4f81ada49ff36a57ccbf5ff7 |
parent 408055 | b9370ab70cbf1ae719bc271830be8ce995e6938c |
child 408057 | 7e5eb5740cac2dbac9d803d5151ef4958bc51862 |
push id | 100842 |
push user | ecoal95@gmail.com |
push date | Wed, 14 Mar 2018 09:54:41 +0000 |
treeherder | mozilla-inbound@6b06d0446007 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | smaug |
bugs | 1438210, 1409975 |
milestone | 61.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -108,16 +108,35 @@ ShadowRoot::CloneInternalDataFrom(Shadow Servo_AuthorStyles_AppendStyleSheet(mServoStyles.get(), clonedSheet->AsServo()); } } } } void +ShadowRoot::InvalidateStyleAndLayoutOnSubtree(Element* aElement) +{ + MOZ_ASSERT(aElement); + + if (!IsComposedDocParticipant()) { + return; + } + + MOZ_ASSERT(GetComposedDoc() == OwnerDoc()); + + nsIPresShell* shell = OwnerDoc()->GetShell(); + if (!shell) { + return; + } + + shell->DestroyFramesForAndRestyle(aElement); +} + +void ShadowRoot::AddSlot(HTMLSlotElement* aSlot) { MOZ_ASSERT(aSlot); // Note that if name attribute missing, the slot is a default slot. nsAutoString name; aSlot->GetName(name); @@ -129,45 +148,48 @@ ShadowRoot::AddSlot(HTMLSlotElement* aSl TreeOrderComparator comparator; currentSlots->InsertElementSorted(aSlot, comparator); HTMLSlotElement* currentSlot = currentSlots->ElementAt(0); if (currentSlot != aSlot) { return; } - bool doEnqueueSlotChange = false; if (oldSlot && oldSlot != currentSlot) { // Move assigned nodes from old slot to new slot. + InvalidateStyleAndLayoutOnSubtree(oldSlot); const nsTArray<RefPtr<nsINode>>& assignedNodes = oldSlot->AssignedNodes(); + bool doEnqueueSlotChange = false; while (assignedNodes.Length() > 0) { nsINode* assignedNode = assignedNodes[0]; oldSlot->RemoveAssignedNode(assignedNode); currentSlot->AppendAssignedNode(assignedNode); doEnqueueSlotChange = true; } if (doEnqueueSlotChange) { oldSlot->EnqueueSlotChangeEvent(); currentSlot->EnqueueSlotChangeEvent(); } } else { + bool doEnqueueSlotChange = false; // Otherwise add appropriate nodes to this slot from the host. for (nsIContent* child = GetHost()->GetFirstChild(); child; child = child->GetNextSibling()) { nsAutoString slotName; if (child->IsElement()) { child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); } - if (child->IsSlotable() && slotName.Equals(name)) { - currentSlot->AppendAssignedNode(child); - doEnqueueSlotChange = true; + if (!child->IsSlotable() || !slotName.Equals(name)) { + continue; } + doEnqueueSlotChange = true; + currentSlot->AppendAssignedNode(child); } if (doEnqueueSlotChange) { currentSlot->EnqueueSlotChangeEvent(); } } } @@ -338,29 +360,29 @@ ShadowRoot::GetEventTargetParent(EventCh nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget)); if (content && content->GetBindingParent() == shadowHost) { aVisitor.mEventTargetAtParent = shadowHost; } return NS_OK; } -const HTMLSlotElement* -ShadowRoot::AssignSlotFor(nsIContent* aContent) +ShadowRoot::SlotAssignment +ShadowRoot::SlotAssignmentFor(nsIContent* aContent) { nsAutoString slotName; // Note that if slot attribute is missing, assign it to the first default // slot, if exists. if (aContent->IsElement()) { aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); } nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(slotName); if (!slots) { - return nullptr; + return { }; } HTMLSlotElement* slot = slots->ElementAt(0); MOZ_ASSERT(slot); // Find the appropriate position in the assigned node list for the // newly assigned content. const nsTArray<RefPtr<nsINode>>& assignedNodes = slot->AssignedNodes(); @@ -378,68 +400,58 @@ ShadowRoot::AssignSlotFor(nsIContent* aC currentContent = currentContent->GetNextSibling(); } if (insertionIndex) { break; } } - if (insertionIndex) { - slot->InsertAssignedNode(*insertionIndex, aContent); - } else { - slot->AppendAssignedNode(aContent); - } - - return slot; + return { slot, insertionIndex }; } -const HTMLSlotElement* -ShadowRoot::UnassignSlotFor(nsIContent* aNode, const nsAString& aSlotName) +void +ShadowRoot::MaybeReassignElement(Element* aElement) { - // Find the insertion point to which the content belongs. Note that if slot - // attribute is missing, unassign it from the first default slot, if exists. - nsTArray<HTMLSlotElement*>* slots = mSlotMap.Get(aSlotName); - if (!slots) { - return nullptr; - } + MOZ_ASSERT(aElement->GetParent() == GetHost()); + HTMLSlotElement* oldSlot = aElement->GetAssignedSlot(); + SlotAssignment assignment = SlotAssignmentFor(aElement); - HTMLSlotElement* slot = slots->ElementAt(0); - MOZ_ASSERT(slot); - - if (!slot->AssignedNodes().Contains(aNode)) { - return nullptr; + if (assignment.mSlot == oldSlot) { + // Nothing to do here. + return; } - slot->RemoveAssignedNode(aNode); - return slot; -} - -bool -ShadowRoot::MaybeReassignElement(Element* aElement, - const nsAttrValue* aOldValue) -{ - nsIContent* parent = aElement->GetParent(); - if (parent && parent == GetHost()) { - const HTMLSlotElement* oldSlot = UnassignSlotFor(aElement, - aOldValue ? aOldValue->GetStringValue() : EmptyString()); - const HTMLSlotElement* newSlot = AssignSlotFor(aElement); - - if (oldSlot != newSlot) { - if (oldSlot) { - oldSlot->EnqueueSlotChangeEvent(); - } - if (newSlot) { - newSlot->EnqueueSlotChangeEvent(); - } - return true; - } + // If the old slot is about to become empty, let layout know that it needs to + // do work. + if (oldSlot && oldSlot->AssignedNodes().Length() == 1) { + InvalidateStyleAndLayoutOnSubtree(oldSlot); + } + // Ditto if the new slot will stop showing fallback content. + if (assignment.mSlot && assignment.mSlot->AssignedNodes().IsEmpty()) { + InvalidateStyleAndLayoutOnSubtree(assignment.mSlot); } - return false; + // Otherwise we only need to care about the reassigned element. Note that this + // is a no-op if we hit the `oldSlot` path above. + InvalidateStyleAndLayoutOnSubtree(aElement); + + if (oldSlot) { + oldSlot->RemoveAssignedNode(aElement); + oldSlot->EnqueueSlotChangeEvent(); + } + + if (assignment.mSlot) { + if (assignment.mIndex) { + assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aElement); + } else { + assignment.mSlot->AppendAssignedNode(aElement); + } + assignment.mSlot->EnqueueSlotChangeEvent(); + } } Element* ShadowRoot::GetActiveElement() { return GetRetargetedFocusedElement(); } @@ -461,32 +473,21 @@ ShadowRoot::AttributeChanged(Element* aE nsAtom* aAttribute, int32_t aModType, const nsAttrValue* aOldValue) { if (aNameSpaceID != kNameSpaceID_None || aAttribute != nsGkAtoms::slot) { return; } - // Attributes may change insertion point matching, find its new distribution. - if (!MaybeReassignElement(aElement, aOldValue)) { + if (aElement->GetParent() != GetHost()) { return; } - if (!aElement->IsInComposedDoc()) { - return; - } - - auto* shell = OwnerDoc()->GetShell(); - if (!shell) { - return; - } - - // FIXME(emilio): We could be more granular in a bunch of cases. - shell->DestroyFramesForAndRestyle(aElement); + MaybeReassignElement(aElement); } void ShadowRoot::ContentAppended(nsIContent* aFirstNewContent) { for (nsIContent* content = aFirstNewContent; content; content = content->GetNextSibling()) { @@ -504,19 +505,32 @@ ShadowRoot::ContentInserted(nsIContent* return; } if (!aChild->IsSlotable()) { return; } if (aChild->GetParent() == GetHost()) { - if (const HTMLSlotElement* slot = AssignSlotFor(aChild)) { - slot->EnqueueSlotChangeEvent(); + SlotAssignment assignment = SlotAssignmentFor(aChild); + if (!assignment.mSlot) { + return; } + + // Fallback content will go away, let layout know. + if (assignment.mSlot->AssignedNodes().IsEmpty()) { + InvalidateStyleAndLayoutOnSubtree(assignment.mSlot); + } + + if (assignment.mIndex) { + assignment.mSlot->InsertAssignedNode(*assignment.mIndex, aChild); + } else { + assignment.mSlot->AppendAssignedNode(aChild); + } + assignment.mSlot->EnqueueSlotChangeEvent(); return; } // If parent's root is a shadow root, and parent is a slot whose assigned // nodes is the empty list, then run signal a slot change for parent. HTMLSlotElement* slot = HTMLSlotElement::FromContentOrNull(aChild->GetParent()); if (slot && slot->GetContainingShadow() == this && slot->AssignedNodes().IsEmpty()) { @@ -534,21 +548,23 @@ ShadowRoot::ContentRemoved(nsIContent* a return; } if (!aChild->IsSlotable()) { return; } if (aChild->GetParent() == GetHost()) { - nsAutoString slotName; - if (aChild->IsElement()) { - aChild->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::slot, slotName); - } - if (const HTMLSlotElement* slot = UnassignSlotFor(aChild, slotName)) { + if (HTMLSlotElement* slot = aChild->GetAssignedSlot()) { + // If the slot is going to start showing fallback content, we need to tell + // layout about it. + if (slot->AssignedNodes().Length() == 1) { + InvalidateStyleAndLayoutOnSubtree(slot); + } + slot->RemoveAssignedNode(aChild); slot->EnqueueSlotChangeEvent(); } return; } // If parent's root is a shadow root, and parent is a slot whose assigned // nodes is the empty list, then run signal a slot change for parent. HTMLSlotElement* slot = HTMLSlotElement::FromContentOrNull(aChild->GetParent());
--- a/dom/base/ShadowRoot.h +++ b/dom/base/ShadowRoot.h @@ -94,33 +94,51 @@ public: */ void CloneInternalDataFrom(ShadowRoot* aOther); private: /** * Try to reassign an element to a slot and returns whether the assignment * changed. */ - bool MaybeReassignElement(Element* aElement, const nsAttrValue* aOldValue); + void MaybeReassignElement(Element* aElement); /** - * Try to assign aContent to a slot in the shadow tree, returns the assigned - * slot if found. + * Represents the insertion point in a slot for a given node. */ - const HTMLSlotElement* AssignSlotFor(nsIContent* aContent); + struct SlotAssignment + { + HTMLSlotElement* mSlot = nullptr; + Maybe<uint32_t> mIndex; + }; /** - * Unassign aContent from the assigned slot in the shadow tree, returns the - * assigned slot if found. + * Return the assignment corresponding to the content node at this particular + * point in time. + * + * It's the caller's responsibility to actually call InsertAssignedNode / + * AppendAssignedNode in the slot as needed. + */ + SlotAssignment SlotAssignmentFor(nsIContent* aContent); + + /** + * Explicitly invalidates the style and layout of the flattened-tree subtree + * rooted at the element. * - * Note: slot attribute of aContent may have changed already, so pass slot - * name explicity here. + * You need to use this whenever the flat tree is going to be shuffled in a + * way that layout doesn't understand via the usual ContentInserted / + * ContentAppended / ContentRemoved notifications. For example, if removing an + * element will cause a change in the flat tree such that other element will + * start showing up (like fallback content), this method needs to be called on + * an ancestor of that element. + * + * It is important that this runs _before_ actually shuffling the flat tree + * around, so that layout knows the actual tree that it needs to invalidate. */ - const HTMLSlotElement* UnassignSlotFor(nsIContent* aContent, - const nsAString& aSlotName); + void InvalidateStyleAndLayoutOnSubtree(Element*); public: void AddSlot(HTMLSlotElement* aSlot); void RemoveSlot(HTMLSlotElement* aSlot); const RawServoAuthorStyles* ServoStyles() const { return mServoStyles.get(); @@ -158,18 +176,16 @@ public: mIsComposedDocParticipant = aIsComposedDocParticipant; } nsresult GetEventTargetParent(EventChainPreVisitor& aVisitor) override; protected: virtual ~ShadowRoot(); - void SyncServoStyles(); - const ShadowRootMode mMode; // The computed data from the style sheets. UniquePtr<RawServoAuthorStyles> mServoStyles; UniquePtr<mozilla::ServoStyleRuleMap> mStyleRuleMap; using SlotArray = AutoTArray<HTMLSlotElement*, 1>; // Map from name of slot to an array of all slots in the shadow DOM with with
--- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -124012,16 +124012,76 @@ [ "/css/css-scoping/reference/green-box.html", "==" ] ], {} ] ], + "css/css-scoping/shadow-fallback-dynamic-001.html": [ + [ + "/css/css-scoping/shadow-fallback-dynamic-001.html", + [ + [ + "/css/css-scoping/reference/green-box.html", + "==" + ] + ], + {} + ] + ], + "css/css-scoping/shadow-fallback-dynamic-002.html": [ + [ + "/css/css-scoping/shadow-fallback-dynamic-002.html", + [ + [ + "/css/css-scoping/reference/green-box.html", + "==" + ] + ], + {} + ] + ], + "css/css-scoping/shadow-fallback-dynamic-003.html": [ + [ + "/css/css-scoping/shadow-fallback-dynamic-003.html", + [ + [ + "/css/css-scoping/reference/green-box.html", + "==" + ] + ], + {} + ] + ], + "css/css-scoping/shadow-fallback-dynamic-004.html": [ + [ + "/css/css-scoping/shadow-fallback-dynamic-004.html", + [ + [ + "/css/css-scoping/reference/green-box.html", + "==" + ] + ], + {} + ] + ], + "css/css-scoping/shadow-fallback-dynamic-005.html": [ + [ + "/css/css-scoping/shadow-fallback-dynamic-005.html", + [ + [ + "/css/css-scoping/reference/green-box.html", + "==" + ] + ], + {} + ] + ], "css/css-scoping/slotted-with-pseudo-element.html": [ [ "/css/css-scoping/slotted-with-pseudo-element.html", [ [ "/css/css-scoping/slotted-with-pseudo-element-ref.html", "==" ] @@ -500792,16 +500852,36 @@ "css/css-scoping/reference/green-box.html": [ "a736f68dc602c0fccab56ec5cc6234cb3298c88d", "support" ], "css/css-scoping/shadow-cascade-order-001.html": [ "46913ea7e47811b11be898de5c3bd0a330ea6637", "testharness" ], + "css/css-scoping/shadow-fallback-dynamic-001.html": [ + "062c99df18077a0205d0170d641b1d1e61199657", + "reftest" + ], + "css/css-scoping/shadow-fallback-dynamic-002.html": [ + "2f66c8bca48c2ce5c9e82c5d67b152e2d143f4c6", + "reftest" + ], + "css/css-scoping/shadow-fallback-dynamic-003.html": [ + "f054b0974277fbee38a96a26559c9a15400266db", + "reftest" + ], + "css/css-scoping/shadow-fallback-dynamic-004.html": [ + "50c12659c9625801c6b4bb25ab76acddd50c8e90", + "reftest" + ], + "css/css-scoping/shadow-fallback-dynamic-005.html": [ + "46d78b6d6931505bbc4bfc2f83e2bd0bac0d3472", + "reftest" + ], "css/css-scoping/slotted-invalidation.html": [ "c500e1ceba1b293d45df5f66fd89d4a5d9ceb952", "testharness" ], "css/css-scoping/slotted-parsing.html": [ "6bac5b15011d7177a40f7ca3e3c5f7e410643920", "testharness" ], @@ -591057,17 +591137,17 @@ "bb08ac2ee06bc12e75cc44b76f06d3f0262f44ba", "testharness" ], "user-timing/idlharness.html": [ "b24d4d5faf5df5d67135560d5fe362cc6d72c28f", "testharness" ], "user-timing/invoke_with_timing_attributes.html": [ - "c946c734573a3e8598389ae854c1d9792e96440c", + "05283be9a7230ba0c4af09fb5dac98d828bfaf2e", "testharness" ], "user-timing/invoke_with_timing_attributes.worker.js": [ "f86e2132ced139cfe712a20ee4f64ba9e44459d2", "testharness" ], "user-timing/invoke_without_parameter.html": [ "2246a41be7f7cca7574e57d76753c9347f738781", @@ -591081,17 +591161,17 @@ "c86845475d74791d9907716ae34743606b2465de", "testharness" ], "user-timing/measure.html": [ "b021b6706afbf40f59ba1bbc743f4f4e57ea4f66", "testharness" ], "user-timing/measure_exceptions_navigation_timing.html": [ - "e557969014be8b0ed1870e288e7f06f4b2a149a4", + "ed9d9be01e740d282ec94379bfd78aca07b56325", "testharness" ], "user-timing/measure_navigation_timing.html": [ "ff8fb60c227078ab96eddbaee26c6c3b1fa3a9d4", "testharness" ], "user-timing/measure_syntax_err.any.js": [ "e04fed804b4d89be63b8fdcbf12774c9a613f6d3", @@ -601013,17 +601093,17 @@ "b7ac10fed9c8a07afcd13f1d4906e10996ae56c6", "testharness" ], "xhr/responsexml-basic.htm": [ "962765bd28850b740b0945d08f31fd94c8883191", "testharness" ], "xhr/responsexml-document-properties.htm": [ - "3171001d9f35e2524a575d02c581c2cbe813f973", + "432b8327dbb09533bb64e8dabf72754d85c25e4f", "testharness" ], "xhr/responsexml-get-twice.htm": [ "6291caac16b148f2265968820a8bd460a1a77092", "testharness" ], "xhr/responsexml-media-type.htm": [ "82f735476786b2cbe5c62de17642ab42125e08ee",
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-001.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>CSS Scoping Module Level 1 - Dynamic fallback content</title> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model"> +<link rel="match" href="reference/green-box.html"/> +<p>Test passes if you see a single 100px by 100px green box below.</p> +<div id="host"> + <span slot="myslot">FAIL</span> +</div> +<script> + let root = host.attachShadow({ mode: "open" }); + root.innerHTML = ` + <slot name="myslot"> + <div style="width: 100px; height: 100px; background: green"></div> + </slot> + `; + document.body.offsetTop; + host.firstElementChild.remove(); +</script>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-002.html @@ -0,0 +1,19 @@ +<!doctype html> +<title>CSS Scoping Module Level 1 - Dynamic fallback content</title> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model"> +<link rel="match" href="reference/green-box.html"/> +<p>Test passes if you see a single 100px by 100px green box below.</p> +<div id="host"> + <span slot="myslot">FAIL</span> +</div> +<script> + let root = host.attachShadow({ mode: "open" }); + root.innerHTML = ` + <slot name="myslot"> + <div style="width: 100px; height: 100px; background: green"></div> + </slot> + `; + document.body.offsetTop; + host.firstElementChild.removeAttribute("slot"); +</script>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-003.html @@ -0,0 +1,18 @@ +<!doctype html> +<title>CSS Scoping Module Level 1 - Dynamic fallback content</title> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model"> +<link rel="match" href="reference/green-box.html"/> +<p>Test passes if you see a single 100px by 100px green box below.</p> +<div id="host"> +</div> +<script> + let root = host.attachShadow({ mode: "open" }); + root.innerHTML = ` + <slot name="myslot">FAIL</slot> + `; + document.body.offsetTop; + host.innerHTML = ` + <div slot="myslot" style="width: 100px; height: 100px; background: green"></div> + `; +</script>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-004.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>CSS Scoping Module Level 1 - Dynamic fallback content</title> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model"> +<link rel="match" href="reference/green-box.html"/> +<p>Test passes if you see a single 100px by 100px green box below.</p> +<div id="host"> + <div slot="myslot" style="width: 100px; height: 100px; background: green"></div> +</div> +<script> + let root = host.attachShadow({ mode: "open" }); + root.innerHTML = ` + <slot name="myslot"></slot> + `; + document.body.offsetTop; + let newSlot = document.createElement('slot'); + newSlot.appendChild(document.createTextNode("FAIL")); + newSlot.setAttribute("name", "myslot"); + root.insertBefore(newSlot, root.firstChild); +</script>
new file mode 100644 --- /dev/null +++ b/testing/web-platform/tests/css/css-scoping/shadow-fallback-dynamic-005.html @@ -0,0 +1,20 @@ +<!doctype html> +<title>CSS Scoping Module Level 1 - Dynamic fallback content</title> +<link rel="author" href="mailto:emilio@crisal.io"> +<link rel="help" href="https://drafts.csswg.org/css-scoping/#selectors-data-model"> +<link rel="match" href="reference/green-box.html"/> +<p>Test passes if you see a single 100px by 100px green box below.</p> +<div id="host"> + <div slot="myslot" style="width: 100px; height: 100px; background: green"></div> +</div> +<script> + let root = host.attachShadow({ mode: "open" }); + root.innerHTML = ` + <slot name="myslot"></slot> + <slot name="myotherslot"> + FAIL + </slot> + `; + document.body.offsetTop; + host.firstElementChild.setAttribute("slot", "myotherslot"); +</script>