Bug 1388062 - Refactor browser aria-owns tests. r=surkov r=yzen
authorEitan Isaacson <eitan@monotonous.org>
Fri, 04 Aug 2017 10:07:14 -0700
changeset 376070 8827ea5e0a505b61b08cf6950b81a4985e9821b9
parent 376069 ed3226f7ff560e2ff36c7a57562b918dc9bf7c28
child 376071 f63a7cba6c08cc4fe68678ed264818f2130ea39d
push id32372
push userarchaeopteryx@coole-files.de
push dateTue, 22 Aug 2017 09:49:24 +0000
treeherdermozilla-central@eb72c8c07751 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssurkov, yzen
bugs1388062
milestone57.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 1388062 - Refactor browser aria-owns tests. r=surkov r=yzen
accessible/tests/browser/e10s/browser_events_show.js
accessible/tests/browser/e10s/browser_events_textchange.js
accessible/tests/browser/events.js
accessible/tests/browser/tree/browser.ini
accessible/tests/browser/tree/browser_aria_owns.js
accessible/tests/browser/tree/browser_test_aria_owns.js
accessible/tests/browser/tree/browser_test_aria_owns_select.js
accessible/tests/browser/tree/head.js
--- a/accessible/tests/browser/e10s/browser_events_show.js
+++ b/accessible/tests/browser/e10s/browser_events_show.js
@@ -6,10 +6,12 @@
 
 /**
  * Test show event
  */
 addAccessibleTask('<div id="div" style="visibility: hidden;"></div>',
   async function(browser) {
     let onShow = waitForEvent(EVENT_SHOW, "div");
     await invokeSetStyle(browser, "div", "visibility", "visible");
-    await onShow;
+    let showEvent = await onShow;
+    ok(showEvent.accessibleDocument instanceof nsIAccessibleDocument,
+      "Accessible document not present.");
   });
--- a/accessible/tests/browser/e10s/browser_events_textchange.js
+++ b/accessible/tests/browser/e10s/browser_events_textchange.js
@@ -8,16 +8,18 @@ function checkTextChangeEvent(event, id,
   let tcEvent = event.QueryInterface(nsIAccessibleTextChangeEvent);
   is(tcEvent.start, start, `Correct start offset for ${prettyName(id)}`);
   is(tcEvent.length, end - start, `Correct length for ${prettyName(id)}`);
   is(tcEvent.isInserted, isInserted,
     `Correct isInserted flag for ${prettyName(id)}`);
   is(tcEvent.modifiedText, text, `Correct text for ${prettyName(id)}`);
   is(tcEvent.isFromUserInput, isFromUserInput,
     `Correct value of isFromUserInput for ${prettyName(id)}`);
+  ok(tcEvent.accessibleDocument instanceof nsIAccessibleDocument,
+    "Accessible document not present.");
 }
 
 async function changeText(browser, id, value, events) {
   let onEvents = waitForOrderedEvents(events.map(({ isInserted }) => {
     let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
     return [ eventType, id ];
   }));
   // Change text in the subtree.
--- a/accessible/tests/browser/events.js
+++ b/accessible/tests/browser/events.js
@@ -8,17 +8,17 @@
 // globals from there.
 /* import-globals-from shared-head.js */
 /* import-globals-from ../mochitest/common.js */
 
 /* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
             EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
             EVENT_DESCRIPTION_CHANGE, EVENT_NAME_CHANGE, EVENT_STATE_CHANGE,
             EVENT_VALUE_CHANGE, EVENT_TEXT_VALUE_CHANGE, EVENT_FOCUS,
-            EVENT_DOCUMENT_RELOAD,
+            EVENT_DOCUMENT_RELOAD, UnexpectedEvents,
             waitForEvent, waitForEvents, waitForOrderedEvents */
 
 const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
 const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE;
 const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
@@ -107,19 +107,16 @@ function waitForEvent(eventType, matchCr
 
         // If event type does not match expected type, skip the event.
         if (event.eventType !== eventType) {
           return;
         }
 
         if (matchEvent(event, matchCriteria)) {
           Logger.log(`Correct event type: ${eventTypeToString(eventType)}`);
-          ok(event.accessibleDocument instanceof nsIAccessibleDocument,
-            "Accessible document present.");
-
           Services.obs.removeObserver(this, "accessible-event");
           resolve(event);
         }
       }
     };
     Services.obs.addObserver(eventObserver, "accessible-event");
   });
 }
