Bug 1513658 - Implement DocumentOrShadowRoot.node(s)FromPoint. r=smaug
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 24 Dec 2018 12:33:22 +0000
changeset 508967 50303cb35e84b2b4663ef147a4653aab0b27911c
parent 508966 5d8e428324c662d96fd47677733880ae546266d4
child 508968 c935bba7e6318712d02d8797bc4040f663df3dd0
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1513658
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1513658 - Implement DocumentOrShadowRoot.node(s)FromPoint. r=smaug ChromeOnly for now, needs tests that I'll make sure to land with. Differential Revision: https://phabricator.services.mozilla.com/D14360
dom/base/DocumentOrShadowRoot.cpp
dom/base/DocumentOrShadowRoot.h
dom/tests/mochitest/chrome/chrome.ini
dom/tests/mochitest/chrome/test_nodesFromPoint.html
dom/webidl/DocumentOrShadowRoot.webidl
--- a/dom/base/DocumentOrShadowRoot.cpp
+++ b/dom/base/DocumentOrShadowRoot.cpp
@@ -325,16 +325,28 @@ Element* DocumentOrShadowRoot::ElementFr
 }
 
 void DocumentOrShadowRoot::ElementsFromPoint(
     float aX, float aY, nsTArray<RefPtr<Element>>& aElements) {
   QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes,
                       aElements);
 }
 
