diff options
85 files changed, 3021 insertions, 1305 deletions
diff --git a/devtools/client/inspector/markup/test/doc_markup_anonymous.html b/devtools/client/inspector/markup/test/doc_markup_anonymous.html index 0ede3ca5ff..bb5389cbe6 100644 --- a/devtools/client/inspector/markup/test/doc_markup_anonymous.html +++ b/devtools/client/inspector/markup/test/doc_markup_anonymous.html @@ -25,10 +25,8 @@ <script> "use strict"; var host = document.querySelector("#shadow"); - if (host.createShadowRoot) { - var root = host.createShadowRoot(); - root.innerHTML = "<h3>Shadow DOM</h3><select multiple></select>"; - } + var root = host.attachShadow({ mode: "open" }); + root.innerHTML = "<h3>Shadow DOM</h3><select multiple></select>"; </script> </body> </html>
\ No newline at end of file diff --git a/devtools/server/tests/mochitest/inspector-traversal-data.html b/devtools/server/tests/mochitest/inspector-traversal-data.html index 45b8c2eded..ada4814d40 100644 --- a/devtools/server/tests/mochitest/inspector-traversal-data.html +++ b/devtools/server/tests/mochitest/inspector-traversal-data.html @@ -21,8 +21,8 @@ // Set up a basic shadow DOM var host = document.querySelector('#shadow'); - if (host.createShadowRoot) { - var root = host.createShadowRoot(); + if (host.attachShadow) { + let root = host.attachShadow({ mode: "open" }); root.innerHTML = '<h3>Shadow <em>DOM</em></h3><select multiple></select>'; } diff --git a/dom/base/ChildIterator.cpp b/dom/base/ChildIterator.cpp index 829077c83a..3d24da3176 100644 --- a/dom/base/ChildIterator.cpp +++ b/dom/base/ChildIterator.cpp @@ -536,23 +536,5 @@ StyleChildrenIterator::IsNeeded(const Element* aElement) return false; } - -nsIContent* -StyleChildrenIterator::GetNextChild() -{ - while (nsIContent* child = AllChildrenIterator::GetNextChild()) { - if (IsNativeAnonymousImplementationOfPseudoElement(child)) { - // Skip any native-anonymous children that are used to implement pseudo- - // elements. These match pseudo-element selectors instead of being - // considered a child of their host, and thus the style system needs to - // handle them separately. - } else { - return child; - } - } - - return nullptr; -} - } // namespace dom } // namespace mozilla diff --git a/dom/base/ChildIterator.h b/dom/base/ChildIterator.h index 86a5754d05..4932fe40a0 100644 --- a/dom/base/ChildIterator.h +++ b/dom/base/ChildIterator.h @@ -7,6 +7,8 @@ #define ChildIterator_h #include "nsIContent.h" +#include "nsIContentInlines.h" +#include <stdint.h> /** * Iterates over the children on a node. If a child is an insertion point, @@ -17,9 +19,6 @@ * binding's <xbl:content> element. */ -#include <stdint.h> -#include "nsAutoPtr.h" - class nsIContent; namespace mozilla { @@ -189,20 +188,33 @@ protected: class AllChildrenIterator : private FlattenedChildIterator { public: - AllChildrenIterator(const nsIContent* aNode, uint32_t aFlags, - bool aStartAtBeginning = true) : - FlattenedChildIterator(aNode, aFlags, aStartAtBeginning), - mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0), - mFlags(aFlags), mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) { } + AllChildrenIterator(const nsIContent* aNode, + uint32_t aFlags, + bool aStartAtBeginning = true) + : FlattenedChildIterator(aNode, aFlags, aStartAtBeginning) + , mAnonKidsIdx(aStartAtBeginning ? UINT32_MAX : 0) + , mFlags(aFlags), mPhase(aStartAtBeginning ? eAtBegin : eAtEnd) + { + } AllChildrenIterator(AllChildrenIterator&& aOther) - : FlattenedChildIterator(Move(aOther)), - mAnonKids(Move(aOther.mAnonKids)), mAnonKidsIdx(aOther.mAnonKidsIdx), - mFlags(aOther.mFlags), mPhase(aOther.mPhase) + : FlattenedChildIterator(Move(aOther)) + , mAnonKids(Move(aOther.mAnonKids)) + , mAnonKidsIdx(aOther.mAnonKidsIdx) + , mFlags(aOther.mFlags) + , mPhase(aOther.mPhase) #ifdef DEBUG - , mMutationGuard(aOther.mMutationGuard) + , mMutationGuard(aOther.mMutationGuard) #endif - {} + { + } + + AllChildrenIterator& operator=(AllChildrenIterator&& aOther) + { + this->~AllChildrenIterator(); + new (this)AllChildrenIterator(Move(aOther)); + return *this; + } #ifdef DEBUG ~AllChildrenIterator() { MOZ_ASSERT(!mMutationGuard.Mutated(0)); } @@ -268,20 +280,43 @@ private: */ class StyleChildrenIterator : private AllChildrenIterator { public: - explicit StyleChildrenIterator(const nsIContent* aContent) + StyleChildrenIterator(const nsIContent* aContent, + bool aStartAtBeginning = true) : AllChildrenIterator(aContent, nsIContent::eAllChildren | - nsIContent::eSkipDocumentLevelNativeAnonymousContent) + nsIContent::eSkipDocumentLevelNativeAnonymousContent, + aStartAtBeginning) + { + MOZ_COUNT_CTOR(StyleChildrenIterator); + } + + StyleChildrenIterator(StyleChildrenIterator&& aOther) + : AllChildrenIterator(Move(aOther)) { MOZ_COUNT_CTOR(StyleChildrenIterator); } + + StyleChildrenIterator& operator=(StyleChildrenIterator&& aOther) + { + AllChildrenIterator::operator=(Move(aOther)); + return *this; + } + ~StyleChildrenIterator() { MOZ_COUNT_DTOR(StyleChildrenIterator); } - nsIContent* GetNextChild(); + static nsIContent* GetParent(const nsIContent& aContent) + { + nsINode* node = aContent.GetFlattenedTreeParentNodeForStyle(); + return node && node->IsContent() ? node->AsContent() : nullptr; + } // Returns true if we cannot find all the children we need to style by // traversing the siblings of the first child. static bool IsNeeded(const Element* aParent); + + using AllChildrenIterator::GetNextChild; + using AllChildrenIterator::GetPreviousChild; + using AllChildrenIterator::Seek; }; } // namespace dom diff --git a/dom/base/DocumentOrShadowRoot.cpp b/dom/base/DocumentOrShadowRoot.cpp index 6213946075..f420ff6a28 100644 --- a/dom/base/DocumentOrShadowRoot.cpp +++ b/dom/base/DocumentOrShadowRoot.cpp @@ -4,11 +4,14 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "DocumentOrShadowRoot.h" +#include "mozilla/EventStateManager.h" #include "mozilla/dom/StyleSheetList.h" #include "nsDocument.h" #include "nsFocusManager.h" #include "ShadowRoot.h" #include "XULDocument.h" +#include "nsLayoutUtils.h" +#include "nsSVGUtils.h" class nsINode; class nsIDocument; @@ -144,5 +147,158 @@ DocumentOrShadowRoot::GetRetargetedFocusedElement() return nullptr; } +Element* +DocumentOrShadowRoot::GetPointerLockElement() +{ + nsCOMPtr<Element> pointerLockedElement = + do_QueryReferent(EventStateManager::sPointerLockedElement); + if (!pointerLockedElement) { + return nullptr; + } + + nsIContent* retargetedPointerLockedElement = Retarget(pointerLockedElement); + return + retargetedPointerLockedElement && retargetedPointerLockedElement->IsElement() ? + retargetedPointerLockedElement->AsElement() : nullptr; +} + +Element* +DocumentOrShadowRoot::GetFullscreenElement() +{ + if (!AsNode().IsInComposedDoc()) { + return nullptr; + } + + Element* element = AsNode().OwnerDoc()->FullScreenStackTop(); + NS_ASSERTION(!element || + element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), + "Fullscreen element should have fullscreen styles applied"); + + nsIContent* retargeted = Retarget(element); + if (retargeted && retargeted->IsElement()) { + return retargeted->AsElement(); + } + + return nullptr; +} + +Element* +DocumentOrShadowRoot::ElementFromPoint(float aX, float aY) +{ + return ElementFromPointHelper(aX, aY, false, true); +} + +void +DocumentOrShadowRoot::ElementsFromPoint(float aX, float aY, + nsTArray<RefPtr<Element>>& aElements) +{ + ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements); +} + +Element* +DocumentOrShadowRoot::ElementFromPointHelper(float aX, float aY, + bool aIgnoreRootScrollFrame, + bool aFlushLayout) +{ + AutoTArray<RefPtr<Element>, 1> elementArray; + ElementsFromPointHelper(aX, aY, + ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) | + (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) | + nsIDocument::IS_ELEMENT_FROM_POINT), + elementArray); + if (elementArray.IsEmpty()) { + return nullptr; + } + return elementArray[0]; +} + +void +DocumentOrShadowRoot::ElementsFromPointHelper(float aX, float aY, + uint32_t aFlags, + nsTArray<RefPtr<mozilla::dom::Element>>& aElements) +{ + // As per the the spec, we return null if either coord is negative + if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) { + return; + } + + nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); + nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); + nsPoint pt(x, y); + + nsCOMPtr<nsIDocument> doc = AsNode().OwnerDoc(); + + // Make sure the layout information we get is up-to-date, and + // ensure we get a root frame (for everything but XUL) + if (aFlags & nsIDocument::FLUSH_LAYOUT) { + doc->FlushPendingNotifications(Flush_Layout); + } + + nsIPresShell* ps = doc->GetShell(); + if (!ps) { + return; + } + nsIFrame* rootFrame = ps->GetRootFrame(); + + // XUL docs, unlike HTML, have no frame tree until everything's done loading + if (!rootFrame) { + return; // return null to premature XUL callers as a reminder to wait + } + + nsTArray<nsIFrame*> outFrames; + // Emulate what GetFrameAtPoint does, since we want all the frames under our + // point. + nsLayoutUtils::GetFramesForArea(rootFrame, nsRect(pt, nsSize(1, 1)), outFrames, + nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | + ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); + + // Dunno when this would ever happen, as we should at least have a root frame under us? + if (outFrames.IsEmpty()) { + return; + } + + // Used to filter out repeated elements in sequence. + nsIContent* lastAdded = nullptr; + + for (uint32_t i = 0; i < outFrames.Length(); i++) { + nsIContent* node = doc->GetContentInThisDocument(outFrames[i]); + + if (!node || !node->IsElement()) { + // If this helper is called via ElementsFromPoint, we need to make sure + // our frame is an element. Otherwise return whatever the top frame is + // even if it isn't the top-painted element. + // SVG 'text' element's SVGTextFrame doesn't respond to hit-testing, so + // if 'node' is a child of such an element then we need to manually defer + // to the parent here. + if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) && + !outFrames[i]->IsSVGText()) { + continue; + } + node = node->GetParent(); + if (node) { + ShadowRoot* shadow = ShadowRoot::FromNode(node); + if (shadow) { + node = shadow->Host(); + } + } + } + + //XXXsmaug There is plenty of unspec'ed behavior here + // https://github.com/w3c/webcomponents/issues/735 + // https://github.com/w3c/webcomponents/issues/736 + node = Retarget(node); + + if (node && node != lastAdded) { + aElements.AppendElement(node->AsElement()); + lastAdded = node; + // If this helper is called via ElementFromPoint, just return the first + // element we find. + if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) { + return; + } + } + } +} + } } diff --git a/dom/base/DocumentOrShadowRoot.h b/dom/base/DocumentOrShadowRoot.h index 89117f3317..962f7ac0b5 100644 --- a/dom/base/DocumentOrShadowRoot.h +++ b/dom/base/DocumentOrShadowRoot.h @@ -114,6 +114,32 @@ public: ~DocumentOrShadowRoot() = default; + Element* GetPointerLockElement(); + Element* GetFullscreenElement(); + + Element* ElementFromPoint(float aX, float aY); + void ElementsFromPoint(float aX, float aY, + nsTArray<RefPtr<mozilla::dom::Element>>& aElements); + + /** + * Helper for nsIDOMDocument::elementFromPoint implementation that allows + * ignoring the scroll frame and/or avoiding layout flushes. + * + * @see nsIDOMWindowUtils::elementFromPoint + */ + Element* ElementFromPointHelper(float aX, float aY, + bool aIgnoreRootScrollFrame, + bool aFlushLayout); + enum ElementsFromPointFlags + { + IGNORE_ROOT_SCROLL_FRAME = 1, + FLUSH_LAYOUT = 2, + IS_ELEMENT_FROM_POINT = 4 + }; + + void ElementsFromPointHelper(float aX, float aY, uint32_t aFlags, + nsTArray<RefPtr<mozilla::dom::Element>>& aElements); + protected: nsIContent* Retarget(nsIContent* aContent) const; diff --git a/dom/base/Element.cpp b/dom/base/Element.cpp index d44d55bc20..c67e42bd70 100644 --- a/dom/base/Element.cpp +++ b/dom/base/Element.cpp @@ -1122,18 +1122,6 @@ Element::AttachShadow(const ShadowRootInit& aInit, ErrorResult& aError) return nullptr; } - return AttachShadowInternal(aInit.mMode == ShadowRootMode::Closed, aError); -} - -already_AddRefed<ShadowRoot> -Element::CreateShadowRoot(ErrorResult& aError) -{ - return AttachShadowInternal(false, aError); -} - -already_AddRefed<ShadowRoot> -Element::AttachShadowInternal(bool aClosed, ErrorResult& aError) -{ /** * 3. If context object is a shadow host, then throw * an "InvalidStateError" DOMException. @@ -1179,8 +1167,9 @@ Element::AttachShadowInternal(bool aClosed, ErrorResult& aError) * context object???s node document, host is context object, * and mode is init???s mode. */ + bool isClosed = (aInit.mMode == ShadowRootMode::Closed); RefPtr<ShadowRoot> shadowRoot = - new ShadowRoot(this, aClosed, nodeInfo.forget(), protoBinding); + new ShadowRoot(this, isClosed, nodeInfo.forget(), protoBinding); shadowRoot->SetIsComposedDocParticipant(IsInComposedDoc()); @@ -1717,10 +1706,11 @@ Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent, if (!hadParent) { uint32_t editableDescendantChange = EditableInclusiveDescendantCount(this); if (editableDescendantChange != 0) { - // If we are binding a subtree root to the document, we need to update - // the editable descendant count of all the ancestors. + // If we are binding a subtree root to the document, we need to update + // the editable descendant count of all the ancestors. However, we don't + // cross the Shadow DOM boundary (expected behavior is unclear). nsIContent* parent = GetParent(); - while (parent) { + while (parent && parent->IsElement()) { parent->ChangeEditableDescendantCount(editableDescendantChange); parent = parent->GetParent(); } diff --git a/dom/base/Element.h b/dom/base/Element.h index 4830878fd9..7dae897e77 100644 --- a/dom/base/Element.h +++ b/dom/base/Element.h @@ -470,9 +470,6 @@ protected: mState &= ~aStates; } - already_AddRefed<ShadowRoot> AttachShadowInternal(bool aClosed, - ErrorResult& aError); - private: // Need to allow the ESM, nsGlobalWindow, and the focus manager to // set our state @@ -930,9 +927,6 @@ public: void SetSlot(const nsAString& aName, ErrorResult& aError); void GetSlot(nsAString& aName); - // [deprecated] Shadow DOM v0 - already_AddRefed<ShadowRoot> CreateShadowRoot(ErrorResult& aError); - ShadowRoot *FastGetShadowRoot() const { nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); diff --git a/dom/base/FragmentOrElement.cpp b/dom/base/FragmentOrElement.cpp index 8cd4bafa70..412031f98a 100644 --- a/dom/base/FragmentOrElement.cpp +++ b/dom/base/FragmentOrElement.cpp @@ -797,6 +797,22 @@ FindChromeAccessOnlySubtreeOwner(nsIContent* aContent) return aContent; } +already_AddRefed<nsINode> +FindChromeAccessOnlySubtreeOwner(EventTarget* aTarget) +{ + nsCOMPtr<nsINode> node = do_QueryInterface(aTarget); + if (!node || !node->ChromeOnlyAccess()) { + return node.forget(); + } + + if (!node->IsContent()) { + return nullptr; + } + + node = FindChromeAccessOnlySubtreeOwner(node->AsContent()); + return node.forget(); +} + nsresult nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) { @@ -817,24 +833,11 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) // chrome access only subtree or if we are about to propagate out of // a shadow root to a shadow root host. ((this == aVisitor.mEvent->mOriginalTarget && - !ChromeOnlyAccess()) || isAnonForEvents || GetShadowRoot())) { + !ChromeOnlyAccess()) || isAnonForEvents)) { nsCOMPtr<nsIContent> relatedTarget = do_QueryInterface(aVisitor.mEvent->AsMouseEvent()->mRelatedTarget); if (relatedTarget && relatedTarget->OwnerDoc() == OwnerDoc()) { - - // In the web components case, we may need to stop propagation of events - // at shadow root host. - if (GetShadowRoot()) { - nsIContent* adjustedTarget = - Event::GetShadowRelatedTarget(this, relatedTarget); - if (this == adjustedTarget) { - aVisitor.SetParentTarget(nullptr, false); - aVisitor.mCanHandle = false; - return NS_OK; - } - } - // If current target is anonymous for events or we know that related // target is descendant of an element which is anonymous for events, // we may want to stop event propagation. @@ -950,6 +953,104 @@ nsIContent::GetEventTargetParent(EventChainPreVisitor& aVisitor) } else { aVisitor.SetParentTarget(GetComposedDoc(), false); } + + if (!ChromeOnlyAccess() && !aVisitor.mRelatedTargetRetargetedInCurrentScope) { + // We don't support Shadow DOM in native anonymous content yet. + aVisitor.mRelatedTargetRetargetedInCurrentScope = true; + if (aVisitor.mEvent->mOriginalRelatedTarget) { + // https://dom.spec.whatwg.org/#concept-event-dispatch + // Step 3. + // "Let relatedTarget be the result of retargeting event's relatedTarget + // against target if event's relatedTarget is non-null, and null + // otherwise." + // + // This is a bit complicated because the event might be from native + // anonymous content, but we need to deal with non-native anonymous + // content there. + bool initialTarget = this == aVisitor.mEvent->mOriginalTarget; + nsCOMPtr<nsINode> originalTargetAsNode; + // Use of mOriginalTargetIsInAnon is an optimization here. + if (!initialTarget && aVisitor.mOriginalTargetIsInAnon) { + originalTargetAsNode = + FindChromeAccessOnlySubtreeOwner(aVisitor.mEvent->mOriginalTarget); + initialTarget = originalTargetAsNode == this; + } + if (initialTarget) { + nsCOMPtr<nsINode> relatedTargetAsNode = + FindChromeAccessOnlySubtreeOwner(aVisitor.mEvent->mOriginalRelatedTarget); + if (!originalTargetAsNode) { + originalTargetAsNode = + do_QueryInterface(aVisitor.mEvent->mOriginalTarget); + } + + if (relatedTargetAsNode && originalTargetAsNode) { + nsINode* retargetedRelatedTarget = + nsContentUtils::Retarget(relatedTargetAsNode, originalTargetAsNode); + if (originalTargetAsNode == retargetedRelatedTarget && + retargetedRelatedTarget != relatedTargetAsNode) { + // Step 4. + // "If target is relatedTarget and target is not event's + // relatedTarget, then return true." + aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting(); + // Old code relies on mTarget to point to the first element which + // was not added to the event target chain because of mCanHandle + // being false, but in Shadow DOM case mTarget really should + // point to a node in Shadow DOM. + aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope; + return NS_OK; + } + + // Part of step 5. Retargeting target has happened already higher + // up in this method. + // "Append to an event path with event, target, targetOverride, + // relatedTarget, and false." + aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget; + } + } else { + nsCOMPtr<nsINode> relatedTargetAsNode = + FindChromeAccessOnlySubtreeOwner(aVisitor.mEvent->mOriginalRelatedTarget); + if (relatedTargetAsNode) { + // Step 11.3. + // "Let relatedTarget be the result of retargeting event's + // relatedTarget against parent if event's relatedTarget is non-null, + // and null otherwise.". + nsINode* retargetedRelatedTarget = + nsContentUtils::Retarget(relatedTargetAsNode, this); + nsCOMPtr<nsINode> targetInKnownToBeHandledScope = + FindChromeAccessOnlySubtreeOwner(aVisitor.mTargetInKnownToBeHandledScope); + if (nsContentUtils::ContentIsShadowIncludingDescendantOf( + this, targetInKnownToBeHandledScope->SubtreeRoot())) { + // Part of step 11.4. + // "If target's root is a shadow-including inclusive ancestor of + // parent, then" + // "...Append to an event path with event, parent, null, relatedTarget, + // " and slot-in-closed-tree." + aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget; + } else if (this == retargetedRelatedTarget) { + // Step 11.5 + // "Otherwise, if parent and relatedTarget are identical, then set + // parent to null." + aVisitor.IgnoreCurrentTargetBecauseOfShadowDOMRetargeting(); + // Old code relies on mTarget to point to the first element which + // was not added to the event target chain because of mCanHandle + // being false, but in Shadow DOM case mTarget really should + // point to a node in Shadow DOM. + aVisitor.mEvent->mTarget = aVisitor.mTargetInKnownToBeHandledScope; + return NS_OK; + } else { + // Step 11.6 + aVisitor.mRetargetedRelatedTarget = retargetedRelatedTarget; + } + } + } + } + } + + if (slot) { + // Inform that we're about to exit the current scope. + aVisitor.mRelatedTargetRetargetedInCurrentScope = false; + } + return NS_OK; } diff --git a/dom/base/ShadowRoot.cpp b/dom/base/ShadowRoot.cpp index 1f7e8264d6..e31b97262c 100644 --- a/dom/base/ShadowRoot.cpp +++ b/dom/base/ShadowRoot.cpp @@ -101,6 +101,23 @@ ShadowRoot::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) return mozilla::dom::ShadowRootBinding::Wrap(aCx, this, aGivenProto); } +void +ShadowRoot::CloneInternalDataFrom(ShadowRoot* aOther) +{ + size_t sheetCount = aOther->SheetCount(); + for (size_t i = 0; i < sheetCount; ++i) { + StyleSheet* sheet = aOther->SheetAt(i); + if (sheet && sheet->IsApplicable()) { + // TODO: Remove AsGecko() call once Stylo is removed. + RefPtr<CSSStyleSheet> clonedSheet = + sheet->AsGecko()->Clone(nullptr, nullptr, nullptr, nullptr); + if (clonedSheet) { + AppendStyleSheet(*clonedSheet); + } + } + } +} + ShadowRoot* ShadowRoot::FromNode(nsINode* aNode) { @@ -311,6 +328,8 @@ ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) { aVisitor.mCanHandle = true; aVisitor.mRootOfClosedTree = IsClosed(); + // Inform that we're about to exit the current scope. + aVisitor.mRelatedTargetRetargetedInCurrentScope = false; // https://dom.spec.whatwg.org/#ref-for-get-the-parent%E2%91%A6 if (!aVisitor.mEvent->mFlags.mComposed) { @@ -333,12 +352,10 @@ ShadowRoot::GetEventTargetParent(EventChainPreVisitor& aVisitor) nsIContent* shadowHost = GetHost(); aVisitor.SetParentTarget(shadowHost, false); - if (aVisitor.mOriginalTargetIsInAnon) { - nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget)); - if (content && content->GetBindingParent() == shadowHost) { - aVisitor.mEventTargetAtParent = shadowHost; - } - } + nsCOMPtr<nsIContent> content(do_QueryInterface(aVisitor.mEvent->mTarget)); + if (content && content->GetBindingParent() == shadowHost) { + aVisitor.mEventTargetAtParent = shadowHost; + } return NS_OK; } @@ -488,25 +505,6 @@ ShadowRoot::Host() return host->AsElement(); } -bool -ShadowRoot::ApplyAuthorStyles() -{ - return mProtoBinding->InheritsStyle(); -} - -void -ShadowRoot::SetApplyAuthorStyles(bool aApplyAuthorStyles) -{ - mProtoBinding->SetInheritsStyle(aApplyAuthorStyles); - - nsIPresShell* shell = OwnerDoc()->GetShell(); - if (shell) { - OwnerDoc()->BeginUpdate(UPDATE_STYLE); - shell->RecordShadowStyleChange(this); - OwnerDoc()->EndUpdate(UPDATE_STYLE); - } -} - void ShadowRoot::AttributeChanged(nsIDocument* aDocument, Element* aElement, diff --git a/dom/base/ShadowRoot.h b/dom/base/ShadowRoot.h index 63535c9a06..e122a65ef1 100644 --- a/dom/base/ShadowRoot.h +++ b/dom/base/ShadowRoot.h @@ -59,14 +59,17 @@ public: // [deprecated] Shadow DOM v0 void InsertSheet(StyleSheet* aSheet, nsIContent* aLinkingContent); void RemoveSheet(StyleSheet* aSheet); - bool ApplyAuthorStyles(); - void SetApplyAuthorStyles(bool aApplyAuthorStyles); StyleSheetList* StyleSheets() { return &DocumentOrShadowRoot::EnsureDOMStyleSheets(); } /** + * Clones internal state, for example stylesheets, of aOther to 'this'. + */ + void CloneInternalDataFrom(ShadowRoot* aOther); + + /** * Distributes all the explicit children of the pool host to the content * insertion points in this ShadowRoot. */ diff --git a/dom/base/crashtests/1024428-1.html b/dom/base/crashtests/1024428-1.html index ce181c8af3..8c96a66ef7 100644 --- a/dom/base/crashtests/1024428-1.html +++ b/dom/base/crashtests/1024428-1.html @@ -5,8 +5,7 @@ <body> <div id="host"></div> <script> -var s = host.createShadowRoot(); -s.innerHTML = '<input type="range" />'; +host.attachShadow({ mode: "open" }).innerHTML = '<input type="range" />'; </script> </body> </html> diff --git a/dom/base/crashtests/1029710.html b/dom/base/crashtests/1029710.html index b6bda3f267..bb78b561e4 100644 --- a/dom/base/crashtests/1029710.html +++ b/dom/base/crashtests/1029710.html @@ -3,7 +3,7 @@ <body> <script> var x = document.createElement('span'); - x.createShadowRoot(); + x.attachShadow({ mode: "open" }); x.id = 'a'; </script> </body> diff --git a/dom/base/crashtests/1118764.html b/dom/base/crashtests/1118764.html deleted file mode 100644 index 54a4d94b6a..0000000000 --- a/dom/base/crashtests/1118764.html +++ /dev/null @@ -1,33 +0,0 @@ -<!DOCTYPE html> -<html> -<body> -<style> -#foo { - overflow: scroll; - height: 100px; -} -</style> -<div id="foo"> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -Mozilla Firefox<br> -<script> -foo.createShadowRoot().innerHTML = "<content></content>"; -</script> -</body> -</html> diff --git a/dom/base/crashtests/1393806.html b/dom/base/crashtests/1393806.html new file mode 100644 index 0000000000..4e859bf602 --- /dev/null +++ b/dom/base/crashtests/1393806.html @@ -0,0 +1,17 @@ +<html> + <head> + <script> + function fun_0() { + document.implementation.createDocument('', '', null).adoptNode(o2); + } + + o1 = document.createElement('map'); + o2 = document.createElement('iframe'); + document.documentElement.appendChild(o1); + document.documentElement.appendChild(o2); + o1.textContent = 'x'; + document.addEventListener('DOMNodeRemoved', fun_0, false); + o1.innerText = 'x'; + </script> + </head> +</html> diff --git a/dom/base/crashtests/crashtests.list b/dom/base/crashtests/crashtests.list index 40d358b38a..ea64f4762b 100644 --- a/dom/base/crashtests/crashtests.list +++ b/dom/base/crashtests/crashtests.list @@ -209,6 +209,7 @@ load 1230422.html load 1251361.html load 1304437.html pref(clipboard.autocopy,true) load 1385272-1.html +load 1393806.html pref(dom.webcomponents.enabled,true) load 1341693.html pref(dom.webcomponents.enabled,true) load 1419799.html pref(dom.webcomponents.enabled,false) load 1422931.html diff --git a/dom/base/nsContentUtils.cpp b/dom/base/nsContentUtils.cpp index c964f0ce29..990e1bc589 100644 --- a/dom/base/nsContentUtils.cpp +++ b/dom/base/nsContentUtils.cpp @@ -2248,6 +2248,34 @@ nsContentUtils::ContentIsHostIncludingDescendantOf( return false; } +bool +nsContentUtils::ContentIsShadowIncludingDescendantOf( + const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor) +{ + MOZ_ASSERT(aPossibleDescendant, "The possible descendant is null!"); + MOZ_ASSERT(aPossibleAncestor, "The possible ancestor is null!"); + + if (aPossibleAncestor == aPossibleDescendant->GetComposedDoc()) { + return true; + } + + do { + if (aPossibleDescendant == aPossibleAncestor) { + return true; + } + + if (aPossibleDescendant->NodeType() == nsIDOMNode::DOCUMENT_FRAGMENT_NODE) { + ShadowRoot* shadowRoot = + ShadowRoot::FromNode(const_cast<nsINode*>(aPossibleDescendant)); + aPossibleDescendant = shadowRoot ? shadowRoot->GetHost() : nullptr; + } else { + aPossibleDescendant = aPossibleDescendant->GetParentNode(); + } + } while (aPossibleDescendant); + + return false; +} + // static bool nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant, @@ -2266,6 +2294,30 @@ nsContentUtils::ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant, return false; } +// static +nsINode* +nsContentUtils::Retarget(nsINode* aTargetA, nsINode* aTargetB) +{ + while (true && aTargetA) { + // If A's root is not a shadow root... + nsINode* root = aTargetA->SubtreeRoot(); + // TODO: replace with IsShadowRoot() check once bug 1427511 lands. + if (!ShadowRoot::FromNode(root)) { + // ...then return A. + return aTargetA; + } + + // or A's root is a shadow-including inclusive ancestor of B... + if (nsContentUtils::ContentIsShadowIncludingDescendantOf(aTargetB, root)) { + // ...then return A. + return aTargetA; + } + + aTargetA = ShadowRoot::FromNode(root)->GetHost(); + } + + return nullptr; +} // static nsresult @@ -6382,9 +6434,7 @@ nsContentUtils::IsFocusedContent(const nsIContent* aContent) bool nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent) { - //XXXsmaug Shadow DOM spec issue! - // We may need to change this to GetComposedDoc(). - nsIDocument* doc = aContent->GetUncomposedDoc(); + nsIDocument* doc = aContent->GetComposedDoc(); if (!doc) { return false; } diff --git a/dom/base/nsContentUtils.h b/dom/base/nsContentUtils.h index eb67ff6a6a..a0c602f61a 100644 --- a/dom/base/nsContentUtils.h +++ b/dom/base/nsContentUtils.h @@ -280,6 +280,13 @@ public: const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor); /** + * Similar to above, but does special case only ShadowRoot, + * not HTMLTemplateElement. + */ + static bool ContentIsShadowIncludingDescendantOf( + const nsINode* aPossibleDescendant, const nsINode* aPossibleAncestor); + + /** * Similar to ContentIsDescendantOf except it crosses document boundaries, * this function uses ancestor/descendant relations in the composed document * (see shadow DOM spec). @@ -287,6 +294,13 @@ public: static bool ContentIsCrossDocDescendantOf(nsINode* aPossibleDescendant, nsINode* aPossibleAncestor); + /** + * Retarget an object A against an object B + * @see https://dom.spec.whatwg.org/#retarget + */ + static nsINode* + Retarget(nsINode* aTargetA, nsINode* aTargetB); + /* * This method fills the |aArray| with all ancestor nodes of |aNode| * including |aNode| at the zero index. diff --git a/dom/base/nsDocument.cpp b/dom/base/nsDocument.cpp index f5df30ffed..5667d1a0fc 100644 --- a/dom/base/nsDocument.cpp +++ b/dom/base/nsDocument.cpp @@ -3120,106 +3120,6 @@ nsDocument::ElementFromPoint(float aX, float aY, nsIDOMElement** aReturn) return NS_OK; } -Element* -nsIDocument::ElementFromPoint(float aX, float aY) -{ - return ElementFromPointHelper(aX, aY, false, true); -} - -void -nsIDocument::ElementsFromPoint(float aX, float aY, - nsTArray<RefPtr<Element>>& aElements) -{ - ElementsFromPointHelper(aX, aY, nsIDocument::FLUSH_LAYOUT, aElements); -} - -Element* -nsDocument::ElementFromPointHelper(float aX, float aY, - bool aIgnoreRootScrollFrame, - bool aFlushLayout) -{ - AutoTArray<RefPtr<Element>, 1> elementArray; - ElementsFromPointHelper(aX, aY, - ((aIgnoreRootScrollFrame ? nsIDocument::IGNORE_ROOT_SCROLL_FRAME : 0) | - (aFlushLayout ? nsIDocument::FLUSH_LAYOUT : 0) | - nsIDocument::IS_ELEMENT_FROM_POINT), - elementArray); - if (elementArray.IsEmpty()) { - return nullptr; - } - return elementArray[0]; -} - -void -nsDocument::ElementsFromPointHelper(float aX, float aY, - uint32_t aFlags, - nsTArray<RefPtr<mozilla::dom::Element>>& aElements) -{ - // As per the the spec, we return null if either coord is negative - if (!(aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) && (aX < 0 || aY < 0)) { - return; - } - - nscoord x = nsPresContext::CSSPixelsToAppUnits(aX); - nscoord y = nsPresContext::CSSPixelsToAppUnits(aY); - nsPoint pt(x, y); - - // Make sure the layout information we get is up-to-date, and - // ensure we get a root frame (for everything but XUL) - if (aFlags & nsIDocument::FLUSH_LAYOUT) { - FlushPendingNotifications(Flush_Layout); - } - - nsIPresShell *ps = GetShell(); - if (!ps) { - return; - } - nsIFrame *rootFrame = ps->GetRootFrame(); - - // XUL docs, unlike HTML, have no frame tree until everything's done loading - if (!rootFrame) { - return; // return null to premature XUL callers as a reminder to wait - } - - nsTArray<nsIFrame*> outFrames; - // Emulate what GetFrameAtPoint does, since we want all the frames under our - // point. - nsLayoutUtils::GetFramesForArea(rootFrame, nsRect(pt, nsSize(1, 1)), outFrames, - nsLayoutUtils::IGNORE_PAINT_SUPPRESSION | nsLayoutUtils::IGNORE_CROSS_DOC | - ((aFlags & nsIDocument::IGNORE_ROOT_SCROLL_FRAME) ? nsLayoutUtils::IGNORE_ROOT_SCROLL_FRAME : 0)); - - // Dunno when this would ever happen, as we should at least have a root frame under us? - if (outFrames.IsEmpty()) { - return; - } - - // Used to filter out repeated elements in sequence. - nsIContent* lastAdded = nullptr; - - for (uint32_t i = 0; i < outFrames.Length(); i++) { - nsIContent* node = GetContentInThisDocument(outFrames[i]); - - if (!node || !node->IsElement()) { - // If this helper is called via ElementsFromPoint, we need to make sure - // our frame is an element. Otherwise return whatever the top frame is - // even if it isn't the top-painted element. - if (!(aFlags & nsIDocument::IS_ELEMENT_FROM_POINT)) { - continue; - } - node = node->GetParent(); - } - if (node && node != lastAdded) { - aElements.AppendElement(node->AsElement()); - lastAdded = node; - // If this helper is called via ElementFromPoint, just return the first - // element we find. - if (aFlags & nsIDocument::IS_ELEMENT_FROM_POINT) { - return; - } - } - } -} - nsresult nsDocument::NodesFromRectHelper(float aX, float aY, float aTopSize, float aRightSize, @@ -5923,14 +5823,9 @@ nsIDocument::ImportNode(nsINode& aNode, bool aDeep, ErrorResult& rv) const case nsIDOMNode::COMMENT_NODE: case nsIDOMNode::DOCUMENT_TYPE_NODE: { - nsCOMPtr<nsINode> newNode; nsCOMArray<nsINode> nodesWithProperties; - rv = nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager, - nodesWithProperties, getter_AddRefs(newNode)); - if (rv.Failed()) { - return nullptr; - } - return newNode.forget(); + return nsNodeUtils::Clone(imported, aDeep, mNodeInfoManager, + &nodesWithProperties, rv); } default: { @@ -7091,8 +6986,8 @@ nsIDocument::AdoptNode(nsINode& aAdoptedNode, ErrorResult& rv) } nsCOMArray<nsINode> nodesWithProperties; - rv = nsNodeUtils::Adopt(adoptedNode, sameDocument ? nullptr : mNodeInfoManager, - newScope, nodesWithProperties); + nsNodeUtils::Adopt(adoptedNode, sameDocument ? nullptr : mNodeInfoManager, + newScope, nodesWithProperties, rv); if (rv.Failed()) { // Disconnect all nodes from their parents, since some have the old document // as their ownerDocument and some have this as their ownerDocument. @@ -8387,7 +8282,7 @@ nsDocument::DoUnblockOnload() } nsIContent* -nsDocument::GetContentInThisDocument(nsIFrame* aFrame) const +nsIDocument::GetContentInThisDocument(nsIFrame* aFrame) const { for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { @@ -8598,7 +8493,7 @@ nsDocument::OnPageHide(bool aPersisted, EnumerateActivityObservers(NotifyActivityChanged, nullptr); ClearPendingFullscreenRequests(this); - if (GetFullscreenElement()) { + if (FullScreenStackTop()) { // If this document was fullscreen, we should exit fullscreen in this // doctree branch. This ensures that if the user navigates while in // fullscreen mode we don't leave its still visible ancestor documents @@ -10302,7 +10197,7 @@ nsIDocument::AsyncExitFullscreen(nsIDocument* aDoc) static bool CountFullscreenSubDocuments(nsIDocument* aDoc, void* aData) { - if (aDoc->GetFullscreenElement()) { + if (aDoc->FullScreenStackTop()) { uint32_t* count = static_cast<uint32_t*>(aData); (*count)++; } @@ -10322,7 +10217,7 @@ nsDocument::IsFullscreenLeaf() { // A fullscreen leaf document is fullscreen, and has no fullscreen // subdocuments. - if (!GetFullscreenElement()) { + if (!FullScreenStackTop()) { return false; } return CountFullscreenSubDocuments(this) == 0; @@ -10331,11 +10226,11 @@ nsDocument::IsFullscreenLeaf() static bool ResetFullScreen(nsIDocument* aDocument, void* aData) { - if (aDocument->GetFullscreenElement()) { + if (aDocument->FullScreenStackTop()) { NS_ASSERTION(CountFullscreenSubDocuments(aDocument) <= 1, "Should have at most 1 fullscreen subdocument."); static_cast<nsDocument*>(aDocument)->CleanupFullscreenState(); - NS_ASSERTION(!aDocument->GetFullscreenElement(), + NS_ASSERTION(!aDocument->FullScreenStackTop(), "Should reset full-screen"); auto changed = reinterpret_cast<nsCOMArray<nsIDocument>*>(aData); changed->AppendElement(aDocument); @@ -10383,7 +10278,7 @@ nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) UnlockPointer(); nsCOMPtr<nsIDocument> root = aMaybeNotARootDoc->GetFullscreenRoot(); - if (!root || !root->GetFullscreenElement()) { + if (!root || !root->FullScreenStackTop()) { // If a document was detached before exiting from fullscreen, it is // possible that the root had left fullscreen state. In this case, // we would not get anything from the ResetFullScreen() call. Root's @@ -10412,7 +10307,7 @@ nsIDocument::ExitFullscreenInDocTree(nsIDocument* aMaybeNotARootDoc) DispatchFullScreenChange(changed[changed.Length() - i - 1]); } - NS_ASSERTION(!root->GetFullscreenElement(), + NS_ASSERTION(!root->FullScreenStackTop(), "Fullscreen root should no longer be a fullscreen doc..."); // Move the top-level window out of fullscreen mode. @@ -10429,7 +10324,7 @@ GetFullscreenLeaf(nsIDocument* aDoc, void* aData) nsIDocument** result = static_cast<nsIDocument**>(aData); *result = aDoc; return false; - } else if (aDoc->GetFullscreenElement()) { + } else if (aDoc->FullScreenStackTop()) { aDoc->EnumerateSubDocuments(GetFullscreenLeaf, aData); } return true; @@ -10448,7 +10343,7 @@ GetFullscreenLeaf(nsIDocument* aDoc) nsIDocument* root = nsContentUtils::GetRootDocument(aDoc); // Check that the root is actually fullscreen so we don't waste time walking // around its descendants. - if (!root->GetFullscreenElement()) { + if (!root->FullScreenStackTop()) { return nullptr; } GetFullscreenLeaf(root, &leaf); @@ -10458,10 +10353,10 @@ GetFullscreenLeaf(nsIDocument* aDoc) void nsDocument::RestorePreviousFullScreenState() { - NS_ASSERTION(!GetFullscreenElement() || !FullscreenRoots::IsEmpty(), + NS_ASSERTION(!FullScreenStackTop() || !FullscreenRoots::IsEmpty(), "Should have at least 1 fullscreen root when fullscreen!"); - if (!GetFullscreenElement() || !GetWindow() || FullscreenRoots::IsEmpty()) { + if (!FullScreenStackTop() || !GetWindow() || FullscreenRoots::IsEmpty()) { return; } @@ -10636,7 +10531,7 @@ nsDocument::FullScreenStackPush(Element* aElement) } EventStateManager::SetFullScreenState(aElement, true); mFullScreenStack.AppendElement(do_GetWeakReference(aElement)); - NS_ASSERTION(GetFullscreenElement() == aElement, "Should match"); + NS_ASSERTION(FullScreenStackTop() == aElement, "Should match"); UpdateViewportScrollbarOverrideForFullscreen(this); return true; } @@ -10684,7 +10579,7 @@ nsDocument::FullScreenStackTop() uint32_t last = mFullScreenStack.Length() - 1; nsCOMPtr<Element> element(do_QueryReferent(mFullScreenStack[last])); NS_ASSERTION(element, "Should have full-screen element!"); - NS_ASSERTION(element->IsInUncomposedDoc(), "Full-screen element should be in doc"); + NS_ASSERTION(element->IsInComposedDoc(), "Full-screen element should be in doc"); NS_ASSERTION(element->OwnerDoc() == this, "Full-screen element should be in this doc"); return element; } @@ -10762,7 +10657,7 @@ nsresult nsDocument::RemoteFrameFullscreenReverted() } /* static */ bool -nsDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject) +nsIDocument::IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject) { MOZ_ASSERT(NS_IsMainThread()); return nsContentUtils::IsCallerChrome() || @@ -10810,10 +10705,10 @@ nsDocument::FullscreenElementReadyCheck(Element* aElement, { NS_ASSERTION(aElement, "Must pass non-null element to nsDocument::RequestFullScreen"); - if (!aElement || aElement == GetFullscreenElement()) { + if (!aElement || aElement == FullScreenStackTop()) { return false; } - if (!aElement->IsInUncomposedDoc()) { + if (!aElement->IsInComposedDoc()) { DispatchFullscreenError("FullscreenDeniedNotInDocument"); return false; } @@ -10837,8 +10732,11 @@ nsDocument::FullscreenElementReadyCheck(Element* aElement, DispatchFullscreenError("FullscreenDeniedSubDocFullScreen"); return false; } - if (GetFullscreenElement() && - !nsContentUtils::ContentIsDescendantOf(aElement, GetFullscreenElement())) { + //XXXsmaug Note, we don't follow the latest fullscreen spec here. + // This whole check could be probably removed. + if (FullScreenStackTop() && + !nsContentUtils::ContentIsHostIncludingDescendantOf(aElement, + FullScreenStackTop())) { // If this document is full-screen, only grant full-screen requests from // a descendant of the current full-screen element. DispatchFullscreenError("FullscreenDeniedNotDescendant"); @@ -11198,16 +11096,6 @@ nsDocument::GetMozFullScreenElement(nsIDOMElement **aFullScreenElement) return NS_OK; } -Element* -nsDocument::GetFullscreenElement() -{ - Element* element = FullScreenStackTop(); - NS_ASSERTION(!element || - element->State().HasState(NS_EVENT_STATE_FULL_SCREEN), - "Fullscreen element should have fullscreen styles applied"); - return element; -} - NS_IMETHODIMP nsDocument::GetMozFullScreen(bool *aFullScreen) { @@ -11326,7 +11214,7 @@ GetPointerLockError(Element* aElement, Element* aCurrentLock, return "PointerLockDeniedInUse"; } - if (!aElement->IsInUncomposedDoc()) { + if (!aElement->IsInComposedDoc()) { return "PointerLockDeniedNotInDocument"; } @@ -11377,11 +11265,11 @@ ChangePointerLockedElement(Element* aElement, nsIDocument* aDocument, MOZ_ASSERT(aDocument); MOZ_ASSERT(aElement != aPointerLockedElement); if (aPointerLockedElement) { - MOZ_ASSERT(aPointerLockedElement->GetUncomposedDoc() == aDocument); + MOZ_ASSERT(aPointerLockedElement->GetComposedDoc() == aDocument); aPointerLockedElement->ClearPointerLock(); } if (aElement) { - MOZ_ASSERT(aElement->GetUncomposedDoc() == aDocument); + MOZ_ASSERT(aElement->GetComposedDoc() == aDocument); aElement->SetPointerLock(); EventStateManager::sPointerLockedElement = do_GetWeakReference(aElement); EventStateManager::sPointerLockedDoc = do_GetWeakReference(aDocument); @@ -11405,9 +11293,9 @@ PointerLockRequest::Run() nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument); nsDocument* d = static_cast<nsDocument*>(doc.get()); const char* error = nullptr; - if (!e || !d || !e->GetUncomposedDoc()) { + if (!e || !d || !e->GetComposedDoc()) { error = "PointerLockDeniedNotInDocument"; - } else if (e->GetUncomposedDoc() != d) { + } else if (e->GetComposedDoc() != d) { error = "PointerLockDeniedMovedDocument"; } if (!error) { @@ -11573,25 +11461,6 @@ nsDocument::GetMozPointerLockElement(nsIDOMElement** aPointerLockedElement) return NS_OK; } -Element* -nsIDocument::GetPointerLockElement() -{ - nsCOMPtr<Element> pointerLockedElement = - do_QueryReferent(EventStateManager::sPointerLockedElement); - if (!pointerLockedElement) { - return nullptr; - } - - // Make sure pointer locked element is in the same document. - nsCOMPtr<nsIDocument> pointerLockedDoc = - do_QueryReferent(EventStateManager::sPointerLockedDoc); - if (pointerLockedDoc != this) { - return nullptr; - } - - return pointerLockedElement; -} - nsresult nsDocument::Observe(nsISupports *aSubject, const char *aTopic, diff --git a/dom/base/nsDocument.h b/dom/base/nsDocument.h index 010f95ae25..923fb49ae9 100644 --- a/dom/base/nsDocument.h +++ b/dom/base/nsDocument.h @@ -692,14 +692,6 @@ public: nsIAtom* aAttrName, const nsAString& aAttrValue) const override; - virtual Element* ElementFromPointHelper(float aX, float aY, - bool aIgnoreRootScrollFrame, - bool aFlushLayout) override; - - virtual void ElementsFromPointHelper(float aX, float aY, - uint32_t aFlags, - nsTArray<RefPtr<mozilla::dom::Element>>& aElements) override; - virtual nsresult NodesFromRectHelper(float aX, float aY, float aTopSize, float aRightSize, float aBottomSize, float aLeftSize, @@ -896,8 +888,6 @@ public: // already_AddRefed<nsSimpleContentList> BlockedTrackingNodes() const; - static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject); - // Do the "fullscreen element ready check" from the fullscreen spec. // It returns true if the given element is allowed to go into fullscreen. bool FullscreenElementReadyCheck(Element* aElement, bool aWasCallerChrome); @@ -921,11 +911,10 @@ public: void FullScreenStackPop(); // Returns the top element from the full-screen stack. - Element* FullScreenStackTop(); + Element* FullScreenStackTop() override; // DOM-exposed fullscreen API bool FullscreenEnabled() override; - Element* GetFullscreenElement() override; void RequestPointerLock(Element* aElement) override; bool SetPointerLock(Element* aElement, int aCursorStyle); @@ -1315,16 +1304,6 @@ private: nsresult InitCSP(nsIChannel* aChannel); - /** - * Find the (non-anonymous) content in this document for aFrame. It will - * be aFrame's content node if that content is in this document and not - * anonymous. Otherwise, when aFrame is in a subdocument, we use the frame - * element containing the subdocument containing aFrame, and/or find the - * nearest non-anonymous ancestor in this document. - * Returns null if there is no such element. - */ - nsIContent* GetContentInThisDocument(nsIFrame* aFrame) const; - // Just like EnableStyleSheetsForSet, but doesn't check whether // aSheetSet is null and allows the caller to control whether to set // aSheetSet as the preferred set in the CSSLoader. diff --git a/dom/base/nsFocusManager.cpp b/dom/base/nsFocusManager.cpp index 6cc5bbd36f..955c3331ff 100644 --- a/dom/base/nsFocusManager.cpp +++ b/dom/base/nsFocusManager.cpp @@ -8,10 +8,12 @@ #include "nsFocusManager.h" #include "AccessibleCaretEventHub.h" +#include "ChildIterator.h" +#include "nsAttrValueInlines.h" #include "nsIInterfaceRequestorUtils.h" #include "nsGkAtoms.h" #include "nsContentUtils.h" -#include "nsIDocument.h" +#include "nsDocument.h" #include "nsIEditor.h" #include "nsPIDOMWindow.h" #include "nsIDOMChromeWindow.h" @@ -46,7 +48,9 @@ #include "mozilla/ContentEvents.h" #include "mozilla/dom/Element.h" +#include "mozilla/dom/ShadowRoot.h" #include "mozilla/dom/HTMLInputElement.h" +#include "mozilla/dom/HTMLSlotElement.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" @@ -2972,6 +2976,378 @@ nsFocusManager::DetermineElementToMoveFocus(nsPIDOMWindowOuter* aWindow, return NS_OK; } +static bool +IsHostOrSlot(nsIContent* aContent) +{ + return aContent && (aContent->GetShadowRoot() || + aContent->IsHTMLElement(nsGkAtoms::slot)); +} + +// Helper class to iterate contents in scope by traversing flattened tree +// in tree order +class MOZ_STACK_CLASS ScopedContentTraversal +{ +public: + ScopedContentTraversal(nsIContent* aStartContent, nsIContent* aOwner) + : mCurrent(aStartContent) + , mOwner(aOwner) + { + MOZ_ASSERT(aStartContent); + } + + void Next(); + void Prev(); + + void Reset() + { + SetCurrent(mOwner); + } + + nsIContent* GetCurrent() + { + return mCurrent; + } + +private: + void SetCurrent(nsIContent* aContent) + { + mCurrent = aContent; + } + + nsIContent* mCurrent; + nsIContent* mOwner; +}; + +void +ScopedContentTraversal::Next() +{ + MOZ_ASSERT(mCurrent); + + // Get mCurrent's first child if it's in the same scope. + if (!IsHostOrSlot(mCurrent) || mCurrent == mOwner) { + StyleChildrenIterator iter(mCurrent); + nsIContent* child = iter.GetNextChild(); + if (child) { + SetCurrent(child); + return; + } + } + + // If mOwner has no children, END traversal + if (mCurrent == mOwner) { + SetCurrent(nullptr); + return; + } + + nsIContent* current = mCurrent; + while (1) { + // Create parent's iterator and move to current + nsIContent* parent = current->GetFlattenedTreeParent(); + StyleChildrenIterator parentIter(parent); + parentIter.Seek(current); + + // Get next sibling of current + if (nsIContent* next = parentIter.GetNextChild()) { + SetCurrent(next); + return; + } + + // If no next sibling and parent is mOwner, END traversal + if (parent == mOwner) { + SetCurrent(nullptr); + return; + } + + current = parent; + } +} + +void +ScopedContentTraversal::Prev() +{ + MOZ_ASSERT(mCurrent); + + nsIContent* parent; + nsIContent* last; + if (mCurrent == mOwner) { + // Get last child of mOwner + StyleChildrenIterator ownerIter(mOwner, false /* aStartAtBeginning */); + last = ownerIter.GetPreviousChild(); + + parent = last; + } else { + // Create parent's iterator and move to mCurrent + parent = mCurrent->GetFlattenedTreeParent(); + StyleChildrenIterator parentIter(parent); + parentIter.Seek(mCurrent); + + // Get previous sibling + last = parentIter.GetPreviousChild(); + } + + while (last) { + parent = last; + if (parent->GetShadowRoot() || + parent->IsHTMLElement(nsGkAtoms::slot)) { + // Skip contents in other scopes + break; + } + + // Find last child + StyleChildrenIterator iter(parent, false /* aStartAtBeginning */); + last = iter.GetPreviousChild(); + } + + // If parent is mOwner and no previous sibling remains, END traversal + SetCurrent(parent == mOwner ? nullptr : parent); +} + +nsIContent* +nsFocusManager::FindOwner(nsIContent* aContent) +{ + nsIContent* currentContent = aContent; + while (currentContent) { + nsIContent* parent = currentContent->GetFlattenedTreeParent(); + + // Shadow host / Slot + if (IsHostOrSlot(parent)) { + return parent; + } + + currentContent = parent; + } + + return nullptr; +} + +/** + * Host and Slot elements need to be handled as if they had tabindex 0 even + * when they don't have the attribute. This is a helper method to get the + * right value for focus navigation. If aIsFocusable is passed, it is set to + * true if the element itself is focusable. + */ +static int32_t +HostOrSlotTabIndexValue(nsIContent* aContent, + bool* aIsFocusable = nullptr) +{ + MOZ_ASSERT(IsHostOrSlot(aContent)); + + if (aIsFocusable) { + *aIsFocusable = false; + nsIFrame* frame = aContent->GetPrimaryFrame(); + if (frame) { + int32_t tabIndex; + frame->IsFocusable(&tabIndex, 0); + *aIsFocusable = tabIndex >= 0; + } + } + + const nsAttrValue* attrVal = + aContent->AsElement()->GetParsedAttr(nsGkAtoms::tabindex); + if (!attrVal) { + return 0; + } + + if (attrVal->Type() == nsAttrValue::eInteger) { + return attrVal->GetIntegerValue(); + } + + return -1; +} + +nsIContent* +nsFocusManager::GetNextTabbableContentInScope(nsIContent* aOwner, + nsIContent* aStartContent, + nsIContent* aOriginalStartContent, + bool aForward, + int32_t aCurrentTabIndex, + bool aIgnoreTabIndex, + bool aForDocumentNavigation, + bool aSkipOwner) +{ + MOZ_ASSERT(IsHostOrSlot(aOwner), "Scope owner should be host or slot"); + + if (!aSkipOwner && (aForward && aOwner == aStartContent)) { + int32_t tabIndex = -1; + nsIFrame* frame = aOwner->GetPrimaryFrame(); + if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) { + return aOwner; + } + } + + // + // Iterate contents in scope + // + ScopedContentTraversal contentTraversal(aStartContent, aOwner); + nsCOMPtr<nsIContent> iterContent; + nsIContent* firstNonChromeOnly = aStartContent->IsInNativeAnonymousSubtree() ? + aStartContent->FindFirstNonChromeOnlyAccessContent() : nullptr; + while (1) { + // Iterate tab index to find corresponding contents in scope + + while (1) { + // Iterate remaining contents in scope to find next content to focus + + // Get next content + aForward ? contentTraversal.Next() : contentTraversal.Prev(); + iterContent = contentTraversal.GetCurrent(); + + if (firstNonChromeOnly && firstNonChromeOnly == iterContent) { + // We just broke out from the native anonymous content, so move + // to the previous/next node of the native anonymous owner. + if (aForward) { + contentTraversal.Next(); + } else { + contentTraversal.Prev(); + } + iterContent = contentTraversal.GetCurrent(); + } + + if (!iterContent) { + // Reach the end + break; + } + + // Get the tab index of the next element. For NAC we rely on frames. + //XXXsmaug we should probably use frames also for Shadow DOM and special + // case only display:contents elements. + int32_t tabIndex = 0; + if (iterContent->IsInNativeAnonymousSubtree() && + iterContent->GetPrimaryFrame()) { + iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex); + } else if (IsHostOrSlot(iterContent)) { + tabIndex = HostOrSlotTabIndexValue(iterContent); + } else { + iterContent->IsFocusable(&tabIndex); + } + if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) { + continue; + } + + if (!IsHostOrSlot(iterContent)) { + nsCOMPtr<nsIContent> elementInFrame; + bool checkSubDocument = true; + if (aForDocumentNavigation && + TryDocumentNavigation(iterContent, &checkSubDocument, + getter_AddRefs(elementInFrame))) { + return elementInFrame; + } + if (!checkSubDocument) { + continue; + } + + if (TryToMoveFocusToSubDocument(iterContent, aOriginalStartContent, + aForward, aForDocumentNavigation, + getter_AddRefs(elementInFrame))) { + return elementInFrame; + } + + // Found content to focus + return iterContent; + } + + // Search in scope owned by iterContent + nsIContent* contentToFocus = + GetNextTabbableContentInScope(iterContent, iterContent, + aOriginalStartContent, aForward, + aForward ? 1 : 0, aIgnoreTabIndex, + aForDocumentNavigation, + false /* aSkipOwner */); + if (contentToFocus) { + return contentToFocus; + } + }; + + // If already at lowest priority tab (0), end search completely. + // A bit counterintuitive but true, tabindex order goes 1, 2, ... 32767, 0 + if (aCurrentTabIndex == (aForward ? 0 : 1)) { + break; + } + + // Continue looking for next highest priority tabindex + aCurrentTabIndex = GetNextTabIndex(aOwner, aCurrentTabIndex, aForward); + contentTraversal.Reset(); + } + + // Return scope owner at last for backward navigation if its tabindex + // is non-negative + if (!aSkipOwner && !aForward) { + int32_t tabIndex = -1; + nsIFrame* frame = aOwner->GetPrimaryFrame(); + if (frame && frame->IsFocusable(&tabIndex, false) && tabIndex >= 0) { + return aOwner; + } + } + + return nullptr; +} + +nsIContent* +nsFocusManager::GetNextTabbableContentInAncestorScopes( + nsIContent* aStartOwner, + nsIContent** aStartContent, + nsIContent* aOriginalStartContent, + bool aForward, + int32_t* aCurrentTabIndex, + bool aIgnoreTabIndex, + bool aForDocumentNavigation) +{ + MOZ_ASSERT(aStartOwner == FindOwner(*aStartContent), + "aStartOwner should be the scope owner of aStartContent"); + MOZ_ASSERT(IsHostOrSlot(aStartOwner), "scope owner should be host or slot"); + + nsIContent* owner = aStartOwner; + nsIContent* startContent = *aStartContent; + + while (IsHostOrSlot(owner)) { + int32_t tabIndex = 0; + if (IsHostOrSlot(startContent)) { + tabIndex = HostOrSlotTabIndexValue(startContent); + } else { + startContent->IsFocusable(&tabIndex); + } + nsIContent* contentToFocus = + GetNextTabbableContentInScope(owner, startContent, aOriginalStartContent, + aForward, tabIndex, aIgnoreTabIndex, + aForDocumentNavigation, + false /* aSkipOwner */); + if (contentToFocus) { + return contentToFocus; + } + + startContent = owner; + owner = FindOwner(startContent); + } + + // If not found in shadow DOM, search from the top level shadow host in light DOM + *aStartContent = startContent; + *aCurrentTabIndex = HostOrSlotTabIndexValue(startContent); + + return nullptr; +} + +static nsIContent* +GetTopLevelScopeOwner(nsIContent* aContent) +{ + nsIContent* topLevelScopeOwner = nullptr; + while (aContent) { + if (HTMLSlotElement* slot = aContent->GetAssignedSlot()) { + aContent = slot; + } else if (ShadowRoot* shadowRoot = aContent->GetContainingShadow()) { + aContent = shadowRoot->Host(); + topLevelScopeOwner = aContent; + } else { + // TODO: replaced with FromNode. + if (HTMLSlotElement::FromContentOrNull(aContent)) { + topLevelScopeOwner = aContent; + } + aContent = aContent->GetParent(); + } + } + + return topLevelScopeOwner; +} + nsresult nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, nsIContent* aRootContent, @@ -2989,70 +3365,163 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, if (!startContent) return NS_OK; + nsIContent* currentTopLevelScopeOwner = GetTopLevelScopeOwner(aStartContent); + LOGCONTENTNAVIGATION("GetNextTabbable: %s", aStartContent); LOGFOCUSNAVIGATION((" tabindex: %d", aCurrentTabIndex)); + if (nsDocument::IsWebComponentsEnabled(aRootContent)) { + // If aStartContent is a shadow host or slot in forward navigation, + // search in scope owned by aStartContent. + if (aForward && IsHostOrSlot(aStartContent)) { + nsIContent* contentToFocus = + GetNextTabbableContentInScope(aStartContent, aStartContent, + aOriginalStartContent, aForward, + aForward ? 1 : 0, aIgnoreTabIndex, + aForDocumentNavigation, + true /* aSkipOwner */); + if (contentToFocus) { + NS_ADDREF(*aResultContent = contentToFocus); + return NS_OK; + } + } + + // If aStartContent is in a scope owned by Shadow DOM search from scope + // including aStartContent. + if (nsIContent* owner = FindOwner(aStartContent)) { + nsIContent* contentToFocus = + GetNextTabbableContentInAncestorScopes(owner, + &aStartContent, + aOriginalStartContent, + aForward, + &aCurrentTabIndex, + aIgnoreTabIndex, + aForDocumentNavigation); + if (contentToFocus) { + NS_ADDREF(*aResultContent = contentToFocus); + return NS_OK; + } + } + + // If we reach here, it means no next tabbable content in shadow DOM. + // We need to continue searching in light DOM, starting at the top level + // shadow host in light DOM (updated aStartContent) and its tabindex + // (updated aCurrentTabIndex). + MOZ_ASSERT(!FindOwner(aStartContent), + "aStartContent should not be owned by Shadow DOM at this point"); + } + nsPresContext* presContext = aPresShell->GetPresContext(); bool getNextFrame = true; nsCOMPtr<nsIContent> iterStartContent = aStartContent; + // Iterate tab index to find corresponding contents while (1) { - nsIFrame* startFrame = iterStartContent->GetPrimaryFrame(); + nsIFrame* frame = iterStartContent->GetPrimaryFrame(); // if there is no frame, look for another content node that has a frame - if (!startFrame) { + while (!frame) { // if the root content doesn't have a frame, just return - if (iterStartContent == aRootContent) + if (iterStartContent == aRootContent) { return NS_OK; + } // look for the next or previous content node in tree order - iterStartContent = aForward ? iterStartContent->GetNextNode() : iterStartContent->GetPreviousContent(); + iterStartContent = aForward ? iterStartContent->GetNextNode() + : iterStartContent->GetPreviousContent(); + if (!iterStartContent) { + break; + } + + frame = iterStartContent->GetPrimaryFrame(); + // Host without frame, enter its scope. + if (nsDocument::IsWebComponentsEnabled(aRootContent) && + (!frame && iterStartContent->GetShadowRoot())) { + int32_t tabIndex = HostOrSlotTabIndexValue(iterStartContent); + if (tabIndex >= 0 && + (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { + nsIContent* contentToFocus = GetNextTabbableContentInScope( + iterStartContent, iterStartContent, aOriginalStartContent, + aForward, aForward ? 1 : 0, aIgnoreTabIndex, + aForDocumentNavigation, true /* aSkipOwner */); + if (contentToFocus) { + NS_ADDREF(*aResultContent = contentToFocus); + return NS_OK; + } + } + } // we've already skipped over the initial focused content, so we // don't want to traverse frames. getNextFrame = false; - if (iterStartContent) - continue; - - // otherwise, as a last attempt, just look at the root content - iterStartContent = aRootContent; - continue; } - // For tab navigation, pass false for aSkipPopupChecks so that we don't - // iterate into or out of a popup. For document naviation pass true to - // ignore these boundaries. nsCOMPtr<nsIFrameEnumerator> frameTraversal; - nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal), - presContext, startFrame, - ePreOrder, - false, // aVisual - false, // aLockInScrollView - true, // aFollowOOFs - aForDocumentNavigation // aSkipPopupChecks - ); - NS_ENSURE_SUCCESS(rv, rv); - - if (iterStartContent == aRootContent) { - if (!aForward) { - frameTraversal->Last(); - } else if (aRootContent->IsFocusable()) { - frameTraversal->Next(); + if (frame) { + // For tab navigation, pass false for aSkipPopupChecks so that we don't + // iterate into or out of a popup. For document navigation, pass true to + // ignore these boundaries. + nsresult rv = NS_NewFrameTraversal(getter_AddRefs(frameTraversal), + presContext, + frame, + ePreOrder, + false, // aVisual + false, // aLockInScrollView + true, // aFollowOOFs + aForDocumentNavigation // aSkipPopupChecks + ); + NS_ENSURE_SUCCESS(rv, rv); + + if (iterStartContent == aRootContent) { + if (!aForward) { + frameTraversal->Last(); + } else if (aRootContent->IsFocusable()) { + frameTraversal->Next(); + } + frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); + } else if (getNextFrame && + (!iterStartContent || + !iterStartContent->IsHTMLElement(nsGkAtoms::area))) { + // Need to do special check in case we're in an imagemap which has multiple + // content nodes per frame, so don't skip over the starting frame. + if (aForward) { + frameTraversal->Next(); + } else { + frameTraversal->Prev(); + } + + frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); } } - else if (getNextFrame && - (!iterStartContent || - !iterStartContent->IsHTMLElement(nsGkAtoms::area))) { - // Need to do special check in case we're in an imagemap which has multiple - // content nodes per frame, so don't skip over the starting frame. - if (aForward) - frameTraversal->Next(); - else - frameTraversal->Prev(); - } // Walk frames to find something tabbable matching mCurrentTabIndex - nsIFrame* frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); while (frame) { nsIContent* currentContent = frame->GetContent(); + if (nsDocument::IsWebComponentsEnabled(currentContent)) { + // Try to find the topmost scope owner, since we want to skip the node + // that is not owned by document in frame traversal. + nsIContent* oldTopLevelScopeOwner = currentTopLevelScopeOwner; + if (!aForward || oldTopLevelScopeOwner != currentContent) { + currentTopLevelScopeOwner = GetTopLevelScopeOwner(currentContent); + } else { + currentTopLevelScopeOwner = currentContent; + } + if (currentTopLevelScopeOwner) { + if (currentTopLevelScopeOwner == oldTopLevelScopeOwner) { + // We're within non-document scope, continue. + do { + if (aForward) { + frameTraversal->Next(); + } else { + frameTraversal->Prev(); + } + frame = static_cast<nsIFrame*>(frameTraversal->CurrentItem()); + // For the usage of GetPrevContinuation, see the comment + // at the end of while (frame) loop. + } while (frame && frame->GetPrevContinuation()); + continue; + } + currentContent = currentTopLevelScopeOwner; + } + } // For document navigation, check if this element is an open panel. Since // panels aren't focusable (tabIndex would be -1), we'll just assume that @@ -3086,7 +3555,7 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, // and root content, so that we only find content within the panel. // Note also that we pass false for aForDocumentNavigation since we // want to locate the first content, not the first document. - rv = GetNextTabbableContent(aPresShell, currentContent, + nsresult rv = GetNextTabbableContent(aPresShell, currentContent, nullptr, currentContent, true, 1, false, false, aResultContent); @@ -3097,6 +3566,34 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, } } + // As of now, 2018/04/12, sequential focus navigation is still + // in the obsolete Shadow DOM specification. + // http://w3c.github.io/webcomponents/spec/shadow/#sequential-focus-navigation + // "if ELEMENT is focusable, a shadow host, or a slot element, + // append ELEMENT to NAVIGATION-ORDER." + // and later in "For each element ELEMENT in NAVIGATION-ORDER: " + // hosts and slots are handled before other elements. + if (nsDocument::IsWebComponentsEnabled(currentContent) && + IsHostOrSlot(currentContent)) { + bool focusableHostSlot; + int32_t tabIndex = + HostOrSlotTabIndexValue(currentContent, &focusableHostSlot); + // Host or slot itself isn't focusable or going backwards, enter its scope. + if ((!aForward || !focusableHostSlot) && tabIndex >= 0 && + (aIgnoreTabIndex || aCurrentTabIndex == tabIndex)) { + nsIContent* contentToFocus = + GetNextTabbableContentInScope(currentContent, currentContent, + aOriginalStartContent, aForward, + aForward ? 1 : 0, aIgnoreTabIndex, + aForDocumentNavigation, + true /* aSkipOwner */); + if (contentToFocus) { + NS_ADDREF(*aResultContent = contentToFocus); + return NS_OK; + } + } + } + // TabIndex not set defaults to 0 for form elements, anchors and other // elements that are normally focusable. Tabindex defaults to -1 // for elements that are not normally focusable. @@ -3145,54 +3642,22 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, // Next, for document navigation, check if this a non-remote child document. bool checkSubDocument = true; - if (aForDocumentNavigation) { - nsIContent* docRoot = GetRootForChildDocument(currentContent); - if (docRoot) { - // If GetRootForChildDocument returned something then call - // FocusFirst to find the root or first element to focus within - // the child document. If this is a frameset though, skip this and - // fall through to the checkSubDocument block below to iterate into - // the frameset's frames and locate the first focusable frame. - if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) { - return FocusFirst(docRoot, aResultContent); - } - } else { - // Set checkSubDocument to false, as this was neither a frame - // type element or a child document that was focusable. - checkSubDocument = false; - } + if (aForDocumentNavigation && + TryDocumentNavigation(currentContent, &checkSubDocument, + aResultContent)) { + return NS_OK; } if (checkSubDocument) { // found a node with a matching tab index. Check if it is a child // frame. If so, navigate into the child frame instead. - nsIDocument* doc = currentContent->GetComposedDoc(); - NS_ASSERTION(doc, "content not in document"); - nsIDocument* subdoc = doc->GetSubDocumentFor(currentContent); - if (subdoc && !subdoc->EventHandlingSuppressed()) { - if (aForward) { - // when tabbing forward into a frame, return the root - // frame so that the canvas becomes focused. - nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow(); - if (subframe) { - *aResultContent = GetRootForFocus(subframe, subdoc, false, true); - if (*aResultContent) { - NS_ADDREF(*aResultContent); - return NS_OK; - } - } - } - Element* rootElement = subdoc->GetRootElement(); - nsIPresShell* subShell = subdoc->GetShell(); - if (rootElement && subShell) { - rv = GetNextTabbableContent(subShell, rootElement, - aOriginalStartContent, rootElement, - aForward, (aForward ? 1 : 0), - false, aForDocumentNavigation, aResultContent); - NS_ENSURE_SUCCESS(rv, rv); - if (*aResultContent) - return NS_OK; - } + if (TryToMoveFocusToSubDocument(currentContent, + aOriginalStartContent, + aForward, + aForDocumentNavigation, + aResultContent)) { + MOZ_ASSERT(*aResultContent); + return NS_OK; } // otherwise, use this as the next content node to tab to, unless // this was the element we started on. This would happen for @@ -3263,6 +3728,77 @@ nsFocusManager::GetNextTabbableContent(nsIPresShell* aPresShell, return NS_OK; } +bool +nsFocusManager::TryDocumentNavigation(nsIContent* aCurrentContent, + bool* aCheckSubDocument, + nsIContent** aResultContent) +{ + *aCheckSubDocument = true; + nsIContent* docRoot = GetRootForChildDocument(aCurrentContent); + if (docRoot) { + // If GetRootForChildDocument returned something then call + // FocusFirst to find the root or first element to focus within + // the child document. If this is a frameset though, skip this and + // fall through to normal tab navigation to iterate into + // the frameset's frames and locate the first focusable frame. + if (!docRoot->IsHTMLElement(nsGkAtoms::frameset)) { + *aCheckSubDocument = false; + Unused << FocusFirst(docRoot, aResultContent); + return *aResultContent != nullptr; + } + } else { + // Set aCheckSubDocument to false, as this was neither a frame + // type element or a child document that was focusable. + *aCheckSubDocument = false; + } + + return false; +} + +bool +nsFocusManager::TryToMoveFocusToSubDocument(nsIContent* aCurrentContent, + nsIContent* aOriginalStartContent, + bool aForward, + bool aForDocumentNavigation, + nsIContent** aResultContent) +{ + nsIDocument* doc = aCurrentContent->GetComposedDoc(); + NS_ASSERTION(doc, "content not in document"); + nsIDocument* subdoc = doc->GetSubDocumentFor(aCurrentContent); + if (subdoc && !subdoc->EventHandlingSuppressed()) { + if (aForward) { + // when tabbing forward into a frame, return the root + // frame so that the canvas becomes focused. + nsCOMPtr<nsPIDOMWindowOuter> subframe = subdoc->GetWindow(); + if (subframe) { + *aResultContent = GetRootForFocus(subframe, subdoc, false, true); + if (*aResultContent) { + NS_ADDREF(*aResultContent); + return true; + } + } + } + Element* rootElement = subdoc->GetRootElement(); + nsIPresShell* subShell = subdoc->GetShell(); + if (rootElement && subShell) { + nsresult rv = GetNextTabbableContent(subShell, + rootElement, + aOriginalStartContent, + rootElement, + aForward, + (aForward ? 1 : 0), + false, + aForDocumentNavigation, + aResultContent); + NS_ENSURE_SUCCESS(rv, false); + if (*aResultContent) { + return true; + } + } + } + return false; +} + nsIContent* nsFocusManager::GetNextTabbableMapArea(bool aForward, int32_t aCurrentTabIndex, @@ -3309,15 +3845,20 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent, bool aForward) { int32_t tabIndex, childTabIndex; + StyleChildrenIterator iter(aParent); if (aForward) { tabIndex = 0; - for (nsIContent* child = aParent->GetFirstChild(); + for (nsIContent* child = iter.GetNextChild(); child; - child = child->GetNextSibling()) { - childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); - if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) { - tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex; + child = iter.GetNextChild()) { + // Skip child's descendants if child is a shadow host, as they are + // in the focus navigation scope owned by child's shadow root + if (!(nsDocument::IsWebComponentsEnabled(aParent) && IsHostOrSlot(child))) { + childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); + if (childTabIndex > aCurrentTabIndex && childTabIndex != tabIndex) { + tabIndex = (tabIndex == 0 || childTabIndex < tabIndex) ? childTabIndex : tabIndex; + } } nsAutoString tabIndexStr; @@ -3331,13 +3872,17 @@ nsFocusManager::GetNextTabIndex(nsIContent* aParent, } else { /* !aForward */ tabIndex = 1; - for (nsIContent* child = aParent->GetFirstChild(); + for (nsIContent* child = iter.GetNextChild(); child; - child = child->GetNextSibling()) { - childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); - if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) || - (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) { - tabIndex = childTabIndex; + child = iter.GetNextChild()) { + // Skip child's descendants if child is a shadow host, as they are + // in the focus navigation scope owned by child's shadow root + if (!(nsDocument::IsWebComponentsEnabled(aParent) && IsHostOrSlot(child))) { + childTabIndex = GetNextTabIndex(child, aCurrentTabIndex, aForward); + if ((aCurrentTabIndex == 0 && childTabIndex > tabIndex) || + (childTabIndex < aCurrentTabIndex && childTabIndex > tabIndex)) { + tabIndex = childTabIndex; + } } nsAutoString tabIndexStr; diff --git a/dom/base/nsFocusManager.h b/dom/base/nsFocusManager.h index 197ecc5153..c98b092468 100644 --- a/dom/base/nsFocusManager.h +++ b/dom/base/nsFocusManager.h @@ -385,6 +385,91 @@ protected: nsIContent** aNextContent); /** + * Returns scope owner of aContent. + * A scope owner is either a document root, shadow host, or slot. + */ + nsIContent* FindOwner(nsIContent* aContent); + + /** + * Retrieve the next tabbable element in scope owned by aOwner, using + * focusability and tabindex to determine the tab order. + * + * aOwner is the owner of scope to search in. + * + * aStartContent is the starting point for this call of this method. + * + * aOriginalStartContent is the initial starting point for sequential + * navigation. + * + * aForward should be true for forward navigation or false for backward + * navigation. + * + * aCurrentTabIndex is the current tabindex. + * + * aIgnoreTabIndex to ignore the current tabindex and find the element + * irrespective or the tab index. + * + * aForDocumentNavigation informs whether we're navigating only through + * documents. + * + * aSkipOwner to skip owner while searching. The flag is set when caller is + * |GetNextTabbableContent| in order to let caller handle owner. + * + * NOTE: + * Consider the method searches downwards in flattened subtree + * rooted at aOwner. + */ + nsIContent* GetNextTabbableContentInScope(nsIContent* aOwner, + nsIContent* aStartContent, + nsIContent* aOriginalStartContent, + bool aForward, + int32_t aCurrentTabIndex, + bool aIgnoreTabIndex, + bool aForDocumentNavigation, + bool aSkipOwner); + + /** + * Retrieve the next tabbable element in scope including aStartContent + * and the scope's ancestor scopes, using focusability and tabindex to + * determine the tab order. + * + * aStartOwner is the scope owner of the aStartContent. + * + * aStartContent an in/out paremeter. It as input is the starting point + * for this call of this method; as output it is the shadow host in + * light DOM if the next tabbable element is not found in shadow DOM, + * in order to continue searching in light DOM. + * + * aOriginalStartContent is the initial starting point for sequential + * navigation. + * + * aForward should be true for forward navigation or false for backward + * navigation. + * + * aCurrentTabIndex returns tab index of shadow host in light DOM if the + * next tabbable element is not found in shadow DOM, in order to continue + * searching in light DOM. + * + * aIgnoreTabIndex to ignore the current tabindex and find the element + * irrespective or the tab index. + * + * aForDocumentNavigation informs whether we're navigating only through + * documents. + * + * NOTE: + * Consider the method searches upwards in all shadow host- or slot-rooted + * flattened subtrees that contains aStartContent as non-root, except + * the flattened subtree rooted at shadow host in light DOM. + */ + nsIContent* GetNextTabbableContentInAncestorScopes(nsIContent* aStartOwner, + nsIContent** aStartContent, + nsIContent* aOriginalStartContent, + bool aForward, + int32_t* aCurrentTabIndex, + bool aIgnoreTabIndex, + bool aForDocumentNavigation); + + /** * Retrieve the next tabbable element within a document, using focusability * and tabindex to determine the tab order. The element is returned in * aResultContent. @@ -503,6 +588,16 @@ private: void SetFocusedWindowInternal(nsPIDOMWindowOuter* aWindow); + bool TryDocumentNavigation(nsIContent* aCurrentContent, + bool* aCheckSubDocument, + nsIContent** aResultContent); + + bool TryToMoveFocusToSubDocument(nsIContent* aCurrentContent, + nsIContent* aOriginalStartContent, + bool aForward, + bool aForDocumentNavigation, + nsIContent** aResultContent); + // the currently active and front-most top-most window nsCOMPtr<nsPIDOMWindowOuter> mActiveWindow; diff --git a/dom/base/nsIDocument.h b/dom/base/nsIDocument.h index f6645aaefc..d64827aa6d 100644 --- a/dom/base/nsIDocument.h +++ b/dom/base/nsIDocument.h @@ -1811,26 +1811,6 @@ public: nsIAtom* aAttrName, const nsAString& aAttrValue) const = 0; - /** - * Helper for nsIDOMDocument::elementFromPoint implementation that allows - * ignoring the scroll frame and/or avoiding layout flushes. - * - * @see nsIDOMWindowUtils::elementFromPoint - */ - virtual Element* ElementFromPointHelper(float aX, float aY, - bool aIgnoreRootScrollFrame, - bool aFlushLayout) = 0; - - enum ElementsFromPointFlags { - IGNORE_ROOT_SCROLL_FRAME = 1, - FLUSH_LAYOUT = 2, - IS_ELEMENT_FROM_POINT = 4 - }; - - virtual void ElementsFromPointHelper(float aX, float aY, - uint32_t aFlags, - nsTArray<RefPtr<mozilla::dom::Element>>& aElements) = 0; - virtual nsresult NodesFromRectHelper(float aX, float aY, float aTopSize, float aRightSize, float aBottomSize, float aLeftSize, @@ -2637,17 +2617,17 @@ public: nsIURI* GetDocumentURIObject() const; // Not const because all the full-screen goop is not const virtual bool FullscreenEnabled() = 0; - virtual Element* GetFullscreenElement() = 0; + virtual Element* FullScreenStackTop() = 0; bool Fullscreen() { return !!GetFullscreenElement(); } void ExitFullscreen(); - Element* GetPointerLockElement(); void ExitPointerLock() { UnlockPointer(this); } + static bool IsUnprefixedFullscreenEnabled(JSContext* aCx, JSObject* aObject); #ifdef MOZILLA_INTERNAL_API bool Hidden() const { @@ -2664,10 +2644,6 @@ public: void GetPreferredStyleSheetSet(nsAString& aSheetSet); virtual mozilla::dom::DOMStringList* StyleSheetSets() = 0; virtual void EnableStyleSheetsForSet(const nsAString& aSheetSet) = 0; - Element* ElementFromPoint(float aX, float aY); - void ElementsFromPoint(float aX, - float aY, - nsTArray<RefPtr<mozilla::dom::Element>>& aElements); /** * Retrieve the location of the caret position (DOM node and character @@ -2835,6 +2811,16 @@ public: bool ModuleScriptsEnabled(); + /** + * Find the (non-anonymous) content in this document for aFrame. It will + * be aFrame's content node if that content is in this document and not + * anonymous. Otherwise, when aFrame is in a subdocument, we use the frame + * element containing the subdocument containing aFrame, and/or find the + * nearest non-anonymous ancestor in this document. + * Returns null if there is no such element. + */ + nsIContent* GetContentInThisDocument(nsIFrame* aFrame) const; + virtual void AddResizeObserver(mozilla::dom::ResizeObserver* aResizeObserver) = 0; virtual void ScheduleResizeObserversNotification() const = 0; diff --git a/dom/base/nsINode.cpp b/dom/base/nsINode.cpp index 749a3ee4a2..735dfb16e5 100644 --- a/dom/base/nsINode.cpp +++ b/dom/base/nsINode.cpp @@ -273,6 +273,18 @@ nsINode* nsINode::GetRootNode(const GetRootNodeOptions& aOptions) return SubtreeRoot(); } +// TODO: was marked as constant. +nsINode* +nsINode::GetParentOrHostNode() +{ + if (mParent) { + return mParent; + } + + ShadowRoot* shadowRoot = ShadowRoot::FromNode(this); + return shadowRoot ? shadowRoot->GetHost() : nullptr; +} + nsINode* nsINode::SubtreeRoot() const { @@ -1543,32 +1555,25 @@ nsINode::SetExplicitBaseURI(nsIURI* aURI) return rv; } -static nsresult -AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode) +static void +AdoptNodeIntoOwnerDoc(nsINode *aParent, nsINode *aNode, ErrorResult& aError) { NS_ASSERTION(!aNode->GetParentNode(), "Should have removed from parent already"); nsIDocument *doc = aParent->OwnerDoc(); - nsresult rv; - nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aNode, &rv); - NS_ENSURE_SUCCESS(rv, rv); - - nsCOMPtr<nsIDOMNode> adoptedNode; - rv = domDoc->AdoptNode(node, getter_AddRefs(adoptedNode)); - NS_ENSURE_SUCCESS(rv, rv); + DebugOnly<nsINode*> adoptedNode = doc->AdoptNode(*aNode, aError); - NS_ASSERTION(aParent->OwnerDoc() == doc, - "ownerDoc chainged while adopting"); - NS_ASSERTION(adoptedNode == node, "Uh, adopt node changed nodes?"); - NS_ASSERTION(aParent->OwnerDoc() == aNode->OwnerDoc(), +#ifdef DEBUG + if (!aError.Failed()) { + MOZ_ASSERT(aParent->OwnerDoc() == doc, + "ownerDoc changed while adopting"); + MOZ_ASSERT(adoptedNode == aNode, "Uh, adopt node changed nodes?"); + MOZ_ASSERT(aParent->OwnerDoc() == aNode->OwnerDoc(), "ownerDocument changed again after adopting!"); - - return NS_OK; + } +#endif // DEBUG } static nsresult @@ -1593,19 +1598,20 @@ ReparentWrappersInSubtree(nsIContent* aRoot) rootedGlobal = xpc::GetXBLScope(cx, rootedGlobal); - nsresult rv; + ErrorResult rv; JS::Rooted<JSObject*> reflector(cx); for (nsIContent* cur = aRoot; cur; cur = cur->GetNextNode(aRoot)) { if ((reflector = cur->GetWrapper())) { JSAutoCompartment ac(cx, reflector); - rv = ReparentWrapper(cx, reflector); - if NS_FAILED(rv) { + ReparentWrapper(cx, reflector, rv); + rv.WouldReportJSException(); + if (rv.Failed()) { // We _could_ consider BlastSubtreeToPieces here, but it's not really // needed. Having some nodes in here accessible to content while others // are not is probably OK. We just need to fail out of the actual // insertion, so they're not in the DOM. Returning a failure here will // do that. - return rv; + return rv.StealNSResult(); } } } @@ -1619,7 +1625,6 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex, { NS_PRECONDITION(!aKid->GetParentNode(), "Inserting node that already has parent"); - nsresult rv; // The id-handling code, and in the future possibly other code, need to // react to unexpected attribute changes. @@ -1630,15 +1635,23 @@ nsINode::doInsertChildAt(nsIContent* aKid, uint32_t aIndex, mozAutoDocUpdate updateBatch(GetComposedDoc(), UPDATE_CONTENT_MODEL, aNotify); if (OwnerDoc() != aKid->OwnerDoc()) { - rv = AdoptNodeIntoOwnerDoc(this, aKid); - NS_ENSURE_SUCCESS(rv, rv); + ErrorResult error; + AdoptNodeIntoOwnerDoc(this, aKid, error); + + // Need to WouldReportJSException() if our callee can throw a JS + // exception (which it can) and we're neither propagating the + // error out nor unconditionally suppressing it. + error.WouldReportJSException(); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } } uint32_t childCount = aChildArray.ChildCount(); NS_ENSURE_TRUE(aIndex <= childCount, NS_ERROR_ILLEGAL_VALUE); bool isAppend = (aIndex == childCount); - rv = aChildArray.InsertChildAt(aKid, aIndex); + nsresult rv = aChildArray.InsertChildAt(aKid, aIndex); NS_ENSURE_SUCCESS(rv, rv); if (aIndex == 0) { mFirstChild = aKid; @@ -2475,7 +2488,7 @@ nsINode::ReplaceOrInsertBefore(bool aReplace, nsINode* aNewChild, // inserting them w/o calling AdoptNode(). nsIDocument* doc = OwnerDoc(); if (doc != newContent->OwnerDoc()) { - aError = AdoptNodeIntoOwnerDoc(this, aNewChild); + AdoptNodeIntoOwnerDoc(this, aNewChild, aError); if (aError.Failed()) { return nullptr; } @@ -3072,9 +3085,7 @@ nsINode::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) already_AddRefed<nsINode> nsINode::CloneNode(bool aDeep, ErrorResult& aError) { - nsCOMPtr<nsINode> result; - aError = nsNodeUtils::CloneNodeImpl(this, aDeep, getter_AddRefs(result)); - return result.forget(); + return nsNodeUtils::CloneNodeImpl(this, aDeep, aError); } nsDOMAttributeMap* diff --git a/dom/base/nsINode.h b/dom/base/nsINode.h index 1d580540f8..3611a4074d 100644 --- a/dom/base/nsINode.h +++ b/dom/base/nsINode.h @@ -943,6 +943,12 @@ public: } /** + * This is similar to above, but in case 'this' is ShadowRoot, we return its + * host element. + */ + nsINode* GetParentOrHostNode(); + + /** * Returns the node that is the parent of this node in the flattened * tree. This differs from the normal parent if the node is filtered * into an insertion point, or if the node is a direct child of a diff --git a/dom/base/nsImageLoadingContent.cpp b/dom/base/nsImageLoadingContent.cpp index 091ee65263..5e5e4666c4 100644 --- a/dom/base/nsImageLoadingContent.cpp +++ b/dom/base/nsImageLoadingContent.cpp @@ -1412,13 +1412,11 @@ nsImageLoadingContent::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { - // We may be entering the document, so if our image should be tracked, - // track it. - if (!aDocument) - return; - - TrackImage(mCurrentRequest); - TrackImage(mPendingRequest); + // We may be getting connected and our image should be tracked. + if (GetOurCurrentDoc()) { + TrackImage(mCurrentRequest); + TrackImage(mPendingRequest); + } if (mCurrentRequestFlags & REQUEST_BLOCKS_ONLOAD) aDocument->BlockOnload(); diff --git a/dom/base/nsNodeUtils.cpp b/dom/base/nsNodeUtils.cpp index cd60eb2806..bb6025c521 100644 --- a/dom/base/nsNodeUtils.cpp +++ b/dom/base/nsNodeUtils.cpp @@ -402,28 +402,20 @@ nsNodeUtils::TraverseUserData(nsINode* aNode, } /* static */ -nsresult -nsNodeUtils::CloneNodeImpl(nsINode *aNode, bool aDeep, nsINode **aResult) +already_AddRefed<nsINode> +nsNodeUtils::CloneNodeImpl(nsINode *aNode, bool aDeep, ErrorResult& aError) { - *aResult = nullptr; - - nsCOMPtr<nsINode> newNode; nsCOMArray<nsINode> nodesWithProperties; - nsresult rv = Clone(aNode, aDeep, nullptr, nodesWithProperties, - getter_AddRefs(newNode)); - NS_ENSURE_SUCCESS(rv, rv); - - newNode.forget(aResult); - return NS_OK; + return Clone(aNode, aDeep, nullptr, &nodesWithProperties, aError); } /* static */ -nsresult +already_AddRefed<nsINode> nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, nsNodeInfoManager *aNewNodeInfoManager, JS::Handle<JSObject*> aReparentScope, - nsCOMArray<nsINode> &aNodesWithProperties, - nsINode *aParent, nsINode **aResult) + nsCOMArray<nsINode> *aNodesWithProperties, + nsINode *aParent, ErrorResult& aError) { NS_PRECONDITION((!aClone && aNewNodeInfoManager) || !aReparentScope, "If cloning or not getting a new nodeinfo we shouldn't " @@ -431,14 +423,11 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, NS_PRECONDITION(!aParent || aNode->IsNodeOfType(nsINode::eCONTENT), "Can't insert document or attribute nodes into a parent"); - *aResult = nullptr; - // First deal with aNode and walk its attributes (and their children). Then, // if aDeep is true, deal with aNode's children (and recurse into their // attributes and children). nsAutoScriptBlocker scriptBlocker; - nsresult rv; nsNodeInfoManager *nodeInfoManager = aNewNodeInfoManager; @@ -450,14 +439,20 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, // Don't allow importing/adopting nodes from non-privileged "scriptable" // documents to "non-scriptable" documents. nsIDocument* newDoc = nodeInfoManager->GetDocument(); - NS_ENSURE_STATE(newDoc); + if (NS_WARN_IF(!newDoc)) { + aError.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } bool hasHadScriptHandlingObject = false; if (!newDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) && !hasHadScriptHandlingObject) { nsIDocument* currentDoc = aNode->OwnerDoc(); - NS_ENSURE_STATE((nsContentUtils::IsChromeDoc(currentDoc) || - (!currentDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) && - !hasHadScriptHandlingObject))); + if (NS_WARN_IF(!nsContentUtils::IsChromeDoc(currentDoc) && + (currentDoc->GetScriptHandlingObject(hasHadScriptHandlingObject) || + hasHadScriptHandlingObject))) { + aError.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } } newNodeInfo = nodeInfoManager->GetNodeInfo(nodeInfo->NameAtom(), @@ -473,8 +468,11 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, nsCOMPtr<nsINode> clone; if (aClone) { - rv = aNode->Clone(nodeInfo, getter_AddRefs(clone)); - NS_ENSURE_SUCCESS(rv, rv); + nsresult rv = aNode->Clone(nodeInfo, getter_AddRefs(clone)); + if (NS_WARN_IF(NS_FAILED(rv))) { + aError.Throw(rv); + return nullptr; + } if (CustomElementRegistry::IsCustomElementEnabled() && clone->IsHTMLElement()) { @@ -512,7 +510,10 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, // parent. rv = aParent->AppendChildTo(static_cast<nsIContent*>(clone.get()), false); - NS_ENSURE_SUCCESS(rv, rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + aError.Throw(rv); + return nullptr; + } } else if (aDeep && clone->IsNodeOfType(nsINode::eDOCUMENT)) { // After cloning the document itself, we want to clone the children into @@ -611,8 +612,8 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, if ((wrapper = aNode->GetWrapper())) { MOZ_ASSERT(IsDOMObject(wrapper)); JSAutoCompartment ac(cx, wrapper); - rv = ReparentWrapper(cx, wrapper); - if (NS_FAILED(rv)) { + ReparentWrapper(cx, wrapper, aError); + if (aError.Failed()) { if (wasRegistered) { aNode->OwnerDoc()->UnregisterActivityObserver(aNode->AsElement()); } @@ -620,17 +621,17 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, if (wasRegistered) { aNode->OwnerDoc()->RegisterActivityObserver(aNode->AsElement()); } - return rv; + return nullptr; } } } } if (aNode->HasProperties()) { - bool ok = aNodesWithProperties.AppendObject(aNode); + bool ok = aNodesWithProperties->AppendObject(aNode); MOZ_RELEASE_ASSERT(ok, "Out of memory"); if (aClone) { - ok = aNodesWithProperties.AppendObject(clone); + ok = aNodesWithProperties->AppendObject(clone); MOZ_RELEASE_ASSERT(ok, "Out of memory"); } } @@ -640,11 +641,47 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, for (nsIContent* cloneChild = aNode->GetFirstChild(); cloneChild; cloneChild = cloneChild->GetNextSibling()) { - nsCOMPtr<nsINode> child; - rv = CloneAndAdopt(cloneChild, aClone, true, nodeInfoManager, - aReparentScope, aNodesWithProperties, clone, - getter_AddRefs(child)); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsINode> child = + CloneAndAdopt(cloneChild, aClone, true, nodeInfoManager, + aReparentScope, aNodesWithProperties, clone, + aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + } + } + + if (aDeep && aNode->IsElement()) { + ShadowRoot* shadowRoot = aNode->AsElement()->GetShadowRoot(); + if (!shadowRoot) { + // Nothing to do here. + } else if (aClone && clone->OwnerDoc()->IsStaticDocument()) { + ShadowRootInit init; + init.mMode = shadowRoot->Mode(); + RefPtr<ShadowRoot> newShadowRoot = + clone->AsElement()->AttachShadow(init, aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + newShadowRoot->CloneInternalDataFrom(shadowRoot); + for (nsIContent* originalChild = shadowRoot->GetFirstChild(); + originalChild; + originalChild = originalChild->GetNextSibling()) { + nsCOMPtr<nsINode> child = CloneAndAdopt(originalChild, aClone, aDeep, nodeInfoManager, + aReparentScope, aNodesWithProperties, newShadowRoot, + aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + } + } else if (!aClone) { + nsCOMPtr<nsINode> child = CloneAndAdopt(shadowRoot, aClone, aDeep, nodeInfoManager, + aReparentScope, aNodesWithProperties, clone, + aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } } } @@ -663,11 +700,13 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, for (nsIContent* cloneChild = origContent->GetFirstChild(); cloneChild; cloneChild = cloneChild->GetNextSibling()) { - nsCOMPtr<nsINode> child; - rv = CloneAndAdopt(cloneChild, aClone, aDeep, ownerNodeInfoManager, - aReparentScope, aNodesWithProperties, cloneContent, - getter_AddRefs(child)); - NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsINode> child = + CloneAndAdopt(cloneChild, aClone, aDeep, ownerNodeInfoManager, + aReparentScope, aNodesWithProperties, cloneContent, + aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } } } @@ -688,9 +727,7 @@ nsNodeUtils::CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, } } - clone.forget(aResult); - - return NS_OK; + return clone.forget(); } diff --git a/dom/base/nsNodeUtils.h b/dom/base/nsNodeUtils.h index 971f8d17c7..6f6f293d0f 100644 --- a/dom/base/nsNodeUtils.h +++ b/dom/base/nsNodeUtils.h @@ -17,6 +17,7 @@ template<class E> class nsCOMArray; class nsCycleCollectionTraversalCallback; namespace mozilla { struct NonOwningAnimationTarget; +class ErrorResult; namespace dom { class Animation; } // namespace dom @@ -181,25 +182,17 @@ public: * @param aNodesWithProperties All nodes (from amongst aNode and its * descendants) with properties. Every node will * be followed by its clone. - * @param aResult *aResult will contain the cloned node. + * @param aError The error, if any. + * + * @return The newly created node. Null in error conditions. */ - static nsresult Clone(nsINode *aNode, bool aDeep, - nsNodeInfoManager *aNewNodeInfoManager, - nsCOMArray<nsINode> &aNodesWithProperties, - nsINode **aResult) + static already_AddRefed<nsINode> Clone(nsINode *aNode, bool aDeep, + nsNodeInfoManager *aNewNodeInfoManager, + nsCOMArray<nsINode> *aNodesWithProperties, + mozilla::ErrorResult& aError) { return CloneAndAdopt(aNode, true, aDeep, aNewNodeInfoManager, - nullptr, aNodesWithProperties, nullptr, aResult); - } - - /** - * Clones aNode, its attributes and, if aDeep is true, its descendant nodes - */ - static nsresult Clone(nsINode *aNode, bool aDeep, nsINode **aResult) - { - nsCOMArray<nsINode> dummyNodeWithProperties; - return CloneAndAdopt(aNode, true, aDeep, nullptr, nullptr, - dummyNodeWithProperties, aNode->GetParent(), aResult); + nullptr, aNodesWithProperties, nullptr, aError); } /** @@ -218,19 +211,20 @@ public: * should be done. * @param aNodesWithProperties All nodes (from amongst aNode and its * descendants) with properties. + * @param aError The error, if any. */ - static nsresult Adopt(nsINode *aNode, nsNodeInfoManager *aNewNodeInfoManager, - JS::Handle<JSObject*> aReparentScope, - nsCOMArray<nsINode> &aNodesWithProperties) + static void Adopt(nsINode *aNode, nsNodeInfoManager *aNewNodeInfoManager, + JS::Handle<JSObject*> aReparentScope, + nsCOMArray<nsINode>& aNodesWithProperties, + mozilla::ErrorResult& aError) { - nsCOMPtr<nsINode> node; - nsresult rv = CloneAndAdopt(aNode, false, true, aNewNodeInfoManager, - aReparentScope, aNodesWithProperties, - nullptr, getter_AddRefs(node)); + // Just need to store the return value of CloneAndAdopt in a + // temporary nsCOMPtr to make sure we release it. + nsCOMPtr<nsINode> node = CloneAndAdopt(aNode, false, true, aNewNodeInfoManager, + aReparentScope, &aNodesWithProperties, + nullptr, aError); nsMutationGuard::DidMutate(); - - return rv; } /** @@ -248,9 +242,12 @@ public: * * @param aNode the node to clone * @param aDeep if true all descendants will be cloned too - * @param aResult the clone + * @param aError the error, if any. + * + * @return the clone, or null if an error occurs. */ - static nsresult CloneNodeImpl(nsINode *aNode, bool aDeep, nsINode **aResult); + static already_AddRefed<nsINode> CloneNodeImpl(nsINode *aNode, bool aDeep, + mozilla::ErrorResult& aError); /** * Release the UserData for aNode. @@ -286,7 +283,7 @@ private: * * @param aNode Node to adopt/clone. * @param aClone If true the node will be cloned and the cloned node will - * be in aResult. + * be returned. * @param aDeep If true the function will be called recursively on * descendants of the node * @param aNewNodeInfoManager The nodeinfo manager to use to create new @@ -302,14 +299,18 @@ private: * @param aParent If aClone is true the cloned node will be appended to * aParent's children. May be null. If not null then aNode * must be an nsIContent. - * @param aResult If aClone is true then *aResult will contain the cloned - * node. + * @param aError The error, if any. + * + * @return If aClone is true then the cloned node will be returned, + * unless an error occurred. In error conditions, null + * will be returned. */ - static nsresult CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, - nsNodeInfoManager *aNewNodeInfoManager, - JS::Handle<JSObject*> aReparentScope, - nsCOMArray<nsINode> &aNodesWithProperties, - nsINode *aParent, nsINode **aResult); + static already_AddRefed<nsINode> + CloneAndAdopt(nsINode *aNode, bool aClone, bool aDeep, + nsNodeInfoManager* aNewNodeInfoManager, + JS::Handle<JSObject*> aReparentScope, + nsCOMArray<nsINode> *aNodesWithProperties, + nsINode *aParent, mozilla::ErrorResult& aError); enum class AnimationMutationType { diff --git a/dom/base/nsStyleLinkElement.cpp b/dom/base/nsStyleLinkElement.cpp index 21a4f694a6..7ea7fce40c 100644 --- a/dom/base/nsStyleLinkElement.cpp +++ b/dom/base/nsStyleLinkElement.cpp @@ -304,14 +304,6 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, return NS_OK; } - // Check for a ShadowRoot because link elements are inert in a - // ShadowRoot. - ShadowRoot* containingShadow = thisContent->GetContainingShadow(); - if (thisContent->IsHTMLElement(nsGkAtoms::link) && - (aOldShadowRoot || containingShadow)) { - return NS_OK; - } - // XXXheycam ServoStyleSheets do not support <style scoped>. Element* oldScopeElement = nullptr; if (mStyleSheet) { @@ -346,15 +338,16 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, } } - // When static documents are created, stylesheets are cloned manually. - if (mDontLoadStyle || !mUpdatesEnabled || - thisContent->OwnerDoc()->IsStaticDocument()) { + nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ? + thisContent->OwnerDoc() : thisContent->GetUncomposedDoc(); + + // Loader could be null during unlink, see bug 1425866. + if (!doc || !doc->CSSLoader() || !doc->CSSLoader()->GetEnabled()) { return NS_OK; } - nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ? - thisContent->OwnerDoc() : thisContent->GetUncomposedDoc(); - if (!doc || !doc->CSSLoader()->GetEnabled()) { + // When static documents are created, stylesheets are cloned manually. + if (mDontLoadStyle || !mUpdatesEnabled || doc->IsStaticDocument()) { return NS_OK; } @@ -426,8 +419,7 @@ nsStyleLinkElement::DoUpdateStyleSheet(nsIDocument* aOldDocument, rv = doc->CSSLoader()-> LoadInlineStyle(thisContent, text, mLineNumber, title, media, scopeElement, aObserver, &doneLoading, &isAlternate, &isExplicitlyEnabled); - } - else { + } else { nsAutoString integrity; thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity); if (!integrity.IsEmpty()) { diff --git a/dom/base/test/file_bug1453693.html b/dom/base/test/file_bug1453693.html new file mode 100644 index 0000000000..1a9a31dc1e --- /dev/null +++ b/dom/base/test/file_bug1453693.html @@ -0,0 +1,903 @@ +<html> + <head> + <title>Test for Bug 1453693</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <script> + + class TestNode extends HTMLElement { + constructor() { + super(); + const styles = "<style>:focus{background-color:yellow;}</style>"; + this.attachShadow({ mode: 'open' }); + this.shadowRoot.innerHTML = + `${styles}<div tabindex='-1'>test node</div> <slot></slot>`; + }} + + window.customElements.define('test-node', TestNode); + + var lastFocusTarget; + function focusLogger(event) { + lastFocusTarget = event.target; + console.log(event.target + " under " + event.target.parentNode); + event.stopPropagation(); + } + + function testTabbingThroughShadowDOMWithTabIndexes() { + var anchor = document.createElement("a"); + anchor.onfocus = focusLogger; + anchor.href = "#"; + anchor.textContent = "in light DOM"; + document.body.appendChild(anchor); + + var host = document.createElement("div"); + document.body.appendChild(host); + + var sr = host.attachShadow({mode: "open"}); + var shadowAnchor = anchor.cloneNode(false); + shadowAnchor.onfocus = focusLogger; + shadowAnchor.textContent = "in shadow DOM"; + sr.appendChild(shadowAnchor); + var shadowInput = document.createElement("input"); + shadowInput.onfocus = focusLogger; + shadowInput.tabIndex = 1; + sr.appendChild(shadowInput); + + var shadowDate = document.createElement("input"); + shadowDate.type = "date"; + shadowDate.onfocus = focusLogger; + shadowDate.tabIndex = 1; + sr.appendChild(shadowDate); + + var shadowIframe = document.createElement("iframe"); + shadowIframe.tabIndex = 1; + sr.appendChild(shadowIframe); + shadowIframe.contentDocument.body.innerHTML = "<input>"; + + var input = document.createElement("input"); + input.onfocus = focusLogger; + input.tabIndex = 1; + document.body.appendChild(input); + + var input2 = document.createElement("input"); + input2.onfocus = focusLogger; + document.body.appendChild(input2); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input, "Should have focused input element. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.documentElement, + "Should have focused document element in shadow iframe. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.body.firstChild, + "Should have focused input element in shadow iframe. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (3)"); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input2, "Should have focused input[2] element. (3)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.body.firstChild, + "Should have focused input element in shadow iframe. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(shadowIframe.contentDocument.activeElement, + shadowIframe.contentDocument.documentElement, + "Should have focused document element in shadow iframe. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowDate, "Should have focused date element in shadow DOM. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (4)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); + + document.body.innerHTML = null; + } + + function testTabbingThroughSimpleShadowDOM() { + var anchor = document.createElement("a"); + anchor.onfocus = focusLogger; + anchor.href = "#"; + anchor.textContent = "in light DOM"; + document.body.appendChild(anchor); + anchor.focus(); + + var host = document.createElement("div"); + document.body.appendChild(host); + + var sr = host.attachShadow({mode: "open"}); + var shadowAnchor = anchor.cloneNode(false); + shadowAnchor.onfocus = focusLogger; + shadowAnchor.textContent = "in shadow DOM"; + sr.appendChild(shadowAnchor); + var shadowInput = document.createElement("input"); + shadowInput.onfocus = focusLogger; + sr.appendChild(shadowInput); + + var hiddenShadowButton = document.createElement("button"); + hiddenShadowButton.setAttribute("style", "display: none;"); + sr.appendChild(hiddenShadowButton); + + var input = document.createElement("input"); + input.onfocus = focusLogger; + document.body.appendChild(input); + + var input2 = document.createElement("input"); + input2.onfocus = focusLogger; + document.body.appendChild(input2); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM."); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM."); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input, "Should have focused input element."); + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input2, "Should have focused input[2] element."); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input, "Should have focused input element. (2)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (2)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowAnchor, "Should have focused anchor element in shadow DOM. (2)"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, anchor, "Should have focused anchor element. (2)"); + + host.remove(); + input.remove(); + input2.remove(); + } + + function testTabbingThroughNestedShadowDOM() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); + + var host = document.createElement("div"); + host.id = "host"; + document.body.appendChild(host); + + var sr0 = host.attachShadow({mode: "open"}); + sr0.innerHTML = "<button id='button'>X</button><br id='br'><div id='h1'></div><div id='h2'></div>"; + var button = sr0.getElementById("button"); + button.onfocus = focusLogger; + + var h1 = sr0.getElementById("h1"); + var sr1 = h1.attachShadow({mode: "open"}); + sr1.innerHTML = "h1 <input id='h11' placeholder='click me and press tab'><input id='h12' placeholder='and then tab again'>"; + var input11 = sr1.getElementById("h11"); + input11.onfocus = focusLogger; + var input12 = sr1.getElementById("h12"); + input12.onfocus = focusLogger; + + var h2 = sr0.getElementById("h2"); + var sr2 = h2.attachShadow({mode: "open"}); + sr2.innerHTML = "h2 <input id='h21'><input id='h22'>"; + var input21 = sr2.getElementById("h21"); + input21.onfocus = focusLogger; + var input22 = sr2.getElementById("h22"); + input22.onfocus = focusLogger; + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input22, "[nested shadow] Should have focused input element. (4)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input21, "[nested shadow] Should have focused input element. (5)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input12, "[nested shadow] Should have focused input element. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input11, "[nested shadow] Should have focused input element. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, button, "[nested shadow] Should have focused button element. (8)"); + + // Back to beginning, outside of Shadow DOM. + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); + + host.remove(); + } + + function testTabbingThroughDisplayContentsHost() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (1)"); + + var host = document.createElement("div"); + host.id = "host"; + host.setAttribute("style", "display: contents; border: 1px solid black;"); + document.body.appendChild(host); + + var sr0 = host.attachShadow({mode: "open"}); + sr0.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; + var shadowInput1 = sr0.getElementById("shadowInput1"); + shadowInput1.onfocus = focusLogger; + var shadowInput2 = sr0.getElementById("shadowInput2"); + shadowInput2.onfocus = focusLogger; + + var host1 = document.createElement("div"); + host1.id = "host"; + host1.tabIndex = 0; + host1.setAttribute("style", "display: contents; border: 1px solid black;"); + document.body.appendChild(host1); + + var sr1 = host1.attachShadow({mode: "open"}); + sr1.innerHTML = "<input id='shadowInput1'><input id='shadowInput2'>"; + var shadowInput3 = sr1.getElementById("shadowInput1"); + shadowInput3.onfocus = focusLogger; + var shadowInput4 = sr1.getElementById("shadowInput2"); + shadowInput4.onfocus = focusLogger; + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput4, "Should have focused input element. (4)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput3, "Should have focused input element. (5)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput2, "Should have focused input element. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput1, "Should have focused input element. (7)"); + + // Back to beginning, outside of Shadow DOM. + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus. (2)"); + + host.remove(); + host1.remove(); + } + + function testTabbingThroughLightDOMShadowDOMLightDOM() { + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + var host = document.createElement("span"); + host.innerHTML = "\n"; + host.id = "host"; + document.body.appendChild(host); + + var sr0 = host.attachShadow({mode: "open"}); + sr0.innerHTML = document.getElementById("template").innerHTML; + var p1 = sr0.getElementById("p1"); + p1.onfocus = focusLogger; + var p2 = sr0.getElementById("p2"); + p2.onfocus = focusLogger; + + var p = document.createElement("p"); + p.innerHTML = " <a href='#p'>link 1</a> "; + var a = p.firstElementChild; + a.onfocus = focusLogger; + document.body.appendChild(p); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, p1, "Should have focused p1."); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, p2, "Should have focused p2."); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, a, "Should have focused a."); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, p2, "Should have focused p2."); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, p1, "Should have focused p1."); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + host.remove(); + p.remove(); + } + + function testFocusableHost() { + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + var host = document.createElement("div"); + host.id = "host"; + host.tabIndex = 0; + host.onfocus = focusLogger; + document.body.appendChild(host); + + var slotted = document.createElement("div"); + slotted.tabIndex = 0; + slotted.onfocus = focusLogger; + host.appendChild(slotted); + + var sr0 = host.attachShadow({mode: "open"}); + sr0.appendChild(document.createElement("slot")); + + var p = document.createElement("p"); + p.innerHTML = " <a href='#p'>link 1</a> "; + var a = p.firstElementChild; + a.onfocus = focusLogger; + document.body.appendChild(p); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, host, "Should have focused host."); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, slotted, "Should have focused slotted."); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, a, "Should have focused a."); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, slotted, "Should have focused slotted."); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, host, "Should have focused host."); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + host.remove(); + p.remove(); + } + + function testShiftTabbingThroughFocusableHost() { + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + var host = document.createElement("div"); + host.id = "host"; + host.tabIndex = 0; + host.onfocus = focusLogger; + document.body.appendChild(host); + + var sr = host.attachShadow({mode: "open"}); + var shadowButton = document.createElement("button"); + shadowButton.innerText = "X"; + shadowButton.onfocus = focusLogger; + sr.appendChild(shadowButton); + + var shadowInput = document.createElement("input"); + shadowInput.onfocus = focusLogger; + sr.appendChild(shadowInput); + sr.appendChild(document.createElement("br")); + + var input = document.createElement("input"); + input.onfocus = focusLogger; + document.body.appendChild(input); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, host, "Should have focused host element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input, "Should have focused input element. (4)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowInput, "Should have focused input element in shadow DOM. (5)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, shadowButton, "Should have focused button element in shadow DOM. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + // focus is already on host + opener.is(sr.activeElement, null, + "Focus should have left button element in shadow DOM. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + host.remove(); + input.remove(); + } + + function testTabbingThroughNestedSlot() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + + var host0 = document.createElement("div"); + var sr0 = host0.attachShadow({mode: "open"}); + sr0.innerHTML = "<slot></slot>"; + document.body.appendChild(host0); + + // focusable + var host00 = document.createElement("div"); + var sr00 = host00.attachShadow({mode: "open"}); + var div00 = document.createElement("div"); + div00.tabIndex = 0; + div00.onfocus = focusLogger; + sr00.appendChild(div00); + host0.appendChild(host00); + + // not focusable + var host01 = document.createElement("div"); + var sr01 = host01.attachShadow({mode: "open"}); + sr01.innerHTML = "<div></div>"; + host0.appendChild(host01); + + // focusable + var host02 = document.createElement("div"); + var sr02 = host02.attachShadow({mode: "open"}); + var div02 = document.createElement("div"); + div02.tabIndex = 0; + div02.onfocus = focusLogger; + sr02.appendChild(div02); + host0.appendChild(host02); + + var host1 = document.createElement("div"); + var sr1 = host1.attachShadow({mode: "open"}); + sr1.innerHTML = "<slot></slot>"; + document.body.appendChild(host1); + + var host10 = document.createElement("div"); + var sr10 = host10.attachShadow({mode: "open"}); + sr10.innerHTML = "<slot></slot>"; + host1.appendChild(host10); + + var input10 = document.createElement("input"); + input10.onfocus = focusLogger; + host10.appendChild(input10); + + var host11 = document.createElement("div"); + var sr11 = host11.attachShadow({mode: "open"}); + sr11.innerHTML = "<slot></slot>"; + host1.appendChild(host11); + + var input11 = document.createElement("input"); + input11.onfocus = focusLogger; + host11.appendChild(input11); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, div00, "Should have focused div element in shadow DOM. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, div02, "Should have focused div element in shadow DOM. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input11, "Should have focused button element in shadow DOM. (4)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input10, "Should have focused input element in shadow DOM. (5)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, div02, "Should have focused input element in shadow DOM. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, div00, "Should have focused input element in shadow DOM. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + host0.remove(); + host1.remove(); + } + + function testTabbingThroughSlotInLightDOM() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + + var input0 = document.createElement("input"); + input0.onfocus = focusLogger; + document.body.appendChild(input0); + + var slot1 = document.createElement("slot"); + document.body.appendChild(slot1); + + var input10 = document.createElement("input"); + input10.onfocus = focusLogger; + slot1.appendChild(input10); + + var input11 = document.createElement("input"); + input11.onfocus = focusLogger; + slot1.appendChild(input11); + + var input2 = document.createElement("input"); + input2.onfocus = focusLogger; + document.body.appendChild(input2); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input0, "Should have focused input element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input2, "Should have focused input element. (4)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input11, "Should have focused input element in slot. (5)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input10, "Should have focused input element in slot. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input0, "Should have focused input element. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + input0.remove(); + slot1.remove(); + input2.remove(); + } + + function testTabbingThroughFocusableSlotInLightDOM() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + + var slot0 = document.createElement("slot"); + slot0.tabIndex = 0; + slot0.setAttribute("style", "display: inline;"); + slot0.onfocus = focusLogger; + document.body.appendChild(slot0); + + var slot00 = document.createElement("slot"); + slot00.tabIndex = 0; + slot00.setAttribute("style", "display: inline;"); + slot00.onfocus = focusLogger; + slot0.appendChild(slot00); + + var input000 = document.createElement("input"); + input000.onfocus = focusLogger; + slot00.appendChild(input000); + + var input01 = document.createElement("input"); + input01.onfocus = focusLogger; + slot0.appendChild(input01); + + var input1 = document.createElement("input"); + input1.onfocus = focusLogger; + document.body.appendChild(input1); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, slot0, "Should have focused slot element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, slot00, "Should have focused slot element. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (4)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input1, "Should have focused input element. (5)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input01, "Should have focused input element in slot. (6)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input000, "Should have focused input element in slot. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, slot00, "Should have focused slot element. (8)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, slot0, "Should have focused slot element. (9)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + slot0.remove(); + input1.remove(); + } + + function testTabbingThroughScrollableShadowDOM() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + + var host0 = document.createElement("div"); + host0.setAttribute("style", "height: 50px; overflow: auto;"); + host0.onfocus = focusLogger; + document.body.appendChild(host0); + + var sr0 = host0.attachShadow({mode: "open"}); + sr0.innerHTML = ` + <style> + div,slot { + height: 30px; + display: block; + overflow: auto; + } + input { + display: block; + } + </style> + `; + + var input00 = document.createElement("input"); + input00.setAttribute("style", "background-color: red;"); + input00.onfocus = focusLogger; + sr0.appendChild(input00); + + var container01 = document.createElement("div"); + container01.onfocus = focusLogger; + sr0.appendChild(container01); + + var input010 = document.createElement("input"); + input010.onfocus = focusLogger; + container01.appendChild(input010); + + var input011 = document.createElement("input"); + input011.onfocus = focusLogger; + container01.appendChild(input011); + + var slot02 = document.createElement("slot"); + slot02.onfocus = focusLogger; + sr0.appendChild(slot02); + + var input020 = document.createElement("input"); + input020.setAttribute("style", "display: block;"); + input020.onfocus = focusLogger; + host0.appendChild(input020); + + var input021 = document.createElement("input"); + input021.setAttribute("style", "display: block;"); + input021.onfocus = focusLogger; + host0.appendChild(input021); + + var input1 = document.createElement("input"); + input1.onfocus = focusLogger; + document.body.appendChild(input1); + + document.body.offsetLeft; + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, host0, "Should have focused shadow host element. (1)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (2)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (3)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (4)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (5)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (6)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (7)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (8)"); + + synthesizeKey("KEY_Tab"); + opener.is(lastFocusTarget, input1, "Should have focused input element in light dom. (9)"); + + // Backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input021, "Should have focused input element in slot. (10)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input020, "Should have focused input element in slot. (11)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, slot02, "Should have focused slot element in shadow dom. (12)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input011, "Should have focused input element in shadow dom. (13)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input010, "Should have focused input element in shadow dom. (14)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, container01, "Should have focused scrollable element in shadow dom. (15)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(lastFocusTarget, input00, "Should have focused input element in shadow dom. (16)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + // focus is already on host + opener.is(sr0.activeElement, null, + "Focus should have left input element in shadow DOM. (7)"); + + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, + "body's first child should have focus."); + + host0.remove(); + input1.remove(); + } + + function testDeeplyNestedShadowTree() { + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + var host1 = document.createElement("test-node"); + var lastHost = host1; + for (var i = 0; i < 20; ++i) { + lastHost.appendChild(document.createElement("test-node")); + lastHost = lastHost.firstChild; + } + + var input = document.createElement("input"); + document.body.appendChild(host1); + document.body.appendChild(input); + document.body.offsetLeft; + + // Test shadow tree which doesn't have anything tab-focusable. + host1.shadowRoot.querySelector("div").focus(); + synthesizeKey("KEY_Tab"); + is(document.activeElement, input, "Should have focused input element."); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, document.body.firstChild, "body's first child should have focus."); + + // Same test but with focusable elements in the tree... + var input2 = document.createElement("input"); + var host2 = host1.firstChild; + var host3 = host2.firstChild; + host2.insertBefore(input2, host3); + var input3 = document.createElement("input"); + lastHost.appendChild(input3); + document.body.offsetLeft; + host3.shadowRoot.querySelector("div").focus(); + synthesizeKey("KEY_Tab"); + is(document.activeElement, input3, "Should have focused input3 element."); + + // ...and backwards + host3.shadowRoot.querySelector("div").focus(); + synthesizeKey("KEY_Tab", {shiftKey: true}); + is(document.activeElement, input2, "Should have focused input2 element."); + + // Remove elements added to body element. + host1.remove(); + input.remove(); + + // Tests expect body.firstChild to have focus. + document.body.firstChild.focus(); + } + + // Bug 1558393 + function testBackwardsTabbingWithSlotsWithoutFocusableContent() { + let first = document.createElement("div"); + first.tabIndex = 0; + let host = document.createElement("div"); + host.tabIndex = 0; + let second = document.createElement("div"); + second.tabIndex = 0; + host.appendChild(document.createTextNode("foo")); + host.attachShadow({ mode: "open" }).innerHTML = `<slot></slot>`; + + document.body.appendChild(first); + document.body.appendChild(host); + document.body.appendChild(second); + document.body.offsetLeft; + + first.focus(); + opener.is(document.activeElement, first, "First light div should have focus"); + synthesizeKey("KEY_Tab"); + opener.is(document.activeElement, host, "Host should be focused"); + synthesizeKey("KEY_Tab"); + opener.is(document.activeElement, second, "Second light div should be focused"); + + // Now backwards + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, host, "Focus should return to host"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + opener.is(document.activeElement, first, "Focus should return to first light div"); + + second.remove(); + host.remove(); + first.remove(); + } + + function runTest() { + + testTabbingThroughShadowDOMWithTabIndexes(); + testTabbingThroughSimpleShadowDOM(); + testTabbingThroughNestedShadowDOM(); + testTabbingThroughDisplayContentsHost(); + testTabbingThroughLightDOMShadowDOMLightDOM(); + testFocusableHost(); + testShiftTabbingThroughFocusableHost(); + testTabbingThroughNestedSlot(); + testTabbingThroughSlotInLightDOM(); + testTabbingThroughFocusableSlotInLightDOM(); + testTabbingThroughScrollableShadowDOM(); + testDeeplyNestedShadowTree(); + testBackwardsTabbingWithSlotsWithoutFocusableContent(); + + opener.didRunTests(); + window.close(); + } + + function init() { + SimpleTest.waitForFocus(runTest); + } + </script> + <style> + </style> + <template id="template"> + <div style="overflow: hidden"> + <p tabindex="0" id="p1">component</p> + <p tabindex="0" id="p2">/component</p> + </div> + </template> + </head> + <body onload="init()"> + </body> +</html> diff --git a/dom/base/test/mochitest.ini b/dom/base/test/mochitest.ini index 8183dab8be..27a970c21a 100644 --- a/dom/base/test/mochitest.ini +++ b/dom/base/test/mochitest.ini @@ -129,6 +129,7 @@ support-files = file_bug1263696_frame_pass.html file_bug1263696_frame_fail.html file_bug1274806.html + file_bug1453693.html file_general_document.html file_htmlserializer_1.html file_htmlserializer_1_bodyonly.html @@ -609,6 +610,8 @@ skip-if = toolkit == 'android' [test_bug1308069.html] [test_bug1314032.html] [test_bug1375050.html] +[test_bug1453693.html] +skip-if = os == "mac" # Different tab focus behavior on mac [test_caretPositionFromPoint.html] [test_change_policy.html] [test_classList.html] @@ -648,6 +651,8 @@ skip-if = toolkit == 'android' #bug 904183 [test_fileapi.html] [test_fileapi_slice.html] skip-if = (toolkit == 'android') # Android: Bug 775227 +[test_focus_shadow_dom_root.html] +skip-if = os == "mac" # Different tab focus behavior on mac [test_getAttribute_after_createAttribute.html] [test_getElementById.html] [test_getTranslationNodes.html] diff --git a/dom/base/test/test_bug1453693.html b/dom/base/test/test_bug1453693.html new file mode 100644 index 0000000000..338fd67ed9 --- /dev/null +++ b/dom/base/test/test_bug1453693.html @@ -0,0 +1,32 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1453693 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1453693</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1453693 **/ + + SimpleTest.waitForExplicitFinish(); + + function runTests() { + win = window.open("file_bug1453693.html", "", "width=300, height=300"); + } + + function didRunTests() { + setTimeout("SimpleTest.finish()"); + } + + ; + + </script> +</head> +<body onload="SimpleTest.waitForFocus(runTests);"> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1453693">Mozilla Bug 1453693</a> +</body> +</html> diff --git a/dom/base/test/test_focus_shadow_dom_root.html b/dom/base/test/test_focus_shadow_dom_root.html new file mode 100644 index 0000000000..67aa74bd02 --- /dev/null +++ b/dom/base/test/test_focus_shadow_dom_root.html @@ -0,0 +1,36 @@ +<!doctype html> +<title>Test for bug 1544826</title> +<script src="/tests/SimpleTest/SimpleTest.js"></script> +<script src="/tests/SimpleTest/EventUtils.js"></script> +<div id="host"><a href="#" id="slotted">This is focusable too</a></div> +<script> + const host = document.getElementById("host"); + const shadow = host.attachShadow({ mode: "open" }); + shadow.innerHTML = ` + <a id="shadow-1" href="#">This is focusable</a> + <slot></slot> + <a id="shadow-2" href="#">So is this</a> + `; + document.documentElement.remove(); + document.appendChild(host); + + SimpleTest.waitForExplicitFinish(); + SimpleTest.waitForFocus(function() { + is(document.documentElement, host, "Host is the document element"); + host.offsetTop; + synthesizeKey("KEY_Tab"); + is(shadow.activeElement.id, "shadow-1", "First link in Shadow DOM is focused"); + synthesizeKey("KEY_Tab"); + is(document.activeElement.id, "slotted", "Slotted link is focused"); + synthesizeKey("KEY_Tab"); + is(shadow.activeElement.id, "shadow-2", "Second link in Shadow DOM is focused"); + + // Now backwards. + synthesizeKey("KEY_Tab", {shiftKey: true}); + is(document.activeElement.id, "slotted", "Backwards: Slotted link is focused"); + synthesizeKey("KEY_Tab", {shiftKey: true}); + is(shadow.activeElement.id, "shadow-1", "Backwards: First slotted link is focused"); + + SimpleTest.finish(); + }); +</script> diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index 879db27a49..080705174e 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2106,16 +2106,19 @@ DictionaryBase::AppendJSONToString(const char16_t* aJSONData, return true; } -nsresult -ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) +void +ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg, ErrorResult& aError) { js::AssertSameCompartment(aCx, aObjArg); + aError.MightThrowJSException(); + // Check if we're anywhere near the stack limit before we reach the // transplanting code, since it has no good way to handle errors. This uses // the untrusted script limit, which is not strictly necessary since no // actual script should run. - JS_CHECK_RECURSION_CONSERVATIVE(aCx, return NS_ERROR_FAILURE); + // TODO: Make sure to retain 'onerror' if bug 1342439 lands. + JS_CHECK_RECURSION_CONSERVATIVE(aCx, aError.StealExceptionFromJSContext(aCx); return); JS::Rooted<JSObject*> aObj(aCx, aObjArg); const DOMJSClass* domClass = GetDOMClass(aObj); @@ -2135,12 +2138,12 @@ ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) JSCompartment* newCompartment = js::GetObjectCompartment(newParent); if (oldCompartment == newCompartment) { MOZ_ASSERT(oldParent == newParent); - return NS_OK; + return; } nsISupports* native = UnwrapDOMObjectToISupports(aObj); if (!native) { - return NS_OK; + return; } bool isProxy = js::IsProxy(aObj); @@ -2156,12 +2159,14 @@ ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) JS::Handle<JSObject*> proto = (domClass->mGetProto)(aCx); if (!proto) { - return NS_ERROR_FAILURE; + aError.StealExceptionFromJSContext(aCx); + return; } JS::Rooted<JSObject*> newobj(aCx, JS_CloneObject(aCx, aObj, proto)); if (!newobj) { - return NS_ERROR_FAILURE; + aError.StealExceptionFromJSContext(aCx); + return; } JS::Rooted<JSObject*> propertyHolder(aCx); @@ -2169,11 +2174,13 @@ ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) if (copyFrom) { propertyHolder = JS_NewObjectWithGivenProto(aCx, nullptr, nullptr); if (!propertyHolder) { - return NS_ERROR_OUT_OF_MEMORY; + aError.StealExceptionFromJSContext(aCx); + return; } if (!JS_CopyPropertiesFrom(aCx, propertyHolder, copyFrom)) { - return NS_ERROR_FAILURE; + aError.StealExceptionFromJSContext(aCx); + return; } } else { propertyHolder = nullptr; @@ -2189,7 +2196,8 @@ ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) // if expandos are present then the wrapper will already have been preserved // for this native. if (!xpc::XrayUtils::CloneExpandoChain(aCx, newobj, aObj)) { - return NS_ERROR_FAILURE; + aError.StealExceptionFromJSContext(aCx); + return; } // We've set up |newobj|, so we make it own the native by setting its reserved @@ -2244,9 +2252,6 @@ ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObjArg) if (htmlobject) { htmlobject->SetupProtoChain(aCx, aObj); } - - // Now we can just return the wrapper - return NS_OK; } GlobalObject::GlobalObject(JSContext* aCx, JSObject* aObject) diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 9ae5ed1f73..d55be9eb89 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -2758,8 +2758,8 @@ const nsAString& NonNullHelper(const binding_detail::FakeString& aArg) // Reparent the wrapper of aObj to whatever its native now thinks its // parent should be. -nsresult -ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObj); +void +ReparentWrapper(JSContext* aCx, JS::Handle<JSObject*> aObj, ErrorResult& aError); /** * Used to implement the Symbol.hasInstance property of an interface object. diff --git a/dom/events/ContentEventHandler.cpp b/dom/events/ContentEventHandler.cpp index ab5ea8df29..11fb53029b 100644 --- a/dom/events/ContentEventHandler.cpp +++ b/dom/events/ContentEventHandler.cpp @@ -116,10 +116,9 @@ ContentEventHandler::InitBasic() // If text frame which has overflowing selection underline is dirty, // we need to flush the pending reflow here. - mPresShell->FlushPendingNotifications(Flush_Layout); - - // Flushing notifications can cause mPresShell to be destroyed (bug 577963). - NS_ENSURE_TRUE(!mPresShell->IsDestroying(), NS_ERROR_FAILURE); + if (nsIDocument* doc = mPresShell->GetDocument()) { + doc->FlushPendingNotifications(Flush_Layout); + } return NS_OK; } @@ -170,11 +169,11 @@ ContentEventHandler::InitRootContent(Selection* aNormalSelection) } // See bug 537041 comment 5, the range could have removed node. - if (NS_WARN_IF(startNode->GetUncomposedDoc() != mPresShell->GetDocument())) { + if (NS_WARN_IF(startNode->GetComposedDoc() != mPresShell->GetDocument())) { return NS_ERROR_FAILURE; } - NS_ASSERTION(startNode->GetUncomposedDoc() == endNode->GetUncomposedDoc(), + NS_ASSERTION(startNode->GetComposedDoc() == endNode->GetComposedDoc(), "firstNormalSelectionRange crosses the document boundary"); mRootContent = startNode->GetSelectionRootContent(mPresShell); @@ -2883,31 +2882,6 @@ ContentEventHandler::AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aRange) } nsresult -ContentEventHandler::GetStartFrameAndOffset(const nsRange* aRange, - nsIFrame*& aFrame, - int32_t& aOffsetInFrame) -{ - MOZ_ASSERT(aRange); - - aFrame = nullptr; - aOffsetInFrame = -1; - - nsINode* node = aRange->GetStartParent(); - if (NS_WARN_IF(!node) || - NS_WARN_IF(!node->IsNodeOfType(nsINode::eCONTENT))) { - return NS_ERROR_FAILURE; - } - nsIContent* content = static_cast<nsIContent*>(node); - RefPtr<nsFrameSelection> fs = mPresShell->FrameSelection(); - aFrame = fs->GetFrameForNodeOffset(content, aRange->StartOffset(), - fs->GetHint(), &aOffsetInFrame); - if (NS_WARN_IF(!aFrame)) { - return NS_ERROR_FAILURE; - } - return NS_OK; -} - -nsresult ContentEventHandler::ConvertToRootRelativeOffset(nsIFrame* aFrame, nsRect& aRect) { diff --git a/dom/events/ContentEventHandler.h b/dom/events/ContentEventHandler.h index 6616f05ea1..831074e9e2 100644 --- a/dom/events/ContentEventHandler.h +++ b/dom/events/ContentEventHandler.h @@ -287,10 +287,6 @@ protected: // If the aRange isn't in text node but next to a text node, this method // modifies it in the text node. Otherwise, not modified. nsresult AdjustCollapsedRangeMaybeIntoTextNode(nsRange* aCollapsedRange); - // Find the first frame for the range and get the start offset in it. - nsresult GetStartFrameAndOffset(const nsRange* aRange, - nsIFrame*& aFrame, - int32_t& aOffsetInFrame); // Convert the frame relative offset to be relative to the root frame of the // root presContext (but still measured in appUnits of aFrame's presContext). nsresult ConvertToRootRelativeOffset(nsIFrame* aFrame, diff --git a/dom/events/Event.cpp b/dom/events/Event.cpp index 864678dd66..31a39a5609 100755 --- a/dom/events/Event.cpp +++ b/dom/events/Event.cpp @@ -148,6 +148,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Event) tmp->mEvent->mCurrentTarget = nullptr; tmp->mEvent->mOriginalTarget = nullptr; tmp->mEvent->mRelatedTarget = nullptr; + tmp->mEvent->mOriginalRelatedTarget = nullptr; switch (tmp->mEvent->mClass) { case eDragEventClass: { WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent(); @@ -176,6 +177,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Event) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mCurrentTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mRelatedTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEvent->mOriginalRelatedTarget) switch (tmp->mEvent->mClass) { case eDragEventClass: { WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent(); @@ -555,6 +557,26 @@ Event::SetEventType(const nsAString& aEventTypeArg) mEvent->SetDefaultComposedInNativeAnonymousContent(); } +already_AddRefed<EventTarget> +Event::EnsureWebAccessibleRelatedTarget(EventTarget* aRelatedTarget) +{ + nsCOMPtr<EventTarget> relatedTarget = aRelatedTarget; + if (relatedTarget) { + nsCOMPtr<nsIContent> content = do_QueryInterface(relatedTarget); + + if (content && content->ChromeOnlyAccess() && + !nsContentUtils::CanAccessNativeAnon()) { + content = content->FindFirstNonChromeOnlyAccessContent(); + relatedTarget = do_QueryInterface(content); + } + + if (relatedTarget) { + relatedTarget = relatedTarget->GetTargetForDOMEvent(); + } + } + return relatedTarget.forget(); +} + void Event::InitEvent(const nsAString& aEventTypeArg, bool aCanBubbleArg, @@ -1227,44 +1249,6 @@ Event::SetOwner(EventTarget* aOwner) #endif } -// static -nsIContent* -Event::GetShadowRelatedTarget(nsIContent* aCurrentTarget, - nsIContent* aRelatedTarget) -{ - if (!aCurrentTarget || !aRelatedTarget) { - return nullptr; - } - - // Walk up the ancestor node trees of the related target until - // we encounter the node tree of the current target in order - // to find the adjusted related target. Walking up the tree may - // not find a common ancestor node tree if the related target is in - // an ancestor tree, but in that case it does not need to be adjusted. - ShadowRoot* currentTargetShadow = aCurrentTarget->GetContainingShadow(); - if (!currentTargetShadow) { - return nullptr; - } - - nsIContent* relatedTarget = aCurrentTarget; - while (relatedTarget) { - ShadowRoot* ancestorShadow = relatedTarget->GetContainingShadow(); - if (currentTargetShadow == ancestorShadow) { - return relatedTarget; - } - - // Didn't find the ancestor tree, thus related target does not have to - // adjusted. - if (!ancestorShadow) { - return nullptr; - } - - relatedTarget = ancestorShadow->GetHost(); - } - - return nullptr; -} - NS_IMETHODIMP Event::GetCancelBubble(bool* aCancelBubble) { diff --git a/dom/events/Event.h b/dom/events/Event.h index 7e461a3f89..e48428f741 100755 --- a/dom/events/Event.h +++ b/dom/events/Event.h @@ -251,14 +251,6 @@ public: return mIsMainThreadEvent; } - /** - * For a given current target, returns the related target adjusted with - * shadow DOM retargeting rules. Returns nullptr if related target - * is not adjusted. - */ - static nsIContent* GetShadowRelatedTarget(nsIContent* aCurrentTarget, - nsIContent* aRelatedTarget); - void MarkUninitialized() { mEvent->mMessage = eVoidEvent; @@ -295,6 +287,9 @@ protected: mEvent->SetComposed(aComposed); } + already_AddRefed<EventTarget> + EnsureWebAccessibleRelatedTarget(EventTarget* aRelatedTarget); + mozilla::WidgetEvent* mEvent; RefPtr<nsPresContext> mPresContext; nsCOMPtr<EventTarget> mExplicitOriginalTarget; diff --git a/dom/events/EventDispatcher.cpp b/dom/events/EventDispatcher.cpp index b708b03e97..e2b5699241 100644 --- a/dom/events/EventDispatcher.cpp +++ b/dom/events/EventDispatcher.cpp @@ -204,6 +204,16 @@ public: mNewTarget = aNewTarget; } + EventTarget* GetRetargetedRelatedTarget() + { + return mRetargetedRelatedTarget; + } + + void SetRetargetedRelatedTarget(EventTarget* aTarget) + { + mRetargetedRelatedTarget = aTarget; + } + void SetForceContentDispatch(bool aForce) { mFlags.mForceContentDispatch = aForce; @@ -354,6 +364,7 @@ public: private: nsCOMPtr<EventTarget> mTarget; + nsCOMPtr<EventTarget> mRetargetedRelatedTarget; class EventTargetChainFlags { @@ -422,6 +433,7 @@ EventTargetChainItem::GetEventTargetParent(EventChainPreVisitor& aVisitor) SetWantsPreHandleEvent(aVisitor.mWantsPreHandleEvent); SetPreHandleEventOnly(aVisitor.mWantsPreHandleEvent && !aVisitor.mCanHandle); SetRootOfClosedTree(aVisitor.mRootOfClosedTree); + SetRetargetedRelatedTarget(aVisitor.mRetargetedRelatedTarget); mItemFlags = aVisitor.mItemFlags; mItemData = aVisitor.mItemData; } @@ -454,6 +466,7 @@ EventTargetChainItem::HandleEventTargetChain( { // Save the target so that it can be restored later. nsCOMPtr<EventTarget> firstTarget = aVisitor.mEvent->mTarget; + nsCOMPtr<EventTarget> firstRelatedTarget = aVisitor.mEvent->mRelatedTarget; uint32_t chainLength = aChain.Length(); uint32_t firstCanHandleEventTargetIdx = EventTargetChainItem::GetFirstCanHandleEventTargetIdx(aChain); @@ -483,6 +496,30 @@ EventTargetChainItem::HandleEventTargetChain( } } } + + // https://dom.spec.whatwg.org/#dispatching-events + // Step 14.2 + // "Set event's relatedTarget to tuple's relatedTarget." + // Note, the initial retargeting was done already when creating + // event target chain, so we need to do this only after calling + // HandleEvent, not before, like in the specification. + if (item.GetRetargetedRelatedTarget()) { + bool found = false; + for (uint32_t j = i; j > 0; --j) { + uint32_t childIndex = j - 1; + EventTarget* relatedTarget = + aChain[childIndex].GetRetargetedRelatedTarget(); + if (relatedTarget) { + found = true; + aVisitor.mEvent->mRelatedTarget = relatedTarget; + break; + } + } + if (!found) { + aVisitor.mEvent->mRelatedTarget = + aVisitor.mEvent->mOriginalRelatedTarget; + } + } } // Target @@ -511,6 +548,14 @@ EventTargetChainItem::HandleEventTargetChain( aVisitor.mEvent->mTarget = newTarget; } + // https://dom.spec.whatwg.org/#dispatching-events + // Step 15.2 + // "Set event's relatedTarget to tuple's relatedTarget." + EventTarget* relatedTarget = item.GetRetargetedRelatedTarget(); + if (relatedTarget) { + aVisitor.mEvent->mRelatedTarget = relatedTarget; + } + if (aVisitor.mEvent->mFlags.mBubbles || newTarget) { if ((!aVisitor.mEvent->mFlags.mNoContentDispatch || item.ForceContentDispatch()) && @@ -542,6 +587,7 @@ EventTargetChainItem::HandleEventTargetChain( // Retarget for system event group (which does the default handling too). // Setting back the target which was used also for default event group. aVisitor.mEvent->mTarget = firstTarget; + aVisitor.mEvent->mRelatedTarget = aVisitor.mEvent->mOriginalRelatedTarget; aVisitor.mEvent->mFlags.mInSystemGroup = true; HandleEventTargetChain(aChain, aVisitor, @@ -599,6 +645,7 @@ MayRetargetToChromeIfCanNotHandleEvent( EventTargetChainItem::DestroyLast(aChain, aTargetEtci); } if (aPreVisitor.mAutomaticChromeDispatch && aContent) { + aPreVisitor.mRelatedTargetRetargetedInCurrentScope = false; // Event target couldn't handle the event. Try to propagate to chrome. EventTargetChainItem* chromeTargetEtci = EventTargetChainItemForChromeTarget(aChain, aContent, aChildEtci); @@ -762,9 +809,10 @@ EventDispatcher::Dispatch(nsISupports* aTarget, aEvent->mOriginalTarget = aEvent->mTarget; } + aEvent->mOriginalRelatedTarget = aEvent->mRelatedTarget; + nsCOMPtr<nsIContent> content = do_QueryInterface(aEvent->mOriginalTarget); - bool isInAnon = (content && (content->IsInAnonymousSubtree() || - content->IsInShadowTree())); + bool isInAnon = (content && content->IsInAnonymousSubtree()); aEvent->mFlags.mIsBeingDispatched = true; @@ -772,7 +820,7 @@ EventDispatcher::Dispatch(nsISupports* aTarget, // GetEventTargetParent for the original target. nsEventStatus status = aEventStatus ? *aEventStatus : nsEventStatus_eIgnore; EventChainPreVisitor preVisitor(aPresContext, aEvent, aDOMEvent, status, - isInAnon); + isInAnon, aEvent->mTarget); targetEtci->GetEventTargetParent(preVisitor); if (!preVisitor.mCanHandle) { @@ -810,14 +858,21 @@ EventDispatcher::Dispatch(nsISupports* aTarget, if (preVisitor.mEventTargetAtParent) { // Need to set the target of the event // so that also the next retargeting works. + preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; preVisitor.mEvent->mTarget = preVisitor.mEventTargetAtParent; parentEtci->SetNewTarget(preVisitor.mEventTargetAtParent); } + if (preVisitor.mRetargetedRelatedTarget) { + preVisitor.mEvent->mRelatedTarget = preVisitor.mRetargetedRelatedTarget; + } + parentEtci->GetEventTargetParent(preVisitor); if (preVisitor.mCanHandle) { + preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; topEtci = parentEtci; } else { + bool ignoreBecauseOfShadowDOM = preVisitor.mIgnoreBecauseOfShadowDOM; nsCOMPtr<nsINode> disabledTarget = do_QueryInterface(parentTarget); parentEtci = MayRetargetToChromeIfCanNotHandleEvent(chain, preVisitor, @@ -825,9 +880,14 @@ EventDispatcher::Dispatch(nsISupports* aTarget, topEtci, disabledTarget); if (parentEtci && preVisitor.mCanHandle) { + preVisitor.mTargetInKnownToBeHandledScope = preVisitor.mEvent->mTarget; EventTargetChainItem* item = EventTargetChainItem::GetFirstCanHandleEventTarget(chain); - item->SetNewTarget(parentTarget); + if (!ignoreBecauseOfShadowDOM) { + // If we ignored the target because of Shadow DOM retargeting, we + // shouldn't treat the target to be in the event path at all. + item->SetNewTarget(parentTarget); + } topEtci = parentEtci; continue; } @@ -871,6 +931,19 @@ EventDispatcher::Dispatch(nsISupports* aTarget, aEvent->mFlags.mIsBeingDispatched = false; aEvent->mFlags.mDispatchedAtLeastOnce = true; + // https://dom.spec.whatwg.org/#concept-event-dispatch + // Step 18 + // "If target's root is a shadow root, then set event's target attribute and + // event's relatedTarget to null." + nsCOMPtr<nsIContent> finalTarget = do_QueryInterface(aEvent->mTarget); + // TODO: replace with IsShadowRoot() check once bug 1427511 lands. + if (finalTarget && ShadowRoot::FromNode(finalTarget->SubtreeRoot())) { + aEvent->mTarget = nullptr; + aEvent->mOriginalTarget = nullptr; + aEvent->mRelatedTarget = nullptr; + aEvent->mOriginalRelatedTarget = nullptr; + } + if (!externalDOMEvent && preVisitor.mDOMEvent) { // An dom::Event was created while dispatching the event. // Duplicate private data if someone holds a pointer to it. diff --git a/dom/events/EventDispatcher.h b/dom/events/EventDispatcher.h index dc11536eb6..403c472c5c 100644 --- a/dom/events/EventDispatcher.h +++ b/dom/events/EventDispatcher.h @@ -113,7 +113,8 @@ public: WidgetEvent* aEvent, nsIDOMEvent* aDOMEvent, nsEventStatus aEventStatus, - bool aIsInAnon) + bool aIsInAnon, + dom::EventTarget* aTargetInKnownToBeHandledScope) : EventChainVisitor(aPresContext, aEvent, aDOMEvent, aEventStatus) , mCanHandle(true) , mAutomaticChromeDispatch(true) @@ -126,8 +127,12 @@ public: , mRootOfClosedTree(false) , mParentIsSlotInClosedTree(false) , mParentIsChromeHandler(false) + , mRelatedTargetRetargetedInCurrentScope(false) + , mIgnoreBecauseOfShadowDOM(false) , mParentTarget(nullptr) , mEventTargetAtParent(nullptr) + , mRetargetedRelatedTarget(nullptr) + , mTargetInKnownToBeHandledScope(aTargetInKnownToBeHandledScope) { } @@ -144,8 +149,13 @@ public: mRootOfClosedTree = false; mParentIsSlotInClosedTree = false; mParentIsChromeHandler = false; + // Note, we don't clear mRelatedTargetRetargetedInCurrentScope explicitly, + // since it is used during event path creation to indicate whether + // relatedTarget may need to be retargeted. + mIgnoreBecauseOfShadowDOM = false; mParentTarget = nullptr; mEventTargetAtParent = nullptr; + mRetargetedRelatedTarget = nullptr; } dom::EventTarget* GetParentTarget() @@ -161,6 +171,14 @@ public: } } + void IgnoreCurrentTargetBecauseOfShadowDOMRetargeting() + { + mCanHandle = false; + mIgnoreBecauseOfShadowDOM = true; + SetParentTarget(nullptr, false); + mEventTargetAtParent = nullptr; + } + /** * Member that must be set in GetEventTargetParent by event targets. If set to * false, indicates that this event target will not be handling the event and @@ -229,6 +247,19 @@ public: */ bool mParentIsChromeHandler; + /** + * True if event's related target has been already retargeted in the + * current 'scope'. This should be set to false initially and whenever + * event path creation crosses shadow boundary. + */ + bool mRelatedTargetRetargetedInCurrentScope; + + /** + * True if Shadow DOM relatedTarget retargeting causes the current item + * to not show up in the event path. + */ + bool mIgnoreBecauseOfShadowDOM; + private: /** * Parent item in the event target chain. @@ -241,6 +272,19 @@ public: * which should be used when the event is handled at mParentTarget. */ dom::EventTarget* mEventTargetAtParent; + + /** + * If the related target of the event needs to be retargeted, set this + * to a new EventTarget. + */ + dom::EventTarget* mRetargetedRelatedTarget; + + /** + * Set to the value of mEvent->mTarget of the previous scope in case of + * Shadow DOM or such, and if there is no anonymous content this just points + * to the initial target. + */ + dom::EventTarget* mTargetInKnownToBeHandledScope; }; class EventChainPostVisitor : public mozilla::EventChainVisitor diff --git a/dom/events/FocusEvent.cpp b/dom/events/FocusEvent.cpp index 02fce7c939..48c22d8d23 100644 --- a/dom/events/FocusEvent.cpp +++ b/dom/events/FocusEvent.cpp @@ -30,14 +30,14 @@ NS_IMETHODIMP FocusEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget) { NS_ENSURE_ARG_POINTER(aRelatedTarget); - NS_IF_ADDREF(*aRelatedTarget = GetRelatedTarget()); + *aRelatedTarget = GetRelatedTarget().take(); return NS_OK; } -EventTarget* +already_AddRefed<EventTarget> FocusEvent::GetRelatedTarget() { - return mEvent->AsFocusEvent()->mRelatedTarget; + return EnsureWebAccessibleRelatedTarget(mEvent->AsFocusEvent()->mRelatedTarget); } void diff --git a/dom/events/FocusEvent.h b/dom/events/FocusEvent.h index 055555716d..f2e0a6ea70 100644 --- a/dom/events/FocusEvent.h +++ b/dom/events/FocusEvent.h @@ -32,7 +32,7 @@ public: nsPresContext* aPresContext, InternalFocusEvent* aEvent); - EventTarget* GetRelatedTarget(); + already_AddRefed<EventTarget> GetRelatedTarget(); static already_AddRefed<FocusEvent> Constructor(const GlobalObject& aGlobal, const nsAString& aType, diff --git a/dom/events/MouseEvent.cpp b/dom/events/MouseEvent.cpp index 72f0df4260..bf1423b2f4 100644 --- a/dom/events/MouseEvent.cpp +++ b/dom/events/MouseEvent.cpp @@ -300,27 +300,7 @@ MouseEvent::GetRelatedTarget() break; } - if (relatedTarget) { - nsCOMPtr<nsIContent> content = do_QueryInterface(relatedTarget); - nsCOMPtr<nsIContent> currentTarget = - do_QueryInterface(mEvent->mCurrentTarget); - - nsIContent* shadowRelatedTarget = GetShadowRelatedTarget(currentTarget, content); - if (shadowRelatedTarget) { - relatedTarget = shadowRelatedTarget; - } - - if (content && content->ChromeOnlyAccess() && - !nsContentUtils::CanAccessNativeAnon()) { - relatedTarget = do_QueryInterface(content->FindFirstNonChromeOnlyAccessContent()); - } - - if (relatedTarget) { - relatedTarget = relatedTarget->GetTargetForDOMEvent(); - } - return relatedTarget.forget(); - } - return nullptr; + return EnsureWebAccessibleRelatedTarget(relatedTarget); } void diff --git a/dom/html/HTMLImageElement.cpp b/dom/html/HTMLImageElement.cpp index b11cd101a0..b697dcabea 100644 --- a/dom/html/HTMLImageElement.cpp +++ b/dom/html/HTMLImageElement.cpp @@ -623,8 +623,9 @@ HTMLImageElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, } if (HaveSrcsetOrInPicture()) { - if (aDocument && !mInDocResponsiveContent) { - aDocument->AddResponsiveContent(this); + nsIDocument* doc = GetComposedDoc(); + if (doc && !mInDocResponsiveContent) { + doc->AddResponsiveContent(this); mInDocResponsiveContent = true; } diff --git a/dom/html/HTMLLinkElement.cpp b/dom/html/HTMLLinkElement.cpp index df0cc72a40..b8e859cc00 100644 --- a/dom/html/HTMLLinkElement.cpp +++ b/dom/html/HTMLLinkElement.cpp @@ -162,7 +162,8 @@ HTMLLinkElement::HasDeferredDNSPrefetchRequest() } nsresult -HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, +HTMLLinkElement::BindToTree(nsIDocument* aDocument, + nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { @@ -173,12 +174,8 @@ HTMLLinkElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); - // Link must be inert in ShadowRoot. - if (aDocument && !GetContainingShadow()) { - aDocument->RegisterPendingLinkUpdate(this); - } - - if (IsInComposedDoc()) { + if (nsIDocument* doc = GetComposedDoc()) { + doc->RegisterPendingLinkUpdate(this); TryDNSPrefetchPreconnectOrPrefetch(); } @@ -222,11 +219,7 @@ HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent) // If this is reinserted back into the document it will not be // from the parser. nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc(); - - // Check for a ShadowRoot because link elements are inert in a - // ShadowRoot. - ShadowRoot* oldShadowRoot = GetBindingParent() ? - GetBindingParent()->GetShadowRoot() : nullptr; + ShadowRoot* oldShadowRoot = GetContainingShadow(); OwnerDoc()->UnregisterPendingLinkUpdate(this); diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp index 0f32d8fb42..da7121a714 100644 --- a/dom/html/nsGenericHTMLElement.cpp +++ b/dom/html/nsGenericHTMLElement.cpp @@ -463,7 +463,7 @@ nsGenericHTMLElement::IntrinsicState() const uint32_t nsGenericHTMLElement::EditableInclusiveDescendantCount() { - bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) && + bool isEditable = IsInComposedDoc() && HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue; return EditableDescendantCount() + isEditable; } @@ -484,12 +484,14 @@ nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, aDocument-> AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); } + } - if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { - nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument); - if (htmlDocument) { - htmlDocument->ChangeContentEditableCount(this, +1); - } + if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue && + IsInComposedDoc()) { + nsCOMPtr<nsIHTMLDocument> htmlDocument = + do_QueryInterface(GetComposedDoc()); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, +1); } } @@ -514,8 +516,7 @@ nsGenericHTMLElement::UnbindFromTree(bool aDeep, bool aNullParent) RemoveFromNameTable(); if (GetContentEditableValue() == eTrue) { - //XXXsmaug Fix this for Shadow DOM, bug 1066965. - nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetUncomposedDoc()); + nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetComposedDoc()); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, -1); } @@ -2774,8 +2775,7 @@ MakeContentDescendantsEditable(nsIContent *aContent, nsIDocument *aDocument) void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) { - //XXXsmaug Fix this for Shadow DOM, bug 1066965. - nsIDocument* document = GetUncomposedDoc(); + nsIDocument* document = GetComposedDoc(); if (!document) { return; } @@ -2788,7 +2788,8 @@ nsGenericHTMLElement::ChangeEditableState(int32_t aChange) } nsIContent* parent = GetParent(); - while (parent) { + // Don't update across Shadow DOM boundary. + while (parent && parent->IsElement()) { parent->ChangeEditableDescendantCount(aChange); parent = parent->GetParent(); } diff --git a/dom/html/test/file_fullscreen-shadowdom.html b/dom/html/test/file_fullscreen-shadowdom.html new file mode 100644 index 0000000000..efc7fe8f5f --- /dev/null +++ b/dom/html/test/file_fullscreen-shadowdom.html @@ -0,0 +1,52 @@ +<!DOCTYPE HTML> +<html> + <!-- + https://bugzilla.mozilla.org/show_bug.cgi?id=1430305 + Bug 1430305 - Implement ShadowRoot.fullscreenElement + --> + <head> + <title>Bug 1430305</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"> + </script> + <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"> + </script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> + </head> + <body> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=1430305"> + Mozilla Bug 1430305</a> + + <div id="host"></div> + + <pre id="test"> + <script type="application/javascript"> + + function begin() { + var host = document.getElementById("host"); + var shadowRoot = host.attachShadow({mode: "open"}); + shadowRoot.innerHTML = "<div>test</div>"; + var elem = shadowRoot.firstChild; + var gotFullscreenEvent = false; + + document.addEventListener("fullscreenchange", function (e) { + if (document.fullscreenElement === host) { + is(shadowRoot.fullscreenElement, elem, + "Expected element entered fullsceen"); + gotFullscreenEvent = true; + document.exitFullscreen(); + } else { + opener.ok(gotFullscreenEvent, "Entered fullscreen as expected"); + is(shadowRoot.fullscreenElement, null, + "Shouldn't have fullscreenElement anymore."); + is(document.fullscreenElement, null, + "Shouldn't have fullscreenElement anymore."); + opener.nextTest(); + } + }); + elem.requestFullscreen(); + } + </script> + </pre> + </body> +</html> diff --git a/dom/html/test/mochitest.ini b/dom/html/test/mochitest.ini index dcbb73840a..df5a9e3b55 100644 --- a/dom/html/test/mochitest.ini +++ b/dom/html/test/mochitest.ini @@ -72,6 +72,7 @@ support-files = file_fullscreen-rollback.html file_fullscreen-scrollbar.html file_fullscreen-selector.html + file_fullscreen-shadowdom.html file_fullscreen-svg-element.html file_fullscreen-top-layer.html file_fullscreen-unprefix-disabled-inner.html @@ -444,6 +445,7 @@ support-files = [test_bug1146116.html] [test_bug1264157.html] [test_bug1287321.html] +[test_bug1323815.html] [test_change_crossorigin.html] [test_checked.html] [test_dir_attributes_reflection.html] diff --git a/dom/html/test/test_bug1323815.html b/dom/html/test/test_bug1323815.html new file mode 100644 index 0000000000..5c54504190 --- /dev/null +++ b/dom/html/test/test_bug1323815.html @@ -0,0 +1,50 @@ +<!DOCTYPE HTML> +<html> +<!-- +https://bugzilla.mozilla.org/show_bug.cgi?id=1323815 +--> +<head> + <meta charset="utf-8"> + <title>Test for Bug 1323815</title> + <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> + <script type="application/javascript"> + + /** Test for Bug 1323815 **/ + +SimpleTest.waitForExplicitFinish(); +function test() { + var n = document.getElementById("number"); + var t = document.getElementById("text"); + t.focus(); + var gotBlur = false; + t.onblur = function(e) { + try { + is(e.relatedTarget.localName, "input"); + } catch(ex) { + ok(false, "Accessing properties on the relatedTarget shouldn't throw! " + ex); + } + gotBlur = true; + } + + n.focus(); + ok(gotBlur); + SimpleTest.finish(); +} + +SimpleTest.waitForFocus(test); + + </script> +</head> +<body> +<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1323815">Mozilla Bug 1323815</a> +<p id="display"></p> +<div id="content" style="display: none"> + +</div> +<pre id="test"> +</pre> + +<input type="number" id="number"><input type="text" id="text"> +</body> +</html> diff --git a/dom/html/test/test_fullscreen-api.html b/dom/html/test/test_fullscreen-api.html index 1ac5b3b517..e269c67ad4 100644 --- a/dom/html/test/test_fullscreen-api.html +++ b/dom/html/test/test_fullscreen-api.html @@ -39,6 +39,7 @@ var gTestWindows = [ "file_fullscreen-navigation.html", "file_fullscreen-scrollbar.html", "file_fullscreen-selector.html", + "file_fullscreen-shadowdom.html", "file_fullscreen-top-layer.html", "file_fullscreen-backdrop.html", "file_fullscreen-nested.html", @@ -96,7 +97,8 @@ addLoadEvent(function() { ["full-screen-api.unprefix.enabled", true], ["full-screen-api.allow-trusted-requests-only", false], ["full-screen-api.transition-duration.enter", "0 0"], - ["full-screen-api.transition-duration.leave", "0 0"] + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.webcomponents.enabled", true] ]}, nextTest); }); SimpleTest.waitForExplicitFinish(); diff --git a/dom/svg/SVGUseElement.cpp b/dom/svg/SVGUseElement.cpp index aceac1bb8a..acd8941b4e 100644 --- a/dom/svg/SVGUseElement.cpp +++ b/dom/svg/SVGUseElement.cpp @@ -259,14 +259,13 @@ SVGUseElement::CreateAnonymousContent() } } - nsCOMPtr<nsINode> newnode; nsCOMArray<nsINode> unused; nsNodeInfoManager* nodeInfoManager = targetContent->OwnerDoc() == OwnerDoc() ? nullptr : OwnerDoc()->NodeInfoManager(); - nsNodeUtils::Clone(targetContent, true, nodeInfoManager, unused, - getter_AddRefs(newnode)); - + IgnoredErrorResult rv; + nsCOMPtr<nsINode> newnode = + nsNodeUtils::Clone(targetContent, true, nodeInfoManager, &unused, rv); nsCOMPtr<nsIContent> newcontent = do_QueryInterface(newnode); if (!newcontent) diff --git a/dom/tests/mochitest/pointerlock/file_pointerlock-api-with-shadow.html b/dom/tests/mochitest/pointerlock/file_pointerlock-api-with-shadow.html new file mode 100644 index 0000000000..dd764b90c5 --- /dev/null +++ b/dom/tests/mochitest/pointerlock/file_pointerlock-api-with-shadow.html @@ -0,0 +1,110 @@ +<!DOCTYPE HTML>
+<html>
+<!--https://bugzilla.mozilla.org/show_bug.cgi?id=633602-->
+<head>
+ <title>Bug 633602</title>
+ <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js">
+ </script>
+ <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <script type="application/javascript" src="pointerlock_utils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <a target="_blank"
+ href="https://bugzilla.mozilla.org/show_bug.cgi?id=633602">
+ Mozilla Bug 633602
+ </a>
+ <div id="host"></div>
+ <pre id="test">
+ <script type="text/javascript">
+ /*
+ * Test for Bug 1430303
+ * Make sure DOM API is correct.
+ */
+
+ SimpleTest.waitForExplicitFinish();
+
+ var div,
+ host,
+ hasRequestPointerLock = false,
+ pointerLockChangeEventFired = false,
+ pointerUnlocked = false,
+ pointerLocked = false,
+ hasExitPointerLock = false,
+ pointerLockElement = false,
+ hasMovementX = false,
+ hasMovementY = false;
+ gotContextMenuEvent = false;
+
+ function runTests () {
+ ok(hasRequestPointerLock, "Element should have requestPointerLock.");
+ ok(pointerLockChangeEventFired, "pointerlockchange event should fire.");
+ ok(pointerUnlocked, "Should be able to unlock pointer locked element.");
+ ok(pointerLocked, "Requested element should be able to lock.");
+ ok(hasExitPointerLock, "Document should have exitPointerLock");
+ ok(pointerLockElement, "Document should keep track of correct pointer locked element");
+ ok(hasMovementX, "Mouse Event should have movementX.");
+ ok(hasMovementY, "Mouse Event should have movementY.");
+ ok(!gotContextMenuEvent, "Shouldn't have got a contextmenu event.");
+ }
+
+ function mouseMoveHandler(e) {
+ info("Got mousemove");
+ document.removeEventListener("mousemove", mouseMoveHandler);
+
+ hasMovementX = "movementX" in e;
+ hasMovementY = "movementY" in e;
+
+ hasExitPointerLock = "exitPointerLock" in document;
+ document.exitPointerLock();
+ }
+
+ document.addEventListener("pointerlockchange", function (e) {
+ pointerLockChangeEventFired = true;
+
+ if (document.pointerLockElement) {
+ info("Got pointerlockchange for entering");
+ pointerLocked = true;
+ pointerLockElement = document.pointerLockElement === host;
+ is(host.shadowRoot.firstChild, host.shadowRoot.pointerLockElement,
+ "ShadowRoot's pointerLockElement shouldn't be retargeted.");
+
+ window.addEventListener("contextmenu",
+ function() { gotContextMenuEvent = true; },
+ true);
+ synthesizeMouse(document.body, 4, 4,
+ { type: "contextmenu", button: 2 },
+ window);
+
+ document.addEventListener("mousemove", mouseMoveHandler);
+ synthesizeMouseAtCenter(host, {type: "mousemove"}, window);
+ } else {
+ info("Got pointerlockchange for exiting");
+ pointerUnlocked = true;
+ addFullscreenChangeContinuation("exit", function() {
+ info("Got fullscreenchange for exiting");
+ runTests();
+ SimpleTest.finish();
+ });
+ document.exitFullscreen();
+ }
+ });
+
+ function start() {
+ host = document.getElementById("host");
+ var sr = host.attachShadow({mode: "open"});
+ sr.innerHTML = "<div><div>";
+ div = sr.firstChild;
+ info("Requesting fullscreen on parent");
+ addFullscreenChangeContinuation("enter", function() {
+ info("Got fullscreenchange for entering");
+ hasRequestPointerLock = "requestPointerLock" in div;
+ div.requestPointerLock();
+ });
+ host.requestFullscreen();
+ }
+ </script>
+ </pre>
+</body>
+</html>
diff --git a/dom/tests/mochitest/pointerlock/mochitest.ini b/dom/tests/mochitest/pointerlock/mochitest.ini index 1d9e92cbcf..57f0101ea5 100644 --- a/dom/tests/mochitest/pointerlock/mochitest.ini +++ b/dom/tests/mochitest/pointerlock/mochitest.ini @@ -8,6 +8,7 @@ tags = fullscreen support-files = pointerlock_utils.js file_pointerlock-api.html + file_pointerlock-api-with-shadow.html file_pointerlockerror.html file_escapeKey.html file_withoutDOM.html diff --git a/dom/tests/mochitest/pointerlock/test_pointerlock-api.html b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html index 3094c565e7..cfc48063ac 100644 --- a/dom/tests/mochitest/pointerlock/test_pointerlock-api.html +++ b/dom/tests/mochitest/pointerlock/test_pointerlock-api.html @@ -33,7 +33,8 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602 ["full-screen-api.unprefix.enabled", true], ["full-screen-api.allow-trusted-requests-only", false], ["full-screen-api.transition-duration.enter", "0 0"], - ["full-screen-api.transition-duration.leave", "0 0"] + ["full-screen-api.transition-duration.leave", "0 0"], + ["dom.webcomponents.enabled", true] ]}, nextTest); // Run the tests which go full-screen in new window, as Mochitests @@ -49,6 +50,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=633602 "file_movementXY.html", "file_nestedFullScreen.html", "file_pointerlock-api.html", + "file_pointerlock-api-with-shadow.html", "file_pointerlockerror.html", "file_pointerLockPref.html", "file_removedFromDOM.html", diff --git a/dom/tests/mochitest/webcomponents/test_shadow_element.html b/dom/tests/mochitest/webcomponents/test_shadow_element.html deleted file mode 100644 index 4440650d7d..0000000000 --- a/dom/tests/mochitest/webcomponents/test_shadow_element.html +++ /dev/null @@ -1,173 +0,0 @@ -<!DOCTYPE HTML> -<html> -<!-- -https://bugzilla.mozilla.org/show_bug.cgi?id=887538 ---> -<head> - <title>Test for HTMLShadowElement</title> - <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> - <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> -</head> -<body> -<div id="grabme"></div> -<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=887538">Bug 887538</a> -<script> -var host = document.createElement("span"); - -// Create three shadow roots on a single host and make sure that shadow elements -// are associated with the correct shadow root. -var firstShadow = host.createShadowRoot(); -firstShadow.innerHTML = '<shadow id="shadowone"></shadow>'; -var secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<shadow id="shadowtwo"></shadow>'; -var thirdShadow = host.createShadowRoot(); -thirdShadow.innerHTML = '<shadow id="shadowthree"></shadow>'; - -is(firstShadow.getElementById("shadowone").olderShadowRoot, null, "Shadow element in oldest ShadowRoot should not be associated with a ShadowRoot."); -is(secondShadow.getElementById("shadowtwo").olderShadowRoot, firstShadow, "Shadow element should be associated with older ShadowRoot."); -is(thirdShadow.getElementById("shadowthree").olderShadowRoot, secondShadow, "Shadow element should be associated with older ShadowRoot."); - -// Only the first ShadowRoot in tree order is an insertion point. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<shadow id="shadowone"></shadow><shadow id="shadowtwo"></shadow>'; -var shadowElemOne = secondShadow.getElementById("shadowone"); -var shadowElemTwo = secondShadow.getElementById("shadowtwo"); - -is(shadowElemOne.olderShadowRoot, firstShadow, "First <shadow> in tree order should be an insertion point."); -is(shadowElemTwo.olderShadowRoot, null, "Second <shadow> in tree order should not be an insertion point."); - -// Remove the first <shadow> element and make sure the second <shadow> element becomes an insertion point. -secondShadow.removeChild(shadowElemOne); -is(shadowElemOne.olderShadowRoot, null, "<shadow> element not in a ShadowRoot is not an insertion point."); -is(shadowElemTwo.olderShadowRoot, firstShadow, "Second <shadow> element should become insertion point after first is removed."); - -// Insert a <shadow> element before the current shadow insertion point and make sure that it becomes an insertion point. -secondShadow.insertBefore(shadowElemOne, shadowElemTwo); -is(shadowElemOne.olderShadowRoot, firstShadow, "<shadow> element inserted as first in tree order should become an insertion point."); -is(shadowElemTwo.olderShadowRoot, null, "<shadow> element should no longer be an insertion point it another is inserted before."); - -// <shadow> element in fallback content is not an insertion point. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<content><shadow id="shadowone"></shadow></content><shadow id="shadowtwo"></shadow>'; -shadowElemOne = secondShadow.getElementById("shadowone"); -shadowElemTwo = secondShadow.getElementById("shadowtwo"); - -is(shadowElemOne.olderShadowRoot, null, "<shadow> element in fallback content is not an insertion point."); -is(shadowElemTwo.olderShadowRoot, null, "<shadow> element preceeded by another <shadow> element is not an insertion point."); - -// <shadow> element that is descendant of shadow element is not an insertion point. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<shadow><shadow id="shadowone"></shadow></shadow>'; -shadowElemOne = secondShadow.getElementById("shadowone"); -is(shadowElemOne.olderShadowRoot, null, "<shadow> element that is descendant of shadow element is not an insertion point."); - -// Check projection of <content> elements through <shadow> elements. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -secondShadow = host.createShadowRoot(); -firstShadow.innerHTML = '<content id="firstcontent"></content>'; -secondShadow.innerHTML = '<shadow><span id="one"></span><content id="secondcontent"></content><span id="four"></span></shadow>'; -host.innerHTML = '<span id="two"></span><span id="three"></span>'; -var firstContent = firstShadow.getElementById("firstcontent"); -var secondContent = secondShadow.getElementById("secondcontent"); -var firstDistNodes = firstContent.getDistributedNodes(); -var secondDistNodes = secondContent.getDistributedNodes(); - -is(secondDistNodes.length, 2, "There should be two distributed nodes from the host."); -ok(secondDistNodes[0].id == "two" && - secondDistNodes[1].id == "three", "Nodes projected from host should preserve order."); - -is(firstDistNodes.length, 4, "There should be four distributed nodes, two from the first shadow, two from the second shadow."); -ok(firstDistNodes[0].id == "one" && - firstDistNodes[1].id == "two" && - firstDistNodes[2].id == "three" && - firstDistNodes[3].id == "four", "Reprojection through shadow should preserve node order."); - -// Remove a node from the host and make sure that it is removed from all insertion points. -host.removeChild(host.firstChild); -firstDistNodes = firstContent.getDistributedNodes(); -secondDistNodes = secondContent.getDistributedNodes(); - -is(secondDistNodes.length, 1, "There should be one distriubted node remaining after removing node from host."); -ok(secondDistNodes[0].id == "three", "Span with id=two should have been removed from content element."); -is(firstDistNodes.length, 3, "There should be three distributed nodes remaining after removing node from host."); -ok(firstDistNodes[0].id == "one" && - firstDistNodes[1].id == "three" && - firstDistNodes[2].id == "four", "Reprojection through shadow should preserve node order."); - -// Check projection of <shadow> elements to <content> elements. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<span id="firstspan"><shadow></shadow></span>'; -thirdShadow = secondShadow.getElementById("firstspan").createShadowRoot(); -thirdShadow.innerHTML = '<content id="firstcontent"></content>'; -firstContent = thirdShadow.getElementById("firstcontent"); -var shadowChild = document.createElement("span"); -firstShadow.appendChild(shadowChild); - -is(firstContent.getDistributedNodes()[0], shadowChild, "Elements in shadow insertioin point should be projected into content insertion points."); - -// Remove child of ShadowRoot and check that projected node is removed from insertion point. -firstShadow.removeChild(firstShadow.firstChild); - -is(firstContent.getDistributedNodes().length, 0, "Reprojected element was removed from ShadowRoot, thus it should be removed from content insertion point."); - -// Check deeply nested projection of <shadow> elements. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -firstShadow.innerHTML = '<content></content>'; -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<shadow><content></content></shadow>'; -thirdShadow = host.createShadowRoot(); -thirdShadow.innerHTML = '<span id="firstspan"><shadow><content></content></shadow></span>'; -var fourthShadow = thirdShadow.getElementById("firstspan").createShadowRoot(); -fourthShadow.innerHTML = '<content id="firstcontent"></content>'; -firstContent = fourthShadow.getElementById("firstcontent"); -host.innerHTML = '<span></span>'; - -is(firstContent.getDistributedNodes()[0], host.firstChild, "Child of host should be projected to insertion point."); - -// Remove node and make sure that it is removed from distributed nodes. -host.removeChild(host.firstChild); - -is(firstContent.getDistributedNodes().length, 0, "Node removed from host should be removed from insertion point."); - -// Check projection of fallback content through <shadow> elements. -host = document.createElement("span"); -firstShadow = host.createShadowRoot(); -firstShadow.innerHTML = '<content><span id="firstspan"></span></content>'; -secondShadow = host.createShadowRoot(); -secondShadow.innerHTML = '<span id="secondspan"><shadow id="firstshadow"></shadow></span>'; -firstShadowElem = secondShadow.getElementById("firstshadow"); -thirdShadow = secondShadow.getElementById("secondspan").createShadowRoot(); -thirdShadow.innerHTML = '<content id="firstcontent"></content>'; -firstContent = thirdShadow.getElementById("firstcontent"); - -is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content."); -is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed."); - -// Add some content to the host (causing the fallback content to be dropped) and make sure distribution nodes are updated. - -var newElem = document.createElement("div"); -firstShadowElem.appendChild(newElem); - -is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from the host."); -is(firstContent.getDistributedNodes()[0], newElem, "Distributed node should be from host, not fallback content."); - -// Remove the distribution node and check that fallback content is used. -firstShadowElem.removeChild(newElem); - -is(firstContent.getDistributedNodes().length, 1, "There should be one node distributed from fallback content."); -is(firstContent.getDistributedNodes()[0], firstShadow.getElementById("firstspan"), "Fallback content should be distributed after removing node from host."); - -</script> -</body> -</html> - diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html b/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html index ca525adad5..04c27753a5 100644 --- a/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html @@ -22,22 +22,7 @@ isnot(document.baseURI, "http://www.example.org/", "Base element should be inert SimpleTest.waitForExplicitFinish(); -// Check that <link> is inert. -var numStyleBeforeLoad = document.styleSheets.length; - -shadow.innerHTML = '<link id="shadowlink" rel="stylesheet" type="text/css" href="inert_style.css" /><span id="shadowspan"></span>'; -shadow.applyAuthorStyles = true; -var shadowSpan = shadow.getElementById("shadowspan"); -var shadowStyle = shadow.getElementById("shadowlink"); - -function runChecks() { - isnot(getComputedStyle(shadowSpan, null).getPropertyValue("padding-top"), "10px", "Link element should be inert."); - is(document.styleSheets.length, numStyleBeforeLoad, "Document style count should remain the same because the style should not be in the doucment."); - is(shadow.styleSheets.length, 0, "Inert link should not add style to ShadowRoot."); - // Remove link to make sure we don't get assertions. - shadow.removeChild(shadowStyle); - SimpleTest.finish(); -}; +SimpleTest.finish(); </script> </body> diff --git a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html index f310ff97c4..2f51b1ba9c 100644 --- a/dom/tests/mochitest/webcomponents/test_shadowroot_style.html +++ b/dom/tests/mochitest/webcomponents/test_shadowroot_style.html @@ -48,19 +48,12 @@ isnot(getComputedStyle(document.getElementById("bodydiv"), null).getPropertyValu // Make sure that elements in the ShadowRoot are styled according to the ShadowRoot style. is(getComputedStyle(divToStyle, null).getPropertyValue("height"), "100px", "ShadowRoot style sheets should apply to elements in ShadowRoot."); -// Tests for applyAuthorStyles. +// Tests for author styles not applying in a ShadowRoot. var authorStyle = document.createElement("style"); authorStyle.innerHTML = ".fat { padding-right: 20px; padding-left: 30px; }"; document.body.appendChild(authorStyle); -is(root.applyAuthorStyles, false, "applyAuthorStyles defaults to false."); -isnot(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false."); -root.applyAuthorStyles = true; -is(root.applyAuthorStyles, true, "applyAuthorStyles was set to true."); -is(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should apply to ShadowRoot when ShadowRoot.applyAuthorStyles is true."); -root.applyAuthorStyles = false; -is(root.applyAuthorStyles, false, "applyAuthorStyles was set to false."); -isnot(getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot when ShadowRoot.applyAuthorStyles is false."); +isnot(iframeWin.getComputedStyle(divToStyle, null).getPropertyValue("padding-right"), "20px", "Author styles should not apply to ShadowRoot."); // Test dynamic changes to style in ShadowRoot. root.innerHTML = '<div id="divtostyle" class="dummy"></div>'; diff --git a/dom/webidl/Document.webidl b/dom/webidl/Document.webidl index 2ade9a2c9b..4cd1c0c269 100644 --- a/dom/webidl/Document.webidl +++ b/dom/webidl/Document.webidl @@ -238,10 +238,6 @@ partial interface Document { readonly attribute boolean fullscreenEnabled; [BinaryName="fullscreenEnabled"] readonly attribute boolean mozFullScreenEnabled; - [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"] - readonly attribute Element? fullscreenElement; - [BinaryName="fullscreenElement"] - readonly attribute Element? mozFullScreenElement; [Func="nsDocument::IsUnprefixedFullscreenEnabled"] void exitFullscreen(); @@ -258,7 +254,6 @@ partial interface Document { // https://w3c.github.io/pointerlock/#extensions-to-the-document-interface // https://w3c.github.io/pointerlock/#extensions-to-the-documentorshadowroot-mixin partial interface Document { - readonly attribute Element? pointerLockElement; [BinaryName="pointerLockElement", Pref="pointer-lock-api.prefixed.enabled"] readonly attribute Element? mozPointerLockElement; void exitPointerLock(); @@ -292,8 +287,6 @@ partial interface Document { // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-document-interface partial interface Document { - Element? elementFromPoint (float x, float y); - sequence<Element> elementsFromPoint (float x, float y); CaretPosition? caretPositionFromPoint (float x, float y); readonly attribute Element? scrollingElement; diff --git a/dom/webidl/DocumentOrShadowRoot.webidl b/dom/webidl/DocumentOrShadowRoot.webidl index 16388d1264..f42f005c82 100644 --- a/dom/webidl/DocumentOrShadowRoot.webidl +++ b/dom/webidl/DocumentOrShadowRoot.webidl @@ -12,18 +12,17 @@ interface DocumentOrShadowRoot { // Not implemented yet: bug 1430308. // Selection? getSelection(); - // Not implemented yet: bug 1430301. - // Element? elementFromPoint (float x, float y); - // Not implemented yet: bug 1430301. - // sequence<Element> elementsFromPoint (float x, float y); + Element? elementFromPoint (float x, float y); + sequence<Element> elementsFromPoint (float x, float y); // Not implemented yet: bug 1430307. // CaretPosition? caretPositionFromPoint (float x, float y); readonly attribute Element? activeElement; readonly attribute StyleSheetList styleSheets; - // Not implemented yet: bug 1430303. - // readonly attribute Element? pointerLockElement; - // Not implemented yet: bug 1430305. - // readonly attribute Element? fullscreenElement; + readonly attribute Element? pointerLockElement; + [LenientSetter, Func="nsDocument::IsUnprefixedFullscreenEnabled"] + readonly attribute Element? fullscreenElement; + [BinaryName="fullscreenElement"] + readonly attribute Element? mozFullScreenElement; }; diff --git a/dom/webidl/Element.webidl b/dom/webidl/Element.webidl index d5cac7c647..57f37aade0 100644 --- a/dom/webidl/Element.webidl +++ b/dom/webidl/Element.webidl @@ -244,10 +244,6 @@ partial interface Element { readonly attribute HTMLSlotElement? assignedSlot; [CEReactions, Unscopable, SetterThrows, Func="nsDocument::IsWebComponentsEnabled"] attribute DOMString slot; - - // [deprecated] Shadow DOM v0 - [Throws, Func="nsDocument::IsWebComponentsEnabled"] - ShadowRoot createShadowRoot(); }; Element implements ChildNode; diff --git a/dom/webidl/ShadowRoot.webidl b/dom/webidl/ShadowRoot.webidl index 83acd4161d..388f8e1501 100644 --- a/dom/webidl/ShadowRoot.webidl +++ b/dom/webidl/ShadowRoot.webidl @@ -31,6 +31,5 @@ interface ShadowRoot : DocumentFragment HTMLCollection getElementsByClassName(DOMString classNames); [CEReactions, SetterThrows, TreatNullAs=EmptyString] attribute DOMString innerHTML; - attribute boolean applyAuthorStyles; }; diff --git a/dom/xbl/nsXBLBinding.cpp b/dom/xbl/nsXBLBinding.cpp index f0b4ff5666..faa4dce852 100644 --- a/dom/xbl/nsXBLBinding.cpp +++ b/dom/xbl/nsXBLBinding.cpp @@ -313,10 +313,11 @@ nsXBLBinding::GenerateAnonymousContent() if (hasContent) { nsIDocument* doc = mBoundElement->OwnerDoc(); - nsCOMPtr<nsINode> clonedNode; - nsCOMArray<nsINode> nodesWithProperties; - nsNodeUtils::Clone(content, true, doc->NodeInfoManager(), - nodesWithProperties, getter_AddRefs(clonedNode)); + IgnoredErrorResult rv; + nsCOMPtr<nsINode> clonedNode = + nsNodeUtils::Clone(content, true, doc->NodeInfoManager(), nullptr, rv); + // FIXME: Bug 1399558, Why is this code OK assuming that nsNodeUtils::Clone + // never fails? mContent = clonedNode->AsElement(); // Search for <xbl:children> elements in the XBL content. In the presence diff --git a/dom/xml/XMLDocument.cpp b/dom/xml/XMLDocument.cpp index ad11f5de83..cbcfdb2e6e 100644 --- a/dom/xml/XMLDocument.cpp +++ b/dom/xml/XMLDocument.cpp @@ -155,6 +155,8 @@ NS_NewDOMDocument(nsIDOMDocument** aInstancePtrResult, if (aDoctype) { nsCOMPtr<nsIDOMNode> tmpNode; rv = doc->AppendChild(aDoctype, getter_AddRefs(tmpNode)); + // TODO: if we choose to land bug 1318479, make sure to call + // result.WouldReportJSException() before stealing the NSResult. NS_ENSURE_SUCCESS(rv, rv); } @@ -167,6 +169,8 @@ NS_NewDOMDocument(nsIDOMDocument** aInstancePtrResult, nsCOMPtr<nsIDOMNode> tmpNode; rv = doc->AppendChild(root, getter_AddRefs(tmpNode)); + // TODO: if we choose to land bug 1318479, make sure to call + // result.WouldReportJSException() before stealing the NSResult. NS_ENSURE_SUCCESS(rv, rv); } diff --git a/editor/composer/nsEditorSpellCheck.cpp b/editor/composer/nsEditorSpellCheck.cpp index 1413653ac9..a3e41bd160 100644 --- a/editor/composer/nsEditorSpellCheck.cpp +++ b/editor/composer/nsEditorSpellCheck.cpp @@ -682,7 +682,7 @@ nsEditorSpellCheck::UpdateCurrentDictionary(nsIEditorSpellCheckCallback* aCallba RefPtr<DictionaryFetcher> fetcher = new DictionaryFetcher(this, aCallback, mDictionaryFetcherGroup); rootContent->GetLang(fetcher->mRootContentLang); - nsCOMPtr<nsIDocument> doc = rootContent->GetUncomposedDoc(); + nsCOMPtr<nsIDocument> doc = rootContent->GetComposedDoc(); NS_ENSURE_STATE(doc); doc->GetContentLanguage(fetcher->mRootDocContentLang); diff --git a/editor/libeditor/EditorEventListener.cpp b/editor/libeditor/EditorEventListener.cpp index 32f3585288..d809d8d8cd 100644 --- a/editor/libeditor/EditorEventListener.cpp +++ b/editor/libeditor/EditorEventListener.cpp @@ -1114,7 +1114,7 @@ EditorEventListener::Focus(WidgetEvent* aFocusEvent) return NS_OK; } - nsCOMPtr<nsIDOMEventTarget> target = aFocusEvent->GetDOMEventTarget(); + nsCOMPtr<nsIDOMEventTarget> target = aFocusEvent->GetOriginalDOMEventTarget(); nsCOMPtr<nsINode> node = do_QueryInterface(target); NS_ENSURE_TRUE(node, NS_ERROR_UNEXPECTED); @@ -1126,13 +1126,15 @@ EditorEventListener::Focus(WidgetEvent* aFocusEvent) } if (node->IsNodeOfType(nsINode::eCONTENT)) { + nsIContent* content = + node->AsContent()->FindFirstNonChromeOnlyAccessContent(); // XXX If the focus event target is a form control in contenteditable // element, perhaps, the parent HTML editor should do nothing by this // handler. However, FindSelectionRoot() returns the root element of the // contenteditable editor. So, the editableRoot value is invalid for // the plain text editor, and it will be set to the wrong limiter of // the selection. However, fortunately, actual bugs are not found yet. - nsCOMPtr<nsIContent> editableRoot = editorBase->FindSelectionRoot(node); + nsCOMPtr<nsIContent> editableRoot = editorBase->FindSelectionRoot(content); // make sure that the element is really focused in case an earlier // listener in the chain changed the focus. diff --git a/editor/libeditor/HTMLEditRules.cpp b/editor/libeditor/HTMLEditRules.cpp index ac1c4ce6ff..a5b284d82f 100644 --- a/editor/libeditor/HTMLEditRules.cpp +++ b/editor/libeditor/HTMLEditRules.cpp @@ -7986,16 +7986,20 @@ HTMLEditRules::ConfirmSelectionInBody() { // get the body NS_ENSURE_STATE(mHTMLEditor); - nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mHTMLEditor->GetRoot()); - NS_ENSURE_TRUE(rootElement, NS_ERROR_UNEXPECTED); + RefPtr<Element> rootElement = mHTMLEditor->GetRoot(); + if (NS_WARN_IF(!rootElement)) { + return NS_ERROR_UNEXPECTED; + } // get the selection NS_ENSURE_STATE(mHTMLEditor); RefPtr<Selection> selection = mHTMLEditor->GetSelection(); - NS_ENSURE_STATE(selection); + if (NS_WARN_IF(!selection)) { + return NS_ERROR_UNEXPECTED; + } // get the selection start location - nsCOMPtr<nsIDOMNode> selNode, temp, parent; + nsCOMPtr<nsINode> selNode; int32_t selOffset; NS_ENSURE_STATE(mHTMLEditor); nsresult rv = @@ -8005,12 +8009,11 @@ HTMLEditRules::ConfirmSelectionInBody() return rv; } - temp = selNode; + nsINode* temp = selNode; // check that selNode is inside body - while (temp && !TextEditUtils::IsBody(temp)) { - temp->GetParentNode(getter_AddRefs(parent)); - temp = parent; + while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { + temp = temp->GetParentOrHostNode(); } // if we aren't in the body, force the issue @@ -8018,6 +8021,7 @@ HTMLEditRules::ConfirmSelectionInBody() // uncomment this to see when we get bad selections // NS_NOTREACHED("selection not in body"); selection->Collapse(rootElement, 0); + return NS_OK; } // get the selection end location @@ -8028,9 +8032,8 @@ HTMLEditRules::ConfirmSelectionInBody() temp = selNode; // check that selNode is inside body - while (temp && !TextEditUtils::IsBody(temp)) { - rv = temp->GetParentNode(getter_AddRefs(parent)); - temp = parent; + while (temp && !temp->IsHTMLElement(nsGkAtoms::body)) { + temp = temp->GetParentOrHostNode(); } // if we aren't in the body, force the issue @@ -8040,9 +8043,7 @@ HTMLEditRules::ConfirmSelectionInBody() selection->Collapse(rootElement, 0); } - // XXX This is the result of the last call of GetParentNode(), it doesn't - // make sense... - return rv; + return NS_OK; } nsresult diff --git a/editor/libeditor/HTMLEditor.cpp b/editor/libeditor/HTMLEditor.cpp index 130b033bd1..2defc3e12c 100644 --- a/editor/libeditor/HTMLEditor.cpp +++ b/editor/libeditor/HTMLEditor.cpp @@ -381,13 +381,14 @@ HTMLEditor::FindSelectionRoot(nsINode* aNode) aNode->IsNodeOfType(nsINode::eCONTENT), "aNode must be content or document node"); - nsCOMPtr<nsIDocument> doc = aNode->GetUncomposedDoc(); + nsCOMPtr<nsIDocument> doc = aNode->GetComposedDoc(); if (!doc) { return nullptr; } nsCOMPtr<nsIContent> content; - if (doc->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent()) { + if (aNode->IsInUncomposedDoc() && + doc->HasFlag(NODE_IS_EDITABLE) || !aNode->IsContent()) { content = doc->GetRootElement(); return content.forget(); } @@ -5135,7 +5136,11 @@ HTMLEditor::IsAcceptableInputEvent(WidgetGUIEvent* aGUIEvent) return true; } - nsCOMPtr<nsIDOMEventTarget> target = aGUIEvent->GetDOMEventTarget(); + nsCOMPtr<nsIDOMEventTarget> target = aGUIEvent->GetOriginalDOMEventTarget(); + nsCOMPtr<nsIContent> content = do_QueryInterface(target); + if (content) { + target = content->FindFirstNonChromeOnlyAccessContent(); + } NS_ENSURE_TRUE(target, false); nsCOMPtr<nsIDocument> document = GetDocument(); diff --git a/extensions/spellcheck/src/mozInlineSpellChecker.cpp b/extensions/spellcheck/src/mozInlineSpellChecker.cpp index 37898c818e..398059fc9b 100644 --- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp +++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp @@ -1483,8 +1483,9 @@ nsresult mozInlineSpellChecker::DoSpellCheck(mozInlineSpellWordUtil& aWordUtil, // Now check that we're still looking at a range that's under // aWordUtil.GetRootNode() nsINode* rootNode = aWordUtil.GetRootNode(); - if (!nsContentUtils::ContentIsDescendantOf(beginNode, rootNode) || - !nsContentUtils::ContentIsDescendantOf(endNode, rootNode)) { + if (!beginNode->IsInComposedDoc() || !endNode->IsInComposedDoc() || + !nsContentUtils::ContentIsShadowIncludingDescendantOf(beginNode, rootNode) || + !nsContentUtils::ContentIsShadowIncludingDescendantOf(endNode, rootNode)) { // Just bail out and don't try to spell-check this return NS_OK; } diff --git a/layout/base/crashtests/1261351-iframe.html b/layout/base/crashtests/1261351-iframe.html index a0484f3325..3730e83fa4 100644 --- a/layout/base/crashtests/1261351-iframe.html +++ b/layout/base/crashtests/1261351-iframe.html @@ -10,7 +10,7 @@ } connectedCallback() { - let shadow = this.createShadowRoot(); + let shadow = this.attachShadow({ mode: "open" }); if (this.template) { let te = document.createElement('template'); te.innerHTML = this.template; diff --git a/layout/base/crashtests/1404789-1.html b/layout/base/crashtests/1404789-1.html deleted file mode 100644 index bd577ae24d..0000000000 --- a/layout/base/crashtests/1404789-1.html +++ /dev/null @@ -1,16 +0,0 @@ -<!doctype html> -<script> - // Test for content redistribution outside of the document. - // Passes if it doesn't assert. - let host = document.createElement('div'); - host.innerHTML = "<div id='foo'></div>"; - - let shadowRoot = host.createShadowRoot(); - shadowRoot.innerHTML = "<content select='#foo'></content>"; - - host.firstElementChild.removeAttribute('id'); - - // Move to the document, do the same. - document.documentElement.appendChild(host); - host.firstElementChild.setAttribute('id', 'foo'); -</script> diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list index c0b0085493..dfd77497e5 100644 --- a/layout/base/crashtests/crashtests.list +++ b/layout/base/crashtests/crashtests.list @@ -484,6 +484,5 @@ load 1308793.svg load 1308848-1.html load 1308848-2.html asserts(0-1) load 1343606.html # bug 1343948 -load 1404789-1.html load 1404789-2.html load 1419762.html diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 1545e1d331..8293e5198c 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -788,21 +788,23 @@ public: nsCOMArray<nsIContent> mGeneratedTextNodesWithInitializer; - TreeMatchContext mTreeMatchContext; + TreeMatchContext& mTreeMatchContext; // Constructor // Use the passed-in history state. nsFrameConstructorState( nsIPresShell* aPresShell, + TreeMatchContext& aTreeMatchContext, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock, already_AddRefed<nsILayoutHistoryState> aHistoryState); // Get the history state from the pres context's pres shell. - nsFrameConstructorState(nsIPresShell* aPresShell, - nsContainerFrame* aFixedContainingBlock, - nsContainerFrame* aAbsoluteContainingBlock, - nsContainerFrame* aFloatContainingBlock); + nsFrameConstructorState(nsIPresShell* aPresShell, + TreeMatchContext& aTreeMatchContext, + nsContainerFrame* aFixedContainingBlock, + nsContainerFrame* aAbsoluteContainingBlock, + nsContainerFrame* aFloatContainingBlock); ~nsFrameConstructorState(); @@ -955,6 +957,7 @@ protected: nsFrameConstructorState::nsFrameConstructorState( nsIPresShell* aPresShell, + TreeMatchContext& aTreeMatchContext, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock, @@ -979,8 +982,7 @@ nsFrameConstructorState::nsFrameConstructorState( mFixedPosIsAbsPos(aFixedContainingBlock == aAbsoluteContainingBlock), mHavePendingPopupgroup(false), mCreatingExtraFrames(false), - mTreeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited, - aPresShell->GetDocument()), + mTreeMatchContext(aTreeMatchContext), mCurrentPendingBindingInsertionPoint(nullptr) { nsIRootBox* rootBox = nsIRootBox::GetRootBox(aPresShell); @@ -991,10 +993,13 @@ nsFrameConstructorState::nsFrameConstructorState( } nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell, + TreeMatchContext& aTreeMatchContext, nsContainerFrame* aFixedContainingBlock, nsContainerFrame* aAbsoluteContainingBlock, nsContainerFrame* aFloatContainingBlock) - : nsFrameConstructorState(aPresShell, aFixedContainingBlock, + : nsFrameConstructorState(aPresShell, + aTreeMatchContext, + aFixedContainingBlock, aAbsoluteContainingBlock, aFloatContainingBlock, aPresShell->GetDocument()->GetLayoutHistoryState()) @@ -2396,13 +2401,15 @@ nsCSSFrameConstructor::ConstructDocElementFrame(Element* aDocEle NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now"); + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); + // Initialize the ancestor filter with null for now; we'll push + // aDocElement once we finish resolving style for it. + matchContext.InitAncestors(nullptr); nsFrameConstructorState state(mPresShell, + matchContext, GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS), nullptr, nullptr, do_AddRef(aFrameState)); - // Initialize the ancestor filter with null for now; we'll push - // aDocElement once we finish resolving style for it. - state.mTreeMatchContext.InitAncestors(nullptr); // XXXbz why, exactly? if (!mTempFrameTreeState) @@ -2831,7 +2838,8 @@ nsCSSFrameConstructor::SetUpDocElementContainingBlock(nsIContent* aDocElement) RefPtr<nsStyleContext> rootPseudoStyle; // we must create a state because if the scrollbars are GFX it needs the // state to build the scrollbar frames. - nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr); + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); + nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr); // Start off with the viewport as parent; we'll adjust it as needed. nsContainerFrame* parentFrame = viewportFrame; @@ -6978,12 +6986,16 @@ nsCSSFrameConstructor::MaybeConstructLazily(Operation aOperation, } void -nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent) +nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent, + TreeMatchContext& aTreeMatchContext) { NS_ASSERTION(!aContent->HasFlag(NODE_NEEDS_FRAME), "shouldn't get here with a content node that has needs frame bit set"); NS_ASSERTION(aContent->HasFlag(NODE_DESCENDANTS_NEED_FRAMES), "should only get here with a content node that has descendants needing frames"); + NS_ASSERTION(aTreeMatchContext.mAncestorFilter.HasFilter(), + "The whole point of having the tree match context is optimizing " + "the ancestor filter usage!"); aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES); @@ -7019,19 +7031,31 @@ nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent) inRun = false; // generate a ContentRangeInserted for [startOfRun,i) ContentRangeInserted(aContent, firstChildInRun, child, nullptr, - false); + false, &aTreeMatchContext); } } } if (inRun) { - ContentAppended(aContent, firstChildInRun, false); + ContentAppended(aContent, firstChildInRun, false, &aTreeMatchContext); } // Now descend. FlattenedChildIterator iter(aContent); for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) { if (child->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) { - CreateNeededFrames(child); + TreeMatchContext::AutoAncestorPusher insertionPointPusher( + aTreeMatchContext); + + // Handle stuff like xbl:children. + if (child->GetParent() != aContent && child->GetParent()->IsElement()) { + insertionPointPusher.PushAncestorAndStyleScope( + child->GetParent()->AsElement()); + } + + TreeMatchContext::AutoAncestorPusher pusher(aTreeMatchContext); + pusher.PushAncestorAndStyleScope(child); + + CreateNeededFrames(child, aTreeMatchContext); } } } @@ -7046,7 +7070,9 @@ void nsCSSFrameConstructor::CreateNeededFrames() "root element should not have frame created lazily"); if (rootElement && rootElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) { BeginUpdate(); - CreateNeededFrames(rootElement); + TreeMatchContext treeMatchContext(mDocument, TreeMatchContext::ForFrameConstruction); + treeMatchContext.InitAncestors(rootElement); + CreateNeededFrames(rootElement, treeMatchContext); EndUpdate(); } } @@ -7160,10 +7186,13 @@ nsCSSFrameConstructor::MaybeRecreateForFrameset(nsIFrame* aParentFrame, } void -nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer, - nsIContent* aFirstNewContent, - bool aAllowLazyConstruction) +nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer, + nsIContent* aFirstNewContent, + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext) { + MOZ_ASSERT_IF(aProvidedTreeMatchContext, !aAllowLazyConstruction); + AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); NS_PRECONDITION(mUpdateCount != 0, "Should be in an update while creating frames"); @@ -7341,13 +7370,23 @@ nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer, } // Create some new frames + // + // We use the provided tree match context, or create a new one on the fly + // otherwise. + Maybe<TreeMatchContext> matchContext; + if (!aProvidedTreeMatchContext) { + matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction); + // We use GetParentElementCrossingShadowRoot to handle the case where + // aContainer is a ShadowRoot. + matchContext->InitAncestors(aFirstNewContent->GetParentElementCrossingShadowRoot()); + } nsFrameConstructorState state(mPresShell, + aProvidedTreeMatchContext + ? *aProvidedTreeMatchContext + : *matchContext, GetAbsoluteContainingBlock(parentFrame, FIXED_POS), GetAbsoluteContainingBlock(parentFrame, ABS_POS), containingBlock); - // We use GetParentElementCrossingShadowRoot to handle the case where - // aContainer is a ShadowRoot. - state.mTreeMatchContext.InitAncestors(aFirstNewContent->GetParentElementCrossingShadowRoot()); nsIAtom* frameType = parentFrame->GetType(); @@ -7553,7 +7592,8 @@ nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer, nsIContent* aStartChild, nsIContent* aEndChild, nsILayoutHistoryState* aFrameState, - bool aAllowLazyConstruction) + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext) { AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC); NS_PRECONDITION(mUpdateCount != 0, @@ -7784,14 +7824,21 @@ nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer, return; } + Maybe<TreeMatchContext> matchContext; + if (!aProvidedTreeMatchContext) { + matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction); + // We use GetParentElementCrossingShadowRoot to handle the case where + // aContainer is a ShadowRoot. + matchContext->InitAncestors(aStartChild->GetParentElementCrossingShadowRoot()); + } nsFrameConstructorState state(mPresShell, + aProvidedTreeMatchContext + ? *aProvidedTreeMatchContext + : *matchContext, GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS), GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS), GetFloatContainingBlock(insertion.mParentFrame), do_AddRef(aFrameState)); - // We use GetParentElementCrossingShadowRoot to handle the case where - // aContainer is a ShadowRoot. - state.mTreeMatchContext.InitAncestors(aStartChild->GetParentElementCrossingShadowRoot()); // Recover state for the containing block - we need to know if // it has :first-letter or :first-line style applied to it. The @@ -8637,7 +8684,9 @@ nsCSSFrameConstructor::CreateContinuingTableFrame(nsIPresShell* aPresShell, // Replicate the header/footer frame. nsTableRowGroupFrame* headerFooterFrame; nsFrameItems childItems; + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); nsFrameConstructorState state(mPresShell, + matchContext, GetAbsoluteContainingBlock(newFrame, FIXED_POS), GetAbsoluteContainingBlock(newFrame, ABS_POS), nullptr); @@ -8909,7 +8958,10 @@ nsCSSFrameConstructor::ReplicateFixedFrames(nsPageContentFrame* aParentFrame) // This should not normally be possible (because fixed-pos elements should // be absolute containers) but fixed-pos tables currently aren't abs-pos // containers. - nsFrameConstructorState state(mPresShell, aParentFrame, + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); + nsFrameConstructorState state(mPresShell, + matchContext, + aParentFrame, nullptr, mRootElementFrame); state.mCreatingExtraFrames = true; @@ -11047,7 +11099,10 @@ nsCSSFrameConstructor::CreateLetterFrame(nsContainerFrame* aBlockFrame, NS_ASSERTION(aBlockContinuation == GetFloatContainingBlock(aParentFrame), "Containing block is confused"); + TreeMatchContext matchContext(mDocument, + TreeMatchContext::ForFrameConstruction); nsFrameConstructorState state(mPresShell, + matchContext, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS), GetAbsoluteContainingBlock(aParentFrame, ABS_POS), aBlockContinuation); @@ -11422,7 +11477,10 @@ nsCSSFrameConstructor::CreateListBoxContent(nsContainerFrame* aParentFrame, // Construct a new frame if (nullptr != aParentFrame) { nsFrameItems frameItems; - nsFrameConstructorState state(mPresShell, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS), + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); + nsFrameConstructorState state(mPresShell, + matchContext, + GetAbsoluteContainingBlock(aParentFrame, FIXED_POS), GetAbsoluteContainingBlock(aParentFrame, ABS_POS), GetFloatContainingBlock(aParentFrame), do_AddRef(mTempFrameTreeState.get())); @@ -12312,7 +12370,8 @@ nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame) BeginUpdate(); nsFrameItems childItems; - nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr); + TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction); + nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr); // We don't have a parent frame with a pending binding constructor here, // so no need to worry about ordering of the kids' constructors with it. // Pass null for the PendingBinding. diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index c4f94ceccd..c01d6ad7a0 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -87,7 +87,8 @@ public: void CreateNeededFrames(); private: - void CreateNeededFrames(nsIContent* aContent); + void CreateNeededFrames(nsIContent* aContent, + TreeMatchContext& aTreeMatchContext); enum Operation { CONTENTAPPEND, @@ -202,9 +203,15 @@ public: // If aAllowLazyConstruction is true then frame construction of the new // children can be done lazily. + // + // When constructing frames lazily, we can keep the tree match context in a + // much easier way than nsFrameConstructorState, and thus, we're allowed to + // provide a TreeMatchContext to avoid calling InitAncestors repeatedly deep + // in the DOM. void ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent, - bool aAllowLazyConstruction); + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext = nullptr); // If aAllowLazyConstruction is true then frame construction of the new child // can be done lazily. @@ -219,11 +226,15 @@ public: // aStartChild. If aAllowLazyConstruction is true then frame construction of // the new children can be done lazily. It is only allowed to be true when // inserting a single node. + // + // See ContentAppended to see why we allow passing an already initialized + // TreeMatchContext. void ContentRangeInserted(nsIContent* aContainer, nsIContent* aStartChild, nsIContent* aEndChild, nsILayoutHistoryState* aFrameState, - bool aAllowLazyConstruction); + bool aAllowLazyConstruction, + TreeMatchContext* aProvidedTreeMatchContext = nullptr); public: // FIXME(emilio): How important is it to keep the frame tree state around for diff --git a/layout/generic/nsSelection.cpp b/layout/generic/nsSelection.cpp index f8a231b00c..b9ee20d1fc 100644 --- a/layout/generic/nsSelection.cpp +++ b/layout/generic/nsSelection.cpp @@ -3679,10 +3679,11 @@ CompareToRangeStart(nsINode* aCompareNode, int32_t aCompareOffset, { nsINode* start = aRange->GetStartParent(); NS_ENSURE_STATE(aCompareNode && start); - // If the nodes that we're comparing are not in the same document, - // assume that aCompareNode will fall at the end of the ranges. + // If the nodes that we're comparing are not in the same document or in the + // same subtree, assume that aCompareNode will fall at the end of the ranges. if (aCompareNode->GetComposedDoc() != start->GetComposedDoc() || - !start->GetComposedDoc()) { + !start->GetComposedDoc() || + aCompareNode->SubtreeRoot() != start->SubtreeRoot()) { *aCmp = 1; } else { *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, @@ -3697,10 +3698,11 @@ CompareToRangeEnd(nsINode* aCompareNode, int32_t aCompareOffset, { nsINode* end = aRange->GetEndParent(); NS_ENSURE_STATE(aCompareNode && end); - // If the nodes that we're comparing are not in the same document, - // assume that aCompareNode will fall at the end of the ranges. + // If the nodes that we're comparing are not in the same document or in the + // same subtree, assume that aCompareNode will fall at the end of the ranges. if (aCompareNode->GetComposedDoc() != end->GetComposedDoc() || - !end->GetComposedDoc()) { + !end->GetComposedDoc() || + aCompareNode->SubtreeRoot() != end->SubtreeRoot()) { *aCmp = 1; } else { *aCmp = nsContentUtils::ComparePoints(aCompareNode, aCompareOffset, diff --git a/layout/reftests/bugs/1066554-1.html b/layout/reftests/bugs/1066554-1.html index f4df207a1b..c78d33f3b1 100644 --- a/layout/reftests/bugs/1066554-1.html +++ b/layout/reftests/bugs/1066554-1.html @@ -7,17 +7,15 @@ <script> function insertShadowSVG() { var x = document.getElementById("x"); - if (x.createShadowRoot) { - x.createShadowRoot(); - x.shadowRoot.innerHTML = - '<svg width="50px" height="10px"> \ - <switch> \ - <foreignObject width="50px" height="50px"> \ - <div style="width: 100px; height: 10px; background: red;"></div> \ - </foreignObject> \ - </switch> \ - </svg>'; - } + x.attachShadow({ mode: "open" }); + x.shadowRoot.innerHTML = + '<svg width="50px" height="10px"> \ + <switch> \ + <foreignObject width="50px" height="50px"> \ + <div style="width: 100px; height: 10px; background: red;"></div> \ + </foreignObject> \ + </switch> \ + </svg>'; document.documentElement.removeAttribute("class"); } window.addEventListener("MozReftestInvalidate", insertShadowSVG, false); diff --git a/layout/reftests/css-display/display-contents-shadow-dom-1.html b/layout/reftests/css-display/display-contents-shadow-dom-1.html deleted file mode 100644 index 6c0f297f9d..0000000000 --- a/layout/reftests/css-display/display-contents-shadow-dom-1.html +++ /dev/null @@ -1,239 +0,0 @@ -<!DOCTYPE html> -<!-- - Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ ---> -<html class="reftest-wait" lang="en-US"> - <head> - <title>CSS Test: CSS display:contents; in Shadow DOM</title> - <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=907396"> - <link rel="help" href="http://dev.w3.org/csswg/css-display"> -<style> -html,body { - color:black; background-color:white; font-size:16px; padding:0; margin:0; -} -.before::before {content: "a ";} -.after::after {content: " c";} -div.before::before {content: "X ";} -div.after::after {content: " Y";} -.b,.c { display:contents; } -</style> - </head> - <body> - <div id="host1" class="before"></div> - <span id="host2"></span> - <div id="host3" class="after"></div> - <div id="host4" class="before after"></div> - <div id="host5" class="after"></div> - <div id="host6" class="before"></div> - <div id="host7"></div> - <div id="host8" class="after"></div> - <div id="host9" class="before after"></div> - <div id="hostA" class="after"></div> - <div id="hostB"></div> - <div id="hostC"></div> - <div id="hostD"></div> - <div id="hostE"></div> - <div id="hostF" class="before"></div> - <div id="hostG"></div> - <span id="hostH"></span> - <div id="hostI"></div> - <div id="hostJ"></div> - <span id="hostK"></span> - <div id="hostL"></div> - <div id="hostM"><i>Two</i><b>One</b></div> - <div id="hostN"><i class="c">Two</i><b>One</b></div> - <div id="hostO"><i>Two</i><b class="c">One</b></div> - <div id="hostP"><i class="c">Two</i><b class="c">One</b></div> - <div id="hostQ" class="c" style="color:blue"><i class="c">Two</i><b class="c">One</b></div>Three - <span id="hostR"><style scoped>:scope{color:green}</style></span> - <div id="hostS" class="c"><span class="c">S</span></div> - <div id="hostT" class="c">T</div> - <div id="hostU"><span class="c">U</span></div> - <div id="hostV" class="c" style="color:red"><b class="c" style="color:inherit">V</b></div> - <!-- TODO(bug 1021572?) <div id="hostY" class="c" style="color:red"><b>Y</b></div> --> - - <script> - function shadow(id) { - return document.getElementById(id).createShadowRoot(); - } - function span(s) { - var e = document.createElement("span"); - var t = document.createTextNode(s); - e.appendChild(t); - return e; - } - function text(s) { - return document.createTextNode(s); - } - function contents(n) { - var e = document.createElement("z"); - e.style.display = "contents"; - e.style.color = "blue"; - if (n) e.appendChild(n); - return e; - } - - function run() { - document.body.offsetHeight; - - shadow("host1").innerHTML = '<content></content> c'; - shadow("host2").innerHTML = 'a <content style="display:contents"></content> c'; - shadow("host3").innerHTML = 'a <content style="display:contents"></content>'; - shadow("host4").innerHTML = '<content style="display:contents"></content>'; - shadow("host5").innerHTML = 'a <content style="display:contents"></content>'; - shadow("host6").innerHTML = '<z style="color:blue; display:contents"><content></content></z> c'; - shadow("host7").innerHTML = 'a <content style="display:contents"></content> c'; - shadow("host8").innerHTML = 'a <z style="color:blue; display:contents"><content style="display:contents"></z></content>'; - shadow("host9").innerHTML = '<content style="display:contents"></content>'; - shadow("hostA").innerHTML = 'a <content style="display:contents"></content>'; - shadow("hostB").innerHTML = 'a <content select=".c"></content> <content select=".b"></content> B'; - shadow("hostC").innerHTML = 'A <content select=".c"></content> <content select=".b"></content> B'; - shadow("hostD").innerHTML = 'A <content select=".c"></content> <content select=".b"></content> B <content select=".b"></content>'; - shadow("hostE").innerHTML = 'A <content select=".c"></content> <content select=".b"></content> B'; - shadow("hostF").innerHTML = '<content select=".c"></content> <content select=".b"></content> B'; - shadow("hostG").innerHTML = '<content select=".b"></content>'; - shadow("hostH").innerHTML = '<content select=".b"></content>'; - shadow("hostI").innerHTML = 'A<content select=".b"></content>'; - shadow("hostJ").innerHTML = 'A<content select=".b"></content>'; - shadow("hostK").innerHTML = '<content select=".b"></content>'; - shadow("hostL").innerHTML = '<content select=".b"></content>'; - shadow("hostM").innerHTML = '<content select="b"></content><content select="i"></content>'; - shadow("hostN").innerHTML = '<content select="b"></content><content select="i"></content>'; - shadow("hostO").innerHTML = '<content select="b"></content><content select="i"></content>'; - shadow("hostP").innerHTML = '<content select="b"></content><content select="i"></content>'; - shadow("hostQ").innerHTML = '<content select="b"></content><content select="i"></content>'; - shadow("hostR").innerHTML = '<content select="span"></content>'; - shadow("hostW").innerHTML = '<z style="color:red"><content select="b"></content></z>'; - shadow("hostX").innerHTML = '<z style="color:red"><content select="b"></content></z>'; - // TODO(bug 1021572?) shadow("hostY").innerHTML = '<content select="b"><style scoped>:scope{color:green}</style></content>'; - } - - function tweak() { - document.body.offsetHeight; - - host1.appendChild(span("1")); - host2.appendChild(text("2")); - host3.appendChild(span("3")); - host4.appendChild(text("4")); - - var e = span("5"); - e.style.display = "contents"; - host5.appendChild(e); - - host6.appendChild(span("6")); - host7.appendChild(contents(text("7"))); - host8.appendChild(contents(span("8"))); - host9.appendChild(contents(text("9"))); - - var e = contents(span("A")); - e.style.display = "contents"; - hostA.appendChild(e); - - var e = contents(text("2")); - e.className = "b"; - hostB.appendChild(e); - var e = contents(text("1")); - e.className = "c"; - hostB.appendChild(e); - - var e = contents(text("2")); - e.className = "b after"; - hostC.appendChild(e); - var e = contents(text("1")); - e.className = "c before"; - hostC.appendChild(e); - - var e = contents(text("2")); - e.className = "b before after"; - hostD.appendChild(e); - var e = contents(text(" 3")); - e.className = "b before after"; - hostD.appendChild(e); - var e = contents(text("1")); - e.className = "c before"; - hostD.appendChild(e); - - var e = contents(contents(text("2"))); - e.className = "before b after"; - hostE.appendChild(e); - var e2 = contents(text("1")); - e2.className = "c before after"; - hostE.insertBefore(e2, e); - - var e = contents(text("2")); - e.className = "before b after"; - hostF.appendChild(e); - var e2 = contents(text("1")); - e2.className = "c before after"; - hostF.insertBefore(e2, e); - - var e = contents(contents(text("1"))); - e.className = "b"; - hostG.appendChild(e); - var e = contents(text("2")); - e.className = "b before after"; - hostG.appendChild(e); - - var e = contents(contents(text("2"))); - e.className = "b"; - hostH.appendChild(e); - var e2 = contents(text("1")); - e2.className = "b before after"; - hostH.insertBefore(e2, e); - - var e = contents(text("b")); - e.className = "b"; - hostI.appendChild(e); - var e = span("c"); - e.className = "b"; - hostI.appendChild(e); - - var e = contents(contents(text("b"))); - e.className = "b"; - hostJ.appendChild(e); - var e = span("c"); - e.className = "b"; - hostJ.appendChild(e); - - var inner = span("b"); - inner.className = "after"; - var e = contents(contents(inner)); - e.className = "b"; - hostK.appendChild(e); - var e = span("d"); - e.className = "b"; - hostK.appendChild(e); - - var inner = contents(null); - inner.className = "before"; - var e = contents(inner); - e.className = "b"; - hostL.appendChild(e); - var e = span("b"); - e.className = "b"; - hostL.appendChild(e); - - hostR.appendChild(span("R")); - - document.body.offsetHeight; - setTimeout(function() { - shadow("hostS"); - shadow("hostT"); - shadow("hostU"); - shadow("hostV").innerHTML = '<z style="color:green"><content select="b"></content></z>'; - - document.body.offsetHeight; - document.documentElement.removeAttribute("class"); - },0); - } - - if (document.body.createShadowRoot) { - run(); - window.addEventListener("MozReftestInvalidate", tweak, false); - } else { - document.documentElement.removeAttribute("class"); - } - </script> - </body> -</html> diff --git a/layout/reftests/css-display/reftest.list b/layout/reftests/css-display/reftest.list index 933ea2d3b7..138bcc8713 100644 --- a/layout/reftests/css-display/reftest.list +++ b/layout/reftests/css-display/reftest.list @@ -17,7 +17,6 @@ pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hi pref(layout.css.display-contents.enabled,true) == display-contents-visibility-hidden-2.html display-contents-visibility-hidden-ref.html pref(layout.css.display-contents.enabled,true) == display-contents-495385-2d.html display-contents-495385-2d-ref.html pref(layout.css.display-contents.enabled,true) == display-contents-xbl.xhtml display-contents-xbl-ref.html -pref(layout.css.display-contents.enabled,true) pref(dom.webcomponents.enabled,true) == display-contents-shadow-dom-1.html display-contents-shadow-dom-1-ref.html pref(layout.css.display-contents.enabled,true) == display-contents-xbl-2.xul display-contents-xbl-2-ref.xul asserts(1) pref(layout.css.display-contents.enabled,true) == display-contents-xbl-3.xul display-contents-xbl-3-ref.xul # bug 1089223 skip pref(layout.css.display-contents.enabled,true) == display-contents-xbl-4.xul display-contents-xbl-4-ref.xul # fails (not just asserts) due to bug 1089223 diff --git a/layout/reftests/mathml/shadow-dom-1.html b/layout/reftests/mathml/shadow-dom-1.html index bbf27069fd..2022e3bcfd 100644 --- a/layout/reftests/mathml/shadow-dom-1.html +++ b/layout/reftests/mathml/shadow-dom-1.html @@ -7,11 +7,9 @@ <script> function insertShadowMathML() { var x = document.getElementById("x"); - if (x.createShadowRoot) { - x.createShadowRoot(); - x.shadowRoot.innerHTML = - '<math><msup><mi>X</mi><mi>X</mi></msup></math>'; - } + x.attachShadow({ mode: "open" }); + x.shadowRoot.innerHTML = + '<math><msup><mi>X</mi><mi>X</mi></msup></math>'; document.documentElement.removeAttribute("class"); } window.addEventListener("MozReftestInvalidate", insertShadowMathML, false); diff --git a/layout/reftests/webcomponents/cross-tree-selection-1.html b/layout/reftests/webcomponents/cross-tree-selection-1.html index 01e7317f23..84e564c181 100644 --- a/layout/reftests/webcomponents/cross-tree-selection-1.html +++ b/layout/reftests/webcomponents/cross-tree-selection-1.html @@ -3,13 +3,8 @@ <head> <script> function tweak() { - if (!document.body.createShadowRoot) { - document.documentElement.className = ""; - return; - } - var host = document.getElementById("host"); - var shadow = host.createShadowRoot(); + var shadow = host.attachShadow({ mode: "open" }); var textNode = document.createTextNode(" World"); shadow.appendChild(textNode); diff --git a/layout/style/Loader.cpp b/layout/style/Loader.cpp index 2103aaf351..1be703c027 100644 --- a/layout/style/Loader.cpp +++ b/layout/style/Loader.cpp @@ -1989,9 +1989,7 @@ Loader::LoadInlineStyle(nsIContent* aElement, PrepareSheet(sheet, aTitle, aMedia, nullptr, aScopeElement, *aIsAlternate, *aIsExplicitlyEnabled); if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) { - ShadowRoot* containingShadow = aElement->GetContainingShadow(); - MOZ_ASSERT(containingShadow); - containingShadow->InsertSheet(sheet, aElement); + aElement->GetContainingShadow()->InsertSheet(sheet, aElement); } else { rv = InsertSheetInDoc(sheet, aElement, mDocument); NS_ENSURE_SUCCESS(rv, rv); @@ -2085,8 +2083,12 @@ Loader::LoadStyleLink(nsIContent* aElement, PrepareSheet(sheet, aTitle, aMedia, nullptr, nullptr, *aIsAlternate, *aIsExplicitlyEnabled); - rv = InsertSheetInDoc(sheet, aElement, mDocument); - NS_ENSURE_SUCCESS(rv, rv); + if (aElement->HasFlag(NODE_IS_IN_SHADOW_TREE)) { + aElement->GetContainingShadow()->InsertSheet(sheet, aElement); + } else { + rv = InsertSheetInDoc(sheet, aElement, mDocument); + NS_ENSURE_SUCCESS(rv, rv); + } nsCOMPtr<nsIStyleSheetLinkingElement> owningElement(do_QueryInterface(aElement)); diff --git a/layout/style/crashtests/1017798-1.html b/layout/style/crashtests/1017798-1.html index 0460c8756f..fe7c5a333c 100644 --- a/layout/style/crashtests/1017798-1.html +++ b/layout/style/crashtests/1017798-1.html @@ -52,7 +52,7 @@ window.GaiaSwitch = (function(win) { // Extend from the HTMLElement prototype class GaiaSwitch extends HTMLElement { connectedCallback() { - var shadow = this.createShadowRoot(); + var shadow = this.attachShadow({ mode: "open" }); this._template = template.content.cloneNode(true); this._input = this._template.querySelector('input[type="checkbox"]'); @@ -104,7 +104,7 @@ window.GaiaSwitch = (function(win) { var template = document.createElement('template'); template.innerHTML = '<label id="switch-label" class="pack-switch">' + '<input type="checkbox">' + - '<span><content select="label"></content></span>' + + '<span><slot></slot></span>' + '</label>'; // Register and return the constructor diff --git a/layout/style/crashtests/1089463-1.html b/layout/style/crashtests/1089463-1.html index f7a2f59613..8efb3f6674 100644 --- a/layout/style/crashtests/1089463-1.html +++ b/layout/style/crashtests/1089463-1.html @@ -3,7 +3,7 @@ <script> window.onload = function() { var div = document.querySelector("div"); - var shadow = div.createShadowRoot(); + var shadow = div.attachShadow({ mode: "open" }); shadow.innerHTML = "<p style='display: none'><span><i>x</i></span></p>"; var p = shadow.lastChild; var span = p.firstChild; diff --git a/layout/style/nsRuleProcessorData.h b/layout/style/nsRuleProcessorData.h index f6d9771001..938c2f9d35 100644 --- a/layout/style/nsRuleProcessorData.h +++ b/layout/style/nsRuleProcessorData.h @@ -444,6 +444,15 @@ struct MOZ_STACK_CLASS TreeMatchContext { } } } + + enum ForFrameConstructionTag { ForFrameConstruction }; + + TreeMatchContext(nsIDocument* aDocument, + ForFrameConstructionTag) + : TreeMatchContext(true, + nsRuleWalker::eRelevantLinkUnvisited, + aDocument) + {} }; struct MOZ_STACK_CLASS RuleProcessorData { diff --git a/widget/BasicEvents.h b/widget/BasicEvents.h index 9631dfd257..14977bdc24 100644 --- a/widget/BasicEvents.h +++ b/widget/BasicEvents.h @@ -427,6 +427,7 @@ public: /// The possible related target nsCOMPtr<dom::EventTarget> mRelatedTarget; + nsCOMPtr<dom::EventTarget> mOriginalRelatedTarget; nsTArray<EventTargetChainItem>* mPath; @@ -448,6 +449,8 @@ public: mCurrentTarget = aCopyTargets ? aEvent.mCurrentTarget : nullptr; mOriginalTarget = aCopyTargets ? aEvent.mOriginalTarget : nullptr; mRelatedTarget = aCopyTargets ? aEvent.mRelatedTarget : nullptr; + mOriginalRelatedTarget = + aCopyTargets ? aEvent.mOriginalRelatedTarget : nullptr; } /** |