--- a/accessible/tests/browser/tree/browser.ini
+++ b/accessible/tests/browser/tree/browser.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
 skip-if = e10s && os == 'win' && release_or_beta
 support-files =
   head.js
   !/accessible/tests/browser/events.js
   !/accessible/tests/browser/shared-head.js
   !/accessible/tests/mochitest/*.js
 
-[browser_test_aria_owns.js]
-[browser_test_aria_owns_select.js]
+[browser_aria_owns.js]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/tree/browser_aria_owns.js
@@ -0,0 +1,149 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+let NO_MOVE = { unexpected: [[EVENT_REORDER, "container"]] };
+let MOVE = { expected: [[EVENT_REORDER, "container"]] };
+
+// Set last ordinal child as aria-owned, should produce no reorder.
+addAccessibleTask(`<ul id="container"><li id="a">Test</li></ul>`,
+  async function(browser, accDoc) {
+    let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+    testChildrenIds(containerAcc, ["a"]);
+
+    await contentSpawnMutation(browser, NO_MOVE, function() {
+      // aria-own ordinal child in place, should be a no-op.
+      document.getElementById("container").setAttribute("aria-owns", "a");
+    });
+
+    testChildrenIds(containerAcc, ["a"]);
+  }
+);
+
+// Add a new ordinal child to a container with an aria-owned child.
+// Order should respect aria-owns.
+addAccessibleTask(`<ul id="container"><li id="a">Test</li></ul>`,
+  async function(browser, accDoc) {
+    let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+    testChildrenIds(containerAcc, ["a"]);
+
+    await contentSpawnMutation(browser, MOVE, function() {
+      let container = document.getElementById("container");
+      container.setAttribute("aria-owns", "a");
+
+      let aa = document.createElement("li");
+      aa.id = "aa";
+      container.appendChild(aa);
+    });
+
+    testChildrenIds(containerAcc, ["aa", "a"]);
+
+    await contentSpawnMutation(browser, MOVE, function() {
+      document.getElementById("container").removeAttribute("aria-owns");
+    });
+
+    testChildrenIds(containerAcc, ["a", "aa"]);
+  }
+);
+
+// Remove a no-move aria-owns attribute, should result in a no-move.
+addAccessibleTask(`<ul id="container" aria-owns="a"><li id="a">Test</li></ul>`,
+  async function(browser, accDoc) {
+    let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+    testChildrenIds(containerAcc, ["a"]);
+
+    await contentSpawnMutation(browser, NO_MOVE, function() {
+      // remove aria-owned child that is already ordinal, should be no-op.
+      document.getElementById("container").removeAttribute("aria-owns");
+    });
+
+    testChildrenIds(containerAcc, ["a"]);
+  }
+);
+
+// Attempt to steal an aria-owned child. The attempt should fail.
+addAccessibleTask(`
+  <ul>
+    <li id="a">Test</li>
+  </ul>
+  <ul aria-owns="a"></ul>
+  <ul id="container"></ul>`,
+  async function(browser, accDoc) {
+    let containerAcc = findAccessibleChildByID(accDoc, "container");
+
+    testChildrenIds(containerAcc, []);
+
+    await contentSpawnMutation(browser, NO_MOVE, function() {
+      document.getElementById("container").setAttribute("aria-owns", "a");
+    });
+
+    testChildrenIds(containerAcc, []);
+  }
+);
+
+// Correctly aria-own children of <select>
+addAccessibleTask(`
+  <div id="container" role="group" aria-owns="b"></div>
+  <select id="select">
+    <option id="a"></option>
+    <option id="b"></option>
+  </select>`,
+  async function(browser, accDoc) {
+    let containerAcc = findAccessibleChildByID(accDoc, "container");
+    let selectAcc = findAccessibleChildByID(accDoc, "select");
+
+    testChildrenIds(containerAcc, ["b"]);
+    testChildrenIds(selectAcc.firstChild, ["a"]);
+
+    let waitfor = { expected: [
+      [EVENT_REORDER, "container"],
+      [EVENT_REORDER,
+        evt => getAccessibleDOMNodeID(evt.accessible.parent) == "select"]] };
+
+    await contentSpawnMutation(browser, waitfor, function() {
+      document.getElementById("container").removeAttribute("aria-owns");
+    });
+
+    testChildrenIds(containerAcc, []);
+    testChildrenIds(selectAcc.firstChild, ["a", "b"]);
+  }
+);
+
+addAccessibleTask(`
+  <ul id="one">
+    <li id="a">Test</li>
+    <li id="b">Test 2</li>
+    <li id="c">Test 3</li>
+  </ul>
+  <ul id="two"></ul>`,
+  async function(browser, accDoc) {
+    let one = findAccessibleChildByID(accDoc, "one");
+    let two = findAccessibleChildByID(accDoc, "two");
+
+    let waitfor = { expected: [
+      [EVENT_REORDER, "one"],
+      [EVENT_REORDER, "two"]] };
+
+    await contentSpawnMutation(browser, waitfor, function() {
+      // Put same id twice in aria-owns
+      document.getElementById("two").setAttribute("aria-owns", "a a");
+    });
+
+    testChildrenIds(one, ["b", "c"]);
+    testChildrenIds(two, ["a"]);
+
+    await contentSpawnMutation(browser, waitfor, function() {
+      // If the previous double-id aria-owns worked correctly, we should
+      // be in a good state and all is fine..
+      document.getElementById("two").setAttribute("aria-owns", "a b");
+    });
+
+    testChildrenIds(one, ["c"]);
+    testChildrenIds(two, ["a", "b"]);
+  }
+);
deleted file mode 100644
--- a/accessible/tests/browser/tree/browser_test_aria_owns.js
+++ /dev/null
@@ -1,105 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-function testChildrenIds(acc, expectedIds) {
-  let ids = arrayFromChildren(acc).map(child => getAccessibleDOMNodeID(child));
-  Assert.deepEqual(ids, expectedIds,
-    `Children for ${getAccessibleDOMNodeID(acc)} are wrong.`);
-}
-
-async function runTests(browser, accDoc) {
-  let one = findAccessibleChildByID(accDoc, "one");
-  let two = findAccessibleChildByID(accDoc, "two");
-  let three = findAccessibleChildByID(accDoc, "three");
-  let four = findAccessibleChildByID(accDoc, "four");
-
-  testChildrenIds(one, ["a"]);
-  testChildrenIds(two, ["b", "c", "d"]);
-  testChildrenIds(three, []);
-
-  let onReorders = waitForEvents({
-    expected: [
-      [EVENT_REORDER, "two"]], // children will be reordered via aria-owns
-    unexpected: [
-      [EVENT_REORDER, "one"],  // child will remain in place
-      [EVENT_REORDER, "three"], // none of its children will be reclaimed
-      [EVENT_REORDER, "four"]] // child will remain in place
-  });
-
-  await ContentTask.spawn(browser, null, async function() {
-    // aria-own ordinal child in place, should be a no-op.
-    document.getElementById("one").setAttribute("aria-owns", "a");
-    // remove aria-owned child that is already ordinal, should be no-op.
-    document.getElementById("four").removeAttribute("aria-owns");
-    // shuffle aria-owns with markup child.
-    document.getElementById("two").setAttribute("aria-owns", "d c");
-  });
-
-  await onReorders;
-
-  testChildrenIds(one, ["a"]);
-  testChildrenIds(two, ["b", "d", "c"]);
-  testChildrenIds(three, []);
-  testChildrenIds(four, ["e"]);
-
-  onReorders = waitForEvent(EVENT_REORDER, "one");
-
-  await ContentTask.spawn(browser, null, async function() {
-    let aa = document.createElement("li");
-    aa.id = "aa";
-    document.getElementById("one").appendChild(aa);
-  });
-
-  await onReorders;
-
-  // aria-owned child should be after ordinal children.
-  testChildrenIds(one, ["aa", "a"]);
-
-  onReorders = waitForEvent(EVENT_REORDER, "one");
-
-  await ContentTask.spawn(browser, null, async function() {
-    // removing aria-owns should reorder the children
-    document.getElementById("one").removeAttribute("aria-owns");
-  });
-
-  await onReorders;
-
-  // with no aria-owns, layout order should prevail.
-  testChildrenIds(one, ["a", "aa"]);
-
-  onReorders = waitForEvents([
-      [EVENT_REORDER, "four"],    // "b" will go to "three"
-      [EVENT_REORDER, "two"]]); // some children will be reclaimed and acquired
-
-  await ContentTask.spawn(browser, null, async function() {
-    // child order will be overridden by aria-owns
-    document.getElementById("four").setAttribute("aria-owns", "b e");
-  });
-
-  await onReorders;
-
-  testChildrenIds(four, ["b", "e"]);
-  testChildrenIds(two, ["d", "c"]);
-}
-
-/**
- * Test caching of accessible object states
- */
-addAccessibleTask(`
-    <ul id="one">
-      <li id="a">Test</li>
-    </ul>
-    <ul id="two" aria-owns="d">
-      <li id="b">Test 2</li>
-      <li id="c">Test 3</li>
-    </ul>
-    <ul id="three">
-      <li id="d">Test 4</li>
-    </ul>
-    <ul id="four" aria-owns="e">
-      <li id="e">Test 5</li>
-    </ul>
-    `, runTests);
deleted file mode 100644
--- a/accessible/tests/browser/tree/browser_test_aria_owns_select.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-function testChildrenIds(acc, expectedIds) {
-  let ids = arrayFromChildren(acc).map(child => getAccessibleDOMNodeID(child));
-  Assert.deepEqual(ids, expectedIds,
-    `Children for ${getAccessibleDOMNodeID(acc)} are wrong.`);
-}
-
-async function runTests(browser, accDoc) {
-  let div = findAccessibleChildByID(accDoc, "div");
-  let select = findAccessibleChildByID(accDoc, "select");
-
-  testChildrenIds(div, ["b"]);
-  testChildrenIds(select.firstChild, ["a"]);
-
-  let onReorders = waitForEvents([
-    [EVENT_REORDER, "div"],
-    [EVENT_REORDER,
-      evt => getAccessibleDOMNodeID(evt.accessible.parent) == "select"]
-  ]);
-
-  await ContentTask.spawn(browser, null, async function() {
-    document.getElementById("div").removeAttribute("aria-owns");
-  });
-
-  await onReorders;
-
-  testChildrenIds(div, []);
-  testChildrenIds(select.firstChild, ["a", "b"]);
-}
-
-/**
- * Test caching of accessible object states
- */
-addAccessibleTask(`
-  <div id="div" role="group" aria-owns="b"></div>
-  <select id="select">
-    <option id="a"></option>
-    <option id="b"></option>
-  </select>
-    `, runTests);
--- a/accessible/tests/browser/tree/head.js
+++ b/accessible/tests/browser/tree/head.js
@@ -1,15 +1,65 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
+/* exported contentSpawnMutation, testChildrenIds */
+
 // Load the shared-head file first.
 /* import-globals-from ../shared-head.js */
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
   this);
 
 // Loading and common.js from accessible/tests/mochitest/ for all tests, as
 // well as events.js.
 loadScripts({ name: "common.js", dir: MOCHITESTS_DIR }, "events.js", "layout.js");