+void DocumentOrShadowRoot::NodesFromPoint(float aX, float aY,
+                                          nsTArray<RefPtr<nsINode>>& aNodes) {
+  QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::Yes,
+                      aNodes);
+}
+
+nsINode* DocumentOrShadowRoot::NodeFromPoint(float aX, float aY) {
+  AutoTArray<RefPtr<nsINode>, 1> nodes;
+  QueryNodesFromPoint(*this, aX, aY, {}, FlushLayout::Yes, Multiple::No, nodes);
+  return nodes.SafeElementAt(0);
+}
+
 Element* DocumentOrShadowRoot::ElementFromPointHelper(
     float aX, float aY, bool aIgnoreRootScrollFrame, bool aFlushLayout) {
   EnumSet<FrameForPointOption> options;
   if (aIgnoreRootScrollFrame) {
     options += FrameForPointOption::IgnoreRootScrollFrame;
   }
 
   auto flush = aFlushLayout ? FlushLayout::Yes : FlushLayout::No;
--- a/dom/base/DocumentOrShadowRoot.h
+++ b/dom/base/DocumentOrShadowRoot.h
@@ -43,17 +43,17 @@ class ShadowRoot;
 class DocumentOrShadowRoot {
   enum class Kind {
     Document,
     ShadowRoot,
   };
 
  public:
   explicit DocumentOrShadowRoot(nsIDocument&);
-  explicit DocumentOrShadowRoot(mozilla::dom::ShadowRoot&);
+  explicit DocumentOrShadowRoot(ShadowRoot&);
 
   // Unusual argument naming is because of cycle collection macros.
   static void Traverse(DocumentOrShadowRoot* tmp,
                        nsCycleCollectionTraversalCallback& cb);
   static void Unlink(DocumentOrShadowRoot* tmp);
 
   nsINode& AsNode() { return mAsNode; }
 
@@ -98,18 +98,20 @@ class DocumentOrShadowRoot {
       const nsAString& aClasses);
 
   ~DocumentOrShadowRoot();
 
   Element* GetPointerLockElement();
   Element* GetFullscreenElement();
 
   Element* ElementFromPoint(float aX, float aY);
-  void ElementsFromPoint(float aX, float aY,
-                         nsTArray<RefPtr<mozilla::dom::Element>>& aElements);
+  nsINode* NodeFromPoint(float aX, float aY);
+
+  void ElementsFromPoint(float aX, float aY, nsTArray<RefPtr<Element>>&);
+  void NodesFromPoint(float aX, float aY, nsTArray<RefPtr<nsINode>>&);
 
   /**
    * Helper for elementFromPoint implementation that allows
    * ignoring the scroll frame and/or avoiding layout flushes.
    *
    * @see nsIDOMWindowUtils::elementFromPoint
    */
   Element* ElementFromPointHelper(float aX, float aY,
@@ -209,18 +211,18 @@ class DocumentOrShadowRoot {
 
   /**
    * If focused element's subtree root is this document or shadow root, return
    * focused element, otherwise, get the shadow host recursively until the
    * shadow host's subtree root is this document or shadow root.
    */
   Element* GetRetargetedFocusedElement();
 
-  nsTArray<RefPtr<mozilla::StyleSheet>> mStyleSheets;
-  RefPtr<mozilla::dom::StyleSheetList> mDOMStyleSheets;
+  nsTArray<RefPtr<StyleSheet>> mStyleSheets;
+  RefPtr<StyleSheetList> mDOMStyleSheets;
 
   /*
    * mIdentifierMap works as follows for IDs:
    * 1) Attribute changes affect the table immediately (removing and adding
    *    entries as needed).
    * 2) Removals from the DOM affect the table immediately
    * 3) Additions to the DOM always update existing entries for names, and add
    *    new ones for IDs.
--- a/dom/tests/mochitest/chrome/chrome.ini
+++ b/dom/tests/mochitest/chrome/chrome.ini
@@ -70,16 +70,17 @@ skip-if = os == 'linux'
 [test_intlUtils_getDisplayNames.html]
 [test_intlUtils_getLocaleInfo.html]
 [test_moving_nodeList.xul]
 [test_moving_xhr.xul]
 [test_MozDomFullscreen_event.xul]
 tags = fullscreen
 # disabled on OS X for intermittent failures--bug-798848
 skip-if = toolkit == 'cocoa'
+[test_nodesFromPoint.html]
 [test_nodesFromRect.html]
 [test_parsingMode.html]
 [test_popup_blocker_chrome.xul]
 [test_queryCaretRect.html]
 [test_resize_move_windows.xul]
 # disabled on linux for timeouts--bug-834716
 skip-if = os == 'linux'
 [test_sandbox_bindings.xul]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/chrome/test_nodesFromPoint.html
@@ -0,0 +1,119 @@
+<!doctype html>
+<meta charset="utf-8">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+  div { text-align: justify; max-height: 100vh; }
+</style>
+<div id="testElement"></div>
+<script>
+
+// TODO(emilio): Upstream to WPT once there's a spec for this and our
+// implementation is not [ChromeOnly].
+
+const testElement = document.getElementById("testElement");
+
+testElement.innerHTML = "X ".repeat(5000);
+
+{
+  const rect = testElement.getBoundingClientRect();
+
+  const node =
+    document.nodeFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+  is(node, testElement.firstChild, "Should return the text node");
+
+  const nodes =
+    document.nodesFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+
+  const expected = [testElement.firstChild, testElement, document.body, document.documentElement];
+  is(nodes.length, expected.length, "Not the amount of expected nodes");
+
+  for (let i = 0; i < nodes.length; ++i)
+    is(nodes[i], expected[i]);
+}
+
+// Make the test slotted, and add some fallback that we'll test later as well.
+{
+  // Work around the sanitizer by building the DOM manually....
+  const slot = document.createElement("slot");
+  slot.innerHTML = "Y ".repeat(5000);
+
+  const wrapper = document.createElement("div");
+  wrapper.appendChild(slot);
+
+  testElement.attachShadow({ mode: "open" }).appendChild(wrapper);
+}
+
+{
+  const rect = testElement.getBoundingClientRect();
+
+  const node =
+    document.nodeFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+  is(node, testElement.firstChild, "Should return the text node");
+
+  const nodes =
+    document.nodesFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+
+  const expected = [testElement.firstChild, testElement, document.body, document.documentElement];
+  is(nodes.length, expected.length, "Not the amount of expected nodes (returned nodes in the shadow?)");
+
+  for (let i = 0; i < nodes.length; ++i)
+    is(nodes[i], expected[i]);
+}
+
+{
+  const rect = testElement.getBoundingClientRect();
+
+  const node =
+    testElement.shadowRoot.nodeFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+  is(node, testElement.shadowRoot.firstChild, "Should return the div wrapping the text node");
+
+  const nodes =
+    testElement.shadowRoot.nodesFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+
+  const expected = [testElement.shadowRoot.firstChild];
+  is(nodes.length, expected.length, "Not the amount of expected nodes (returned nodes outside of the shadow?)");
+
+  for (let i = 0; i < nodes.length; ++i)
+    is(nodes[i], expected[i]);
+}
+
+// Show the fallback.
+testElement.firstChild.remove();
+
+{
+  const rect = testElement.getBoundingClientRect();
+
+  const fallbackText = testElement.shadowRoot.querySelector("slot").firstChild;
+
+  const node =
+    testElement.shadowRoot.nodeFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+  is(node, fallbackText, "Should return the fallback text");
+
+  const nodes =
+    testElement.shadowRoot.nodesFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+
+  const expected = [fallbackText, testElement.shadowRoot.firstChild];
+  is(nodes.length, expected.length, "Not the amount of expected nodes (returned nodes outside of the shadow?)");
+
+  for (let i = 0; i < nodes.length; ++i)
+    is(nodes[i], expected[i]);
+}
+
+// Test the fallback from the document.
+{
+  const rect = testElement.getBoundingClientRect();
+
+  const node =
+    document.nodeFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+  is(node, testElement, "Should return the element, since the fallback text is in the shadow");
+
+  const nodes =
+    document.nodesFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2);
+
+  const expected = [testElement, document.body, document.documentElement];
+  is(nodes.length, expected.length, "Not the amount of expected nodes (returned nodes inside of the shadow?)");
+
+  for (let i = 0; i < nodes.length; ++i)
+    is(nodes[i], expected[i]);
+}
+</script>
--- a/dom/webidl/DocumentOrShadowRoot.webidl
+++ b/dom/webidl/DocumentOrShadowRoot.webidl
@@ -7,18 +7,26 @@
  * https://dom.spec.whatwg.org/#documentorshadowroot
  * http://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin
  */
 
 [NoInterfaceObject]
 interface DocumentOrShadowRoot {
   // Not implemented yet: bug 1430308.
   // Selection? getSelection();
-  Element? elementFromPoint (float x, float y);
-  sequence<Element> elementsFromPoint (float x, float y);
+  Element? elementFromPoint(float x, float y);
+  sequence<Element> elementsFromPoint(float x, float y);
+
+  // TODO: Avoid making these ChromeOnly, see:
+  // https://github.com/w3c/csswg-drafts/issues/556
+  [ChromeOnly]
+  Node? nodeFromPoint(float x, float y);
+  [ChromeOnly]
+  sequence<Node> nodesFromPoint(float x, float y);
+
   // Not implemented yet: bug 1430307.
   // CaretPosition? caretPositionFromPoint (float x, float y);
 
   readonly attribute Element? activeElement;
   readonly attribute StyleSheetList styleSheets;
 
   readonly attribute Element? pointerLockElement;
   [LenientSetter, Func="nsIDocument::IsUnprefixedFullscreenEnabled"]