summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--devtools/client/inspector/markup/test/doc_markup_anonymous.html6
-rw-r--r--devtools/server/tests/mochitest/inspector-traversal-data.html4
-rw-r--r--dom/base/ChildIterator.cpp18
-rw-r--r--dom/base/ChildIterator.h67
-rw-r--r--dom/base/DocumentOrShadowRoot.cpp156
-rw-r--r--dom/base/DocumentOrShadowRoot.h26
-rw-r--r--dom/base/Element.cpp22
-rw-r--r--dom/base/Element.h6
-rw-r--r--dom/base/FragmentOrElement.cpp129
-rw-r--r--dom/base/ShadowRoot.cpp48
-rw-r--r--dom/base/ShadowRoot.h7
-rw-r--r--dom/base/crashtests/1024428-1.html3
-rw-r--r--dom/base/crashtests/1029710.html2
-rw-r--r--dom/base/crashtests/1118764.html33
-rw-r--r--dom/base/crashtests/1393806.html17
-rw-r--r--dom/base/crashtests/crashtests.list1
-rw-r--r--dom/base/nsContentUtils.cpp56
-rw-r--r--dom/base/nsContentUtils.h14
-rw-r--r--dom/base/nsDocument.cpp193
-rw-r--r--dom/base/nsDocument.h23
-rw-r--r--dom/base/nsFocusManager.cpp735
-rw-r--r--dom/base/nsFocusManager.h95
-rw-r--r--dom/base/nsIDocument.h38
-rw-r--r--dom/base/nsINode.cpp71
-rw-r--r--dom/base/nsINode.h6
-rw-r--r--dom/base/nsImageLoadingContent.cpp12
-rw-r--r--dom/base/nsNodeUtils.cpp121
-rw-r--r--dom/base/nsNodeUtils.h71
-rw-r--r--dom/base/nsStyleLinkElement.cpp24
-rw-r--r--dom/base/test/file_bug1453693.html903
-rw-r--r--dom/base/test/mochitest.ini5
-rw-r--r--dom/base/test/test_bug1453693.html32
-rw-r--r--dom/base/test/test_focus_shadow_dom_root.html36
-rw-r--r--dom/bindings/BindingUtils.cpp31
-rw-r--r--dom/bindings/BindingUtils.h4
-rw-r--r--dom/events/ContentEventHandler.cpp36
-rw-r--r--dom/events/ContentEventHandler.h4
-rwxr-xr-xdom/events/Event.cpp60
-rwxr-xr-xdom/events/Event.h11
-rw-r--r--dom/events/EventDispatcher.cpp81
-rw-r--r--dom/events/EventDispatcher.h46
-rw-r--r--dom/events/FocusEvent.cpp6
-rw-r--r--dom/events/FocusEvent.h2
-rw-r--r--dom/events/MouseEvent.cpp22
-rw-r--r--dom/html/HTMLImageElement.cpp5
-rw-r--r--dom/html/HTMLLinkElement.cpp17
-rw-r--r--dom/html/nsGenericHTMLElement.cpp23
-rw-r--r--dom/html/test/file_fullscreen-shadowdom.html52
-rw-r--r--dom/html/test/mochitest.ini2
-rw-r--r--dom/html/test/test_bug1323815.html50
-rw-r--r--dom/html/test/test_fullscreen-api.html4
-rw-r--r--dom/svg/SVGUseElement.cpp7
-rw-r--r--dom/tests/mochitest/pointerlock/file_pointerlock-api-with-shadow.html110
-rw-r--r--dom/tests/mochitest/pointerlock/mochitest.ini1
-rw-r--r--dom/tests/mochitest/pointerlock/test_pointerlock-api.html4
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadow_element.html173
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_inert_element.html17
-rw-r--r--dom/tests/mochitest/webcomponents/test_shadowroot_style.html11
-rw-r--r--dom/webidl/Document.webidl7
-rw-r--r--dom/webidl/DocumentOrShadowRoot.webidl15
-rw-r--r--dom/webidl/Element.webidl4
-rw-r--r--dom/webidl/ShadowRoot.webidl1
-rw-r--r--dom/xbl/nsXBLBinding.cpp9
-rw-r--r--dom/xml/XMLDocument.cpp4
-rw-r--r--editor/composer/nsEditorSpellCheck.cpp2
-rw-r--r--editor/libeditor/EditorEventListener.cpp6
-rw-r--r--editor/libeditor/HTMLEditRules.cpp29
-rw-r--r--editor/libeditor/HTMLEditor.cpp11
-rw-r--r--extensions/spellcheck/src/mozInlineSpellChecker.cpp5
-rw-r--r--layout/base/crashtests/1261351-iframe.html2
-rw-r--r--layout/base/crashtests/1404789-1.html16
-rw-r--r--layout/base/crashtests/crashtests.list1
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp119
-rw-r--r--layout/base/nsCSSFrameConstructor.h17
-rw-r--r--layout/generic/nsSelection.cpp14
-rw-r--r--layout/reftests/bugs/1066554-1.html20
-rw-r--r--layout/reftests/css-display/display-contents-shadow-dom-1.html239
-rw-r--r--layout/reftests/css-display/reftest.list1
-rw-r--r--layout/reftests/mathml/shadow-dom-1.html8
-rw-r--r--layout/reftests/webcomponents/cross-tree-selection-1.html7
-rw-r--r--layout/style/Loader.cpp12
-rw-r--r--layout/style/crashtests/1017798-1.html4
-rw-r--r--layout/style/crashtests/1089463-1.html2
-rw-r--r--layout/style/nsRuleProcessorData.h9
-rw-r--r--widget/BasicEvents.h3
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;
}
/**