+
+/*
+ * A test function for comparing the IDs of an accessible's children
+ * with an expected array of IDs.
+ */
+function testChildrenIds(acc, expectedIds) {
+  let ids = arrayFromChildren(acc).map(child => getAccessibleDOMNodeID(child));
+  Assert.deepEqual(ids, expectedIds,
+    `Children for ${getAccessibleDOMNodeID(acc)} are wrong.`);
+}
+
+/*
+ * This function spawns a content task and awaits expected mutation events from
+ * aria-owns changes. It's good at catching events we did *not* expect. We do
+ * this advancing the layout refresh to flush the relocations/insertions queue.
+ */
+async function contentSpawnMutation(browser, waitFor, func) {
+  let onReorders = waitForEvents({ expected: waitFor.expected || [] });
+  let unexpectedListener = new UnexpectedEvents(waitFor.unexpected || []);
+
+  function tick() {
+    // 100ms is an arbitrary positive number to advance the clock.
+    // We don't need to advance the clock for a11y mutations, but other
+    // tick listeners may depend on an advancing clock with each refresh.
+    content.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindowUtils).advanceTimeAndRefresh(100);
+  }
+
+  // This stops the refreh driver from doing its regular ticks, and leaves
+  // us in control.
+  await ContentTask.spawn(browser, null, tick);
+
+  // Perform the tree mutation.
+  await ContentTask.spawn(browser, null, func);
+
+  // Do one tick to flush our queue (insertions, relocations, etc.)
+  await ContentTask.spawn(browser, null, tick);
+
+  await onReorders;
+
+  unexpectedListener.stop();
+
+  // Go back to normal refresh driver ticks.
+  await ContentTask.spawn(browser, null, function() {
+    content.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindowUtils).restoreNormalRefresh();
+  });
+}