Bug 1186029 - e10s compatible name caching, events and tree update tests. r=eeejay draft
authorYura Zenevich <yzenevich@mozilla.com>
Mon, 02 May 2016 11:09:28 -0400
changeset 358363 c742ebc96467872847e0a82c6ff87b964fafb5b2
parent 358327 77cead2cd20300623eea2416bc9bce4d5021df09
child 519831 d1d0d1cbb74c46b5f91c244a9784fa10a56f5257
push id16984
push useryura.zenevich@gmail.com
push dateMon, 02 May 2016 15:10:00 +0000
reviewerseeejay
bugs1186029, 100644
milestone49.0a1
Bug 1186029 - e10s compatible name caching, events and tree update tests. r=eeejay MozReview-Commit-ID: 5Y5sYgGI2L9 --- .eslintignore | 1 - accessible/.eslintrc | 15 + accessible/jsat/OutputGenerator.jsm | 7 +- accessible/moz.build | 2 + accessible/tests/browser/.eslintrc | 202 ++++++++++ accessible/tests/browser/browser.ini | 53 +++ accessible/tests/browser/browser_caching_name.js | 434 +++++++++++++++++++++ .../tests/browser/browser_events_caretmove.js | 21 + accessible/tests/browser/browser_events_hide.js | 32 ++ accessible/tests/browser/browser_events_show.js | 17 + .../tests/browser/browser_events_statechange.js | 60 +++ .../tests/browser/browser_events_textchange.js | 72 ++++ .../tests/browser/browser_treeupdate_ariadialog.js | 42 ++ .../tests/browser/browser_treeupdate_ariaowns.js | 317 +++++++++++++++ .../tests/browser/browser_treeupdate_canvas.js | 25 ++ .../browser/browser_treeupdate_cssoverflow.js | 64 +++ accessible/tests/browser/browser_treeupdate_doc.js | 303 ++++++++++++++ .../tests/browser/browser_treeupdate_gencontent.js | 78 ++++ .../tests/browser/browser_treeupdate_hidden.js | 30 ++ .../tests/browser/browser_treeupdate_imagemap.js | 176 +++++++++ .../tests/browser/browser_treeupdate_list.js | 43 ++ .../browser/browser_treeupdate_list_editabledoc.js | 39 ++ .../tests/browser/browser_treeupdate_listener.js | 43 ++ .../tests/browser/browser_treeupdate_optgroup.js | 91 +++++ .../tests/browser/browser_treeupdate_removal.js | 39 ++ .../tests/browser/browser_treeupdate_table.js | 51 +++ .../tests/browser/browser_treeupdate_textleaf.js | 34 ++ .../tests/browser/browser_treeupdate_visibility.js | 196 ++++++++++ .../tests/browser/browser_treeupdate_whitespace.js | 71 ++++ .../tests/browser/doc_treeupdate_ariadialog.html | 23 ++ .../tests/browser/doc_treeupdate_ariaowns.html | 44 +++ .../tests/browser/doc_treeupdate_imagemap.html | 21 + .../tests/browser/doc_treeupdate_removal.xhtml | 11 + .../tests/browser/doc_treeupdate_visibility.html | 78 ++++ .../tests/browser/doc_treeupdate_whitespace.html | 10 + accessible/tests/browser/events.js | 100 +++++ accessible/tests/browser/head.js | 297 ++++++++++++++ accessible/tests/mochitest/common.js | 41 +- 38 files changed, 3176 insertions(+), 7 deletions(-) create mode 100644 accessible/.eslintrc create mode 100644 accessible/tests/browser/.eslintrc create mode 100644 accessible/tests/browser/browser.ini create mode 100644 accessible/tests/browser/browser_caching_name.js create mode 100644 accessible/tests/browser/browser_events_caretmove.js create mode 100644 accessible/tests/browser/browser_events_hide.js create mode 100644 accessible/tests/browser/browser_events_show.js create mode 100644 accessible/tests/browser/browser_events_statechange.js create mode 100644 accessible/tests/browser/browser_events_textchange.js create mode 100644 accessible/tests/browser/browser_treeupdate_ariadialog.js create mode 100644 accessible/tests/browser/browser_treeupdate_ariaowns.js create mode 100644 accessible/tests/browser/browser_treeupdate_canvas.js create mode 100644 accessible/tests/browser/browser_treeupdate_cssoverflow.js create mode 100644 accessible/tests/browser/browser_treeupdate_doc.js create mode 100644 accessible/tests/browser/browser_treeupdate_gencontent.js create mode 100644 accessible/tests/browser/browser_treeupdate_hidden.js create mode 100644 accessible/tests/browser/browser_treeupdate_imagemap.js create mode 100644 accessible/tests/browser/browser_treeupdate_list.js create mode 100644 accessible/tests/browser/browser_treeupdate_list_editabledoc.js create mode 100644 accessible/tests/browser/browser_treeupdate_listener.js create mode 100644 accessible/tests/browser/browser_treeupdate_optgroup.js create mode 100644 accessible/tests/browser/browser_treeupdate_removal.js create mode 100644 accessible/tests/browser/browser_treeupdate_table.js create mode 100644 accessible/tests/browser/browser_treeupdate_textleaf.js create mode 100644 accessible/tests/browser/browser_treeupdate_visibility.js create mode 100644 accessible/tests/browser/browser_treeupdate_whitespace.js create mode 100644 accessible/tests/browser/doc_treeupdate_ariadialog.html create mode 100644 accessible/tests/browser/doc_treeupdate_ariaowns.html create mode 100644 accessible/tests/browser/doc_treeupdate_imagemap.html create mode 100644 accessible/tests/browser/doc_treeupdate_removal.xhtml create mode 100644 accessible/tests/browser/doc_treeupdate_visibility.html create mode 100644 accessible/tests/browser/doc_treeupdate_whitespace.html create mode 100644 accessible/tests/browser/events.js create mode 100644 accessible/tests/browser/head.js
.eslintignore
accessible/.eslintrc
accessible/jsat/OutputGenerator.jsm
accessible/moz.build
accessible/tests/browser/.eslintrc
accessible/tests/browser/browser.ini
accessible/tests/browser/browser_caching_name.js
accessible/tests/browser/browser_events_caretmove.js
accessible/tests/browser/browser_events_hide.js
accessible/tests/browser/browser_events_show.js
accessible/tests/browser/browser_events_statechange.js
accessible/tests/browser/browser_events_textchange.js
accessible/tests/browser/browser_treeupdate_ariadialog.js
accessible/tests/browser/browser_treeupdate_ariaowns.js
accessible/tests/browser/browser_treeupdate_canvas.js
accessible/tests/browser/browser_treeupdate_cssoverflow.js
accessible/tests/browser/browser_treeupdate_doc.js
accessible/tests/browser/browser_treeupdate_gencontent.js
accessible/tests/browser/browser_treeupdate_hidden.js
accessible/tests/browser/browser_treeupdate_imagemap.js
accessible/tests/browser/browser_treeupdate_list.js
accessible/tests/browser/browser_treeupdate_list_editabledoc.js
accessible/tests/browser/browser_treeupdate_listener.js
accessible/tests/browser/browser_treeupdate_optgroup.js
accessible/tests/browser/browser_treeupdate_removal.js
accessible/tests/browser/browser_treeupdate_table.js
accessible/tests/browser/browser_treeupdate_textleaf.js
accessible/tests/browser/browser_treeupdate_visibility.js
accessible/tests/browser/browser_treeupdate_whitespace.js
accessible/tests/browser/doc_treeupdate_ariadialog.html
accessible/tests/browser/doc_treeupdate_ariaowns.html
accessible/tests/browser/doc_treeupdate_imagemap.html
accessible/tests/browser/doc_treeupdate_removal.xhtml
accessible/tests/browser/doc_treeupdate_visibility.html
accessible/tests/browser/doc_treeupdate_whitespace.html
accessible/tests/browser/events.js
accessible/tests/browser/head.js
accessible/tests/mochitest/common.js
--- a/.eslintignore
+++ b/.eslintignore
@@ -2,17 +2,16 @@
 **/node_modules/**/*.*
 
 # Exclude expected objdirs.
 obj*/**
 
 # We ignore all these directories by default, until we get them enabled.
 # If you are enabling a directory, please add directory specific exclusions
 # below.
-accessible/**
 addon-sdk/**
 build/**
 caps/**
 chrome/**
 config/**
 db/**
 docshell/**
 dom/**
new file mode 100644
--- /dev/null
+++ b/accessible/.eslintrc
@@ -0,0 +1,15 @@
+{
+  "extends": [
+    "../.eslintrc"
+  ],
+  "globals": {
+    "Cc": true,
+    "Ci": true,
+    "Components": true,
+    "console": true,
+    "Cu": true,
+    "dump": true,
+    "Services": true,
+    "XPCOMUtils": true
+  }
+}
--- a/accessible/jsat/OutputGenerator.jsm
+++ b/accessible/jsat/OutputGenerator.jsm
@@ -271,20 +271,19 @@ var OutputGenerator = {
   /**
    * Adds MathML menclose notations to the output.
    * @param {Array} aOutput Output array.
    * @param {nsIAccessible} aAccessible current accessible object.
    */
   _addMencloseNotations: function _addMencloseNotations(aOutput, aAccessible) {
     let notations = Utils.getAttributes(aAccessible).notation || 'longdiv';
     aOutput[this.outputOrder === OUTPUT_DESC_FIRST ? 'push' : 'unshift'].apply(
-      aOutput, [for (notation of notations.split(' '))
-        {string: this._getOutputName('notation-' + notation)}
-      ]
-    );
+      aOutput, notations.split(' ').map(notation => {
+        return { string: this._getOutputName('notation-' + notation) };
+      }));
   },
 
   /**
    * Adds an entry type attribute to the description if available.
    * @param {Array} aOutput Output array.
    * @param {nsIAccessible} aAccessible current accessible object.
    * @param {String} aRoleStr aAccessible's role string.
    */
--- a/accessible/moz.build
+++ b/accessible/moz.build
@@ -16,8 +16,10 @@ else:
     DIRS += ['other']
 
 DIRS += ['base', 'generic', 'html', 'interfaces', 'ipc', 'jsat', 'xpcom']
 
 if CONFIG['MOZ_XUL']:
     DIRS += ['xul']
 
 TEST_DIRS += ['tests/mochitest']
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/.eslintrc
@@ -0,0 +1,202 @@
+{
+  "extends": [
+    "../../../testing/mochitest/browser.eslintrc"
+  ],
+  // All globals made available in the test environment.
+  "globals": {
+    // Content scripts have global 'content' object
+    "content": true,
+
+    // Defined in accessible/tests/mochitest/ common.js, name.js, states.js
+    "prettyName": true,
+    "statesToString": true,
+    "eventTypeToString": true,
+    "testName": true,
+    "testStates": true,
+    "testAccessibleTree": true,
+    "isAccessible": true,
+    "getAccessibleDOMNodeID": true,
+
+    // Defined for all accessibility browser tests.
+    "addAccessibleTask": true,
+    "BrowserTestUtils": true,
+    "ContentTask": true,
+    "gBrowser": true,
+    "isDefunct": true,
+    "loadScripts": true,
+    "Logger": true,
+    "MOCHITESTS_DIR": true,
+    "waitForEvent": true,
+    "waitForMultipleEvents": true,
+    "invokeSetAttribute": true,
+    "invokeSetStyle": true,
+    "invokeFocus": true,
+    "findAccessibleChildByID": true
+  },
+  "rules": {
+    "mozilla/mark-test-function-used": 1,
+    "mozilla/no-aArgs": 1,
+    "mozilla/no-cpows-in-tests": 1,
+    "mozilla/reject-importGlobalProperties": 1,
+    "mozilla/var-only-at-top-level": 1,
+
+    "block-scoped-var": 2,
+    "brace-style": [2, "1tbs"],
+    "camelcase": 2,
+    "comma-dangle": [2, "never"],
+    "comma-spacing": 2,
+    "comma-style": [2, "last"],
+    "complexity": [2, 35],
+    "consistent-this": 0,
+    "curly": [2, "multi-line"],
+    "default-case": 0,
+    "dot-location": [2, "property"],
+    "dot-notation": 2,
+    "eol-last": 2,
+    "eqeqeq": 0,
+    "func-names": 0,
+    "func-style": 0,
+    "generator-star": 0,
+    "global-strict": 0,
+    "handle-callback-err": [2, "er"],
+    "indent": [2, 2, {"SwitchCase": 1}],
+    "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
+    "linebreak-style": 0,
+    "max-depth": 0,
+    "max-nested-callbacks": [2, 3],
+    "max-params": 0,
+    "max-statements": 0,
+    "new-cap": [2, {"capIsNew": false}],
+    "new-parens": 2,
+    "no-array-constructor": 2,
+    "no-bitwise": 0,
+    "no-caller": 2,
+    "no-catch-shadow": 2,
+    "no-comma-dangle": 0,
+    "no-cond-assign": 2,
+    "no-console": 0,
+    "no-constant-condition": 0,
+    "no-continue": 0,
+    "no-control-regex": 2,
+    "no-debugger": 2,
+    "no-delete-var": 2,
+    "no-div-regex": 0,
+    "no-dupe-args": 2,
+    "no-dupe-keys": 2,
+    "no-duplicate-case": 2,
+    "no-else-return": 2,
+    "no-empty": 2,
+    "no-empty-character-class": 2,
+    "no-eval": 2,
+    "no-ex-assign": 2,
+    "no-extend-native": 2,
+    "no-extra-bind": 2,
+    "no-extra-boolean-cast": 2,
+    "no-extra-parens": 0,
+    "no-extra-semi": 2,
+    "no-extra-strict": 0,
+    "no-fallthrough": 2,
+    "no-floating-decimal": 0,
+    "no-inline-comments": 0,
+    "no-lonely-if": 2,
+    "no-mixed-requires": 0,
+    "no-mixed-spaces-and-tabs": 2,
+    "no-multi-spaces": 2,
+    "no-multi-str": 2,
+    "no-multiple-empty-lines": [2, {"max": 1}],
+    "no-native-reassign": 2,
+    "no-nested-ternary": 2,
+    "no-new-require": 0,
+    "no-octal": 2,
+    "no-param-reassign": 0,
+    "no-path-concat": 0,
+    "no-plusplus": 0,
+    "no-process-env": 0,
+    "no-process-exit": 0,
+    "no-proto": 2,
+    "no-redeclare": 2,
+    "no-regex-spaces": 2,
+    "no-reserved-keys": 0,
+    "no-restricted-modules": 0,
+    "no-return-assign": 1,
+    "no-script-url": 0,
+    "no-self-compare": 2,
+    "no-sequences": 2,
+    "no-shadow": 1,
+    "no-shadow-restricted-names": 2,
+    "no-space-before-semi": 0,
+    "no-spaced-func": 2,
+    "no-sparse-arrays": 2,
+    "no-sync": 0,
+    "no-ternary": 0,
+    "no-throw-literal": 2,
+    "no-trailing-spaces": 2,
+    "no-undef": 2,
+    "no-underscore-dangle": 0,
+    "no-undefined": 0,
+    "no-unneeded-ternary": 2,
+    "no-unreachable": 2,
+    "no-unused-vars": [2, {"vars": "all", "args": "none"}],
+    "no-use-before-define": 0,
+    "no-var": 0,
+    "no-warning-comments": 0,
+    "no-with": 2,
+    "object-shorthand": 0,
+    "one-var": [2, "never"],
+    "padded-blocks": [2, "never"],
+    "quote-props": 0,
+    "radix": 2,
+    "semi": [2, "always"],
+    "semi-spacing": [2, {"before": false, "after": true}],
+    "sort-vars": 0,
+    "space-after-function-name": 0,
+    "keyword-spacing": 2,
+    "space-before-blocks": 2,
+    "space-before-function-parentheses": 0,
+    "space-before-function-paren": [2, "never"],
+    "space-in-brackets": 0,
+    "space-in-parens": [2, "never"],
+    "space-infix-ops": [2, {"int32Hint": true}],
+    "space-unary-ops": [2, { "words": true, "nonwords": false }],
+    "space-unary-word-ops": 0,
+    "spaced-comment": [2, "always"],
+    "strict": [2, "global"],
+    "use-isnan": 2,
+    "valid-jsdoc": 0,
+    "valid-typeof": 2,
+    "vars-on-top": 0,
+    "wrap-iife": 0,
+    "wrap-regex": 0,
+    "yoda": 2,
+
+    "guard-for-in": 0,
+    "newline-after-var": 0,
+    "no-alert": 0,
+    "no-eq-null": 0,
+    "no-func-assign": 0,
+    "no-implied-eval": 0,
+    "no-inner-declarations": 0,
+    "no-invalid-regexp": 0,
+    "no-irregular-whitespace": 0,
+    "no-iterator": 0,
+    "no-label-var": 0,
+    "no-labels": 2,
+    "no-lone-blocks": 0,
+    "no-loop-func": 0,
+    "no-negated-in-lhs": 0,
+    "no-new": 0,
+    "no-new-func": 0,
+    "no-new-object": 0,
+    "no-new-wrappers": 0,
+    "no-obj-calls": 0,
+    "no-octal-escape": 0,
+    "no-undef-init": 2,
+    "no-unexpected-multiline": 2,
+    "object-curly-spacing": 0,
+    "no-unused-expressions": 0,
+    "no-void": 0,
+    "no-wrap-func": 0,
+    "operator-assignment": 0,
+    "operator-linebreak": [2, "after"],
+  }
+}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser.ini
@@ -0,0 +1,53 @@
+[DEFAULT]
+skip-if = (e10s && os == 'win') # Bug 1269369: Document loaded event does not fire in Windows
+support-files =
+  events.js
+  head.js
+  doc_treeupdate_ariadialog.html
+  doc_treeupdate_ariaowns.html
+  doc_treeupdate_imagemap.html
+  doc_treeupdate_removal.xhtml
+  doc_treeupdate_visibility.html
+  doc_treeupdate_whitespace.html
+  !/accessible/tests/mochitest/*.js
+  !/accessible/tests/mochitest/letters.gif
+  !/accessible/tests/mochitest/moz.png
+
+# Caching tests
+[browser_caching_name.js]
+skip-if = e10s
+
+# Events tests
+[browser_events_caretmove.js]
+[browser_events_hide.js]
+skip-if = e10s
+[browser_events_show.js]
+skip-if = e10s
+[browser_events_statechange.js]
+[browser_events_textchange.js]
+skip-if = e10s
+
+# Tree update tests
+[browser_treeupdate_ariadialog.js]
+skip-if = e10s
+[browser_treeupdate_ariaowns.js]
+skip-if = e10s
+[browser_treeupdate_canvas.js]
+skip-if = e10s
+[browser_treeupdate_cssoverflow.js]
+[browser_treeupdate_doc.js]
+skip-if = e10s
+[browser_treeupdate_gencontent.js]
+[browser_treeupdate_hidden.js]
+[browser_treeupdate_imagemap.js]
+skip-if = e10s
+[browser_treeupdate_list.js]
+[browser_treeupdate_list_editabledoc.js]
+[browser_treeupdate_listener.js]
+[browser_treeupdate_optgroup.js]
+[browser_treeupdate_removal.js]
+[browser_treeupdate_table.js]
+[browser_treeupdate_textleaf.js]
+[browser_treeupdate_visibility.js]
+[browser_treeupdate_whitespace.js]
+skip-if = true # Failing due to incorrect index of test container children on document load.
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_caching_name.js
@@ -0,0 +1,434 @@
+/* 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';
+
+/* global EVENT_REORDER, EVENT_TEXT_INSERTED */
+
+loadScripts({ name: 'name.js', dir: MOCHITESTS_DIR });
+
+/**
+ * Rules for name tests that are inspired by
+ *   accessible/tests/mochitest/name/markuprules.xul
+ *
+ * Each element in the list of rules represents a name calculation rule for a
+ * particular test case.
+ *
+ * The rules have the following format:
+ *   { attr } - calculated from attribute
+ *   { elm } - calculated from another element
+ *   { fromsubtree } - calculated from element's subtree
+ *
+ *
+ * Options include:
+ *   * recreated   - subrtee is recreated and the test should only continue
+ *                   after a reorder event
+ *   * textchanged - text is inserted into a subtree and the test should only
+ *                   continue after a text inserted event
+ */
+const ARIARule = [{ attr: 'aria-labelledby' }, { attr: 'aria-label' }];
+const HTMLControlHeadRule = [...ARIARule, { elm: 'label', isSibling: true }];
+const rules = {
+  CSSContent: [{ elm: 'style', isSibling: true }, { fromsubtree: true }],
+  HTMLARIAGridCell: [...ARIARule, { fromsubtree: true }, { attr: 'title' }],
+  HTMLControl: [...HTMLControlHeadRule, { fromsubtree: true },
+    { attr: 'title' }],
+  HTMLElm: [...ARIARule, { attr: 'title' }],
+  HTMLImg: [...ARIARule, { attr: 'alt', recreated: true }, { attr: 'title' }],
+  HTMLImgEmptyAlt: [...ARIARule, { attr: 'title' }, { attr: 'alt' }],
+  HTMLInputButton: [...HTMLControlHeadRule, { attr: 'value' },
+    { attr: 'title' }],
+  HTMLInputImage: [...HTMLControlHeadRule, { attr: 'alt', recreated: true },
+    { attr: 'value', recreated: true }, { attr: 'title' }],
+  HTMLInputImageNoValidSrc: [...HTMLControlHeadRule,
+    { attr: 'alt', recreated: true }, { attr: 'value', recreated: true }],
+  HTMLInputReset: [...HTMLControlHeadRule,
+    { attr: 'value', textchanged: true }],
+  HTMLInputSubmit: [...HTMLControlHeadRule,
+    { attr: 'value', textchanged: true }],
+  HTMLLink: [...ARIARule, { fromsubtree: true }, { attr: 'title' }],
+  HTMLLinkImage: [...ARIARule, { elm: 'img' }, { attr: 'title' }],
+  HTMLOption: [...ARIARule, { attr: 'label' }, { fromsubtree: true },
+    { attr: 'title' }],
+  HTMLTable: [...ARIARule, { elm: 'caption' }, { attr: 'summary' },
+    { attr: 'title' }]
+};
+
+const markupTests = [{
+  id: 'btn',
+  ruleset: 'HTMLControl',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn">test4</label>
+    <button id="btn"
+            aria-label="test1"
+            aria-labelledby="l1 l2"
+            title="test5">press me</button>`,
+  expected: ['test2 test3', 'test1', 'test4', 'press me', 'test5']
+}, {
+  id: 'btn',
+  ruleset: 'HTMLInputButton',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn">test4</label>
+    <input id="btn"
+           type="button"
+           aria-label="test1"
+           aria-labelledby="l1 l2"
+           value="name from value"
+           alt="no name from al"
+           src="no name from src"
+           data="no name from data"
+           title="name from title"/>`,
+  expected: ['test2 test3', 'test1', 'test4', 'name from value',
+    'name from title']
+}, {
+  id: 'btn-submit',
+  ruleset: 'HTMLInputSubmit',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn-submit">test4</label>
+    <input id="btn-submit"
+           type="submit"
+           aria-label="test1"
+           aria-labelledby="l1 l2"
+           value="name from value"
+           alt="no name from atl"
+           src="no name from src"
+           data="no name from data"
+           title="no name from title"/>`,
+  expected: ['test2 test3', 'test1', 'test4', 'name from value']
+}, {
+  id: 'btn-reset',
+  ruleset: 'HTMLInputReset',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn-reset">test4</label>
+    <input id="btn-reset"
+           type="reset"
+           aria-label="test1"
+           aria-labelledby="l1 l2"
+           value="name from value"
+           alt="no name from alt"
+           src="no name from src"
+           data="no name from data"
+           title="no name from title"/>`,
+  expected: ['test2 test3', 'test1', 'test4', 'name from value']
+}, {
+  id: 'btn-image',
+  ruleset: 'HTMLInputImage',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn-image">test4</label>
+    <input id="btn-image"
+           type="image"
+           aria-label="test1"
+           aria-labelledby="l1 l2"
+           alt="name from alt"
+           value="name from value"
+           src="http://example.com/a11y/accessible/tests/mochitest/moz.png"
+           data="no name from data"
+           title="name from title"/>`,
+  expected: ['test2 test3', 'test1', 'test4', 'name from alt',
+    'name from value', 'name from title']
+}, {
+  id: 'btn-image',
+  ruleset: 'HTMLInputImageNoValidSrc',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="btn-image">test4</label>
+    <input id="btn-image"
+           type="image"
+           aria-label="test1"
+           aria-labelledby="l1 l2"
+           alt="name from alt"
+           value="name from value"
+           data="no name from data"
+           title="no name from title"/>`,
+  expected: ['test2 test3', 'test1', 'test4', 'name from alt',
+    'name from value']
+}, {
+  id: 'opt',
+  ruleset: 'HTMLOption',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <select>
+      <option id="opt"
+              aria-label="test1"
+              aria-labelledby="l1 l2"
+              label="test4"
+              title="test5">option1</option>
+      <option>option2</option>
+    </select>`,
+  expected: ['test2 test3', 'test1', 'test4', 'option1', 'test5']
+}, {
+  id: 'img',
+  ruleset: 'HTMLImg',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <img id="img"
+         aria-label="Logo of Mozilla"
+         aria-labelledby="l1 l2"
+         alt="Mozilla logo"
+         title="This is a logo"
+         src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+  expected: ['test2 test3', 'Logo of Mozilla', 'Mozilla logo', 'This is a logo']
+}, {
+  id: 'imgemptyalt',
+  ruleset: 'HTMLImgEmptyAlt',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <img id="imgemptyalt"
+         aria-label="Logo of Mozilla"
+         aria-labelledby="l1 l2"
+         title="This is a logo"
+         alt=""
+         src="http://example.com/a11y/accessible/tests/mochitest/moz.png"/>`,
+  expected: ['test2 test3', 'Logo of Mozilla', 'This is a logo', '']
+}, {
+  id: 'tc',
+  ruleset: 'HTMLElm',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="tc">test4</label>
+    <table>
+      <tr>
+        <td id="tc"
+            aria-label="test1"
+            aria-labelledby="l1 l2"
+            title="test5">
+          <p>This is a paragraph</p>
+          <a href="#">This is a link</a>
+          <ul>
+            <li>This is a list</li>
+          </ul>
+        </td>
+      </tr>
+    </table>`,
+  expected: ['test2 test3', 'test1', 'test5']
+}, {
+  id: 'gc',
+  ruleset: 'HTMLARIAGridCell',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <label for="gc">test4</label>
+    <table>
+      <tr>
+        <td id="gc"
+            role="gridcell"
+            aria-label="test1"
+            aria-labelledby="l1 l2"
+            title="This is a paragraph This is a link This is a list">
+          <p>This is a paragraph</p>
+          <a href="#">This is a link</a>
+          <ul>
+            <li>Listitem1</li>
+            <li>Listitem2</li>
+          </ul>
+        </td>
+      </tr>
+    </table>`,
+  expected: ['test2 test3', 'test1', 'This is a paragraph',
+    'This is a paragraph This is a link This is a list']
+}, {
+  id: 't',
+  ruleset: 'HTMLTable',
+  markup: `
+    <span id="l1">lby_tst6_1</span>
+    <span id="l2">lby_tst6_2</span>
+    <label for="t">label_tst6</label>
+    <table id="t"
+           aria-label="arialabel_tst6"
+           aria-labelledby="l1 l2"
+           summary="summary_tst6"
+           title="title_tst6">
+      <caption>caption_tst6</caption>
+      <tr>
+        <td>cell1</td>
+        <td>cell2</td>
+      </tr>
+    </table>`,
+  expected: ['lby_tst6_1 lby_tst6_2', 'arialabel_tst6', 'caption_tst6',
+    'summary_tst6', 'title_tst6']
+}, {
+  id: 'btn',
+  ruleset: 'CSSContent',
+  markup: `
+    <style>
+      button::before {
+        content: "do not ";
+      }
+    </style>
+    <button id="btn">press me</button>`,
+  expected: ['do not press me', 'press me']
+}, {
+  // TODO: uncomment when Bug-1256382 is resoved.
+  // id: 'li',
+  // ruleset: 'CSSContent',
+  // markup: `
+  //   <style>
+  //     ul {
+  //       list-style-type: decimal;
+  //     }
+  //   </style>
+  //   <ul id="ul">
+  //     <li id="li">Listitem</li>
+  //   </ul>`,
+  // expected: ['1. Listitem', `${String.fromCharCode(0x2022)} Listitem`]
+// }, {
+  id: 'a',
+  ruleset: 'HTMLLink',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <a id="a"
+       aria-label="test1"
+       aria-labelledby="l1 l2"
+       title="test4">test5</a>`,
+  expected: ['test2 test3', 'test1', 'test5', 'test4']
+}, {
+  id: 'a-img',
+  ruleset: 'HTMLLinkImage',
+  markup: `
+    <span id="l1">test2</span>
+    <span id="l2">test3</span>
+    <a id="a-img"
+       aria-label="test1"
+       aria-labelledby="l1 l2"
+       title="test4"><img alt="test5"/></a>`,
+  expected: ['test2 test3', 'test1', 'test5', 'test4']
+}];
+
+/**
+ * Wait for an accessible event to happen and, in case given accessible is
+ * defunct, update it to one that is attached to the accessible event.
+ * @param {Promise} onEvent      accessible event promise
+ * @param {Object}  target       { acc, parent, id } structure that contains an
+ *                                accessible, its parent and its content element
+ *                                id.
+ */
+function* updateAccessibleIfNeeded(onEvent, target) {
+  let event = yield onEvent;
+  if (isDefunct(target.acc)) {
+    target.acc = findAccessibleChildByID(event.accessible, target.id);
+  }
+}
+
+/**
+ * Test accessible name that is calculated from an attribute, remove the
+ * attribute before proceeding to the next name test. If attribute removal
+ * results in a reorder or text inserted event - wait for it. If accessible
+ * becomes defunct, update its reference using the one that is attached to one
+ * of the above events.
+ * @param {Object} browser      current "tabbrowser" element
+ * @param {Object} target       { acc, parent, id } structure that contains an
+ *                               accessible, its parent and its content element
+ *                               id.
+ * @param {Object} rule         current attr rule for name calculation
+ * @param {[type]} expected     expected name value
+ */
+function* testAttrRule(browser, target, rule, expected) {
+  testName(target.acc, expected);
+  let onEvent;
+  if (rule.recreated) {
+    onEvent = waitForEvent(EVENT_REORDER, target.parent);
+  } else if (rule.textchanged) {
+    onEvent = waitForEvent(EVENT_TEXT_INSERTED, target.id);
+  }
+  yield invokeSetAttribute(browser, target.id, rule.attr);
+  if (onEvent) {
+    yield updateAccessibleIfNeeded(onEvent, target);
+  }
+}
+
+/**
+ * Test accessible name that is calculated from an element name, remove the
+ * element before proceeding to the next name test. If element removal results
+ * in a reorder event - wait for it. If accessible becomes defunct, update its
+ * reference using the one that is attached to a possible reorder event.
+ * @param {Object} browser      current "tabbrowser" element
+ * @param {Object} target       { acc, parent, id } structure that contains an
+ *                               accessible, its parent and its content element
+ *                               id.
+ * @param {Object} rule         current elm rule for name calculation
+ * @param {[type]} expected     expected name value
+ */
+function* testElmRule(browser, target, rule, expected) {
+  testName(target.acc, expected);
+  let onEvent = waitForEvent(EVENT_REORDER, rule.isSibling ?
+    target.parent : target.id);
+  yield ContentTask.spawn(browser, rule.elm, elm =>
+    content.document.querySelector(`${elm}`).remove());
+  yield updateAccessibleIfNeeded(onEvent, target);
+}
+
+/**
+ * Test accessible name that is calculated from its subtree, remove the subtree
+ * and wait for a reorder event before proceeding to the next name test. If
+ * accessible becomes defunct, update its reference using the one that is
+ * attached to a reorder event.
+ * @param {Object} browser      current "tabbrowser" element
+ * @param {Object} target       { acc, parent, id } structure that contains an
+ *                               accessible, its parent and its content element
+ *                               id.
+ * @param {Object} rule         current subtree rule for name calculation
+ * @param {[type]} expected     expected name value
+ */
+function* testSubtreeRule(browser, target, rule, expected) {
+  testName(target.acc, expected);
+  let onEvent = waitForEvent(EVENT_REORDER, target.id);
+  yield ContentTask.spawn(browser, target.id, id => {
+    let elm = content.document.getElementById(id);
+    while (elm.firstChild) {
+      elm.removeChild(elm.firstChild);
+    }
+  });
+  yield updateAccessibleIfNeeded(onEvent, target);
+}
+
+/**
+ * Iterate over a list of rules and test accessible names for each one of the
+ * rules.
+ * @param {Object} browser      current "tabbrowser" element
+ * @param {Object} target       { acc, parent, id } structure that contains an
+ *                               accessible, its parent and its content element
+ *                               id.
+ * @param {Array}  ruleset      A list of rules to test a target with
+ * @param {Array}  expected     A list of expected name value for each rule
+ */
+function* testNameRule(browser, target, ruleset, expected) {
+  for (let i = 0; i < ruleset.length; ++i) {
+    let rule = ruleset[i];
+    let testFn;
+    if (rule.attr) {
+      testFn = testAttrRule;
+    } else if (rule.elm) {
+      testFn = testElmRule;
+    } else if (rule.fromsubtree) {
+      testFn = testSubtreeRule;
+    }
+    yield testFn(browser, target, rule, expected[i]);
+  }
+}
+
+markupTests.forEach(({ id, ruleset, markup, expected }) =>
+  addAccessibleTask(markup, function*(browser, accDoc) {
+    // Find a target accessible from an accessible subtree.
+    let acc = findAccessibleChildByID(accDoc, id);
+    // Find target's parent accessible from an accessible subtree.
+    let parent = getAccessibleDOMNodeID(acc.parent);
+    let target = { id, parent, acc };
+    yield testNameRule(browser, target, rules[ruleset], expected);
+  }));
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_events_caretmove.js
@@ -0,0 +1,21 @@
+/* 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/. */
+
+/* global EVENT_TEXT_CARET_MOVED, nsIAccessibleCaretMoveEvent */
+
+'use strict';
+
+/**
+ * Test caret move event and its interface:
+ *   - caretOffset
+ */
+addAccessibleTask('<input id="textbox" value="hello"/>', function*(browser) {
+  let onCaretMoved = waitForEvent(EVENT_TEXT_CARET_MOVED, 'textbox');
+  yield invokeFocus(browser, 'textbox');
+  let event = yield onCaretMoved;
+
+  let caretMovedEvent = event.QueryInterface(nsIAccessibleCaretMoveEvent);
+  is(caretMovedEvent.caretOffset, 5,
+    'Correct caret offset.');
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_events_hide.js
@@ -0,0 +1,32 @@
+/* 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/. */
+
+/* global EVENT_HIDE */
+
+'use strict';
+
+/**
+ * Test hide event and its interface:
+ *   - targetParent
+ *   - targetNextSibling
+ *   - targetPrevSibling
+ */
+addAccessibleTask(`
+  <div id="parent">
+    <div id="previous"></div>
+    <div id="div"></div>
+    <div id="next"></div>
+  </div>`, function*(browser) {
+  let onHide = waitForEvent(EVENT_HIDE, 'div');
+  yield invokeSetStyle(browser, 'div', 'visibility', 'hidden');
+  let event = yield onHide;
+  let hideEvent = event.QueryInterface(Ci.nsIAccessibleHideEvent);
+
+  is(getAccessibleDOMNodeID(hideEvent.targetParent), 'parent',
+    'Correct target parent.');
+  is(getAccessibleDOMNodeID(hideEvent.targetNextSibling), 'next',
+    'Correct target next sibling.');
+  is(getAccessibleDOMNodeID(hideEvent.targetPrevSibling), 'previous',
+    'Correct target previous sibling.');
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_events_show.js
@@ -0,0 +1,17 @@
+/* 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/. */
+
+/* global EVENT_SHOW */
+
+'use strict';
+
+/**
+ * Test show event
+ */
+addAccessibleTask('<div id="div" style="visibility: hidden;"></div>',
+  function*(browser) {
+    let onShow = waitForEvent(EVENT_SHOW, 'div');
+    yield invokeSetStyle(browser, 'div', 'visibility', 'visible');
+    yield onShow;
+  });
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_events_statechange.js
@@ -0,0 +1,60 @@
+/* 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/. */
+
+/* global STATE_CHECKED, EXT_STATE_EDITABLE, nsIAccessibleStateChangeEvent,
+          EVENT_STATE_CHANGE */
+
+'use strict';
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR },
+            { name: 'states.js', dir: MOCHITESTS_DIR });
+
+function checkStateChangeEvent(event, state, isExtraState, isEnabled) {
+  let scEvent = event.QueryInterface(nsIAccessibleStateChangeEvent);
+  is(scEvent.state, state, 'Correct state of the statechange event.');
+  is(scEvent.isExtraState, isExtraState,
+    'Correct extra state bit of the statechange event.');
+  is(scEvent.isEnabled, isEnabled, 'Correct state of statechange event state');
+}
+
+// Insert mock source into the iframe to be able to verify the right document
+// body id.
+let iframeSrc = `data:text/html,
+  <html>
+    <head>
+      <meta charset='utf-8'/>
+      <title>Inner Iframe</title>
+    </head>
+    <body id='iframe'></body>
+  </html>`;
+
+/**
+ * Test state change event and its interface:
+ *   - state
+ *   - isExtraState
+ *   - isEnabled
+ */
+addAccessibleTask(`
+  <iframe id="iframe" src="${iframeSrc}"></iframe>
+  <input id="checkbox" type="checkbox" />`, function*(browser) {
+  // Test state change
+  let onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'checkbox');
+  // Set checked for a checkbox.
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById('checkbox').checked = true);
+  let event = yield onStateChange;
+
+  checkStateChangeEvent(event, STATE_CHECKED, false, true);
+  testStates(event.accessible, STATE_CHECKED, 0);
+
+  // Test extra state
+  onStateChange = waitForEvent(EVENT_STATE_CHANGE, 'iframe');
+  // Set design mode on.
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById('iframe').contentDocument.designMode = 'on');
+  event = yield onStateChange;
+
+  checkStateChangeEvent(event, EXT_STATE_EDITABLE, true, true);
+  testStates(event.accessible, 0, EXT_STATE_EDITABLE);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_events_textchange.js
@@ -0,0 +1,72 @@
+/* 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/. */
+
+/* global EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
+          nsIAccessibleTextChangeEvent */
+
+'use strict';
+
+function checkTextChangeEvent(event, id, text, start, end, isInserted, isFromUserInput) {
+  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)}`);
+}
+
+function* changeText(browser, id, value, events) {
+  let onEvents = waitForMultipleEvents(events.map(({ isInserted }) => {
+    let eventType = isInserted ? EVENT_TEXT_INSERTED : EVENT_TEXT_REMOVED;
+    return { id, eventType };
+  }));
+  // Change text in the subtree.
+  yield ContentTask.spawn(browser, { id, value }, ({ id, value }) =>
+    content.document.getElementById(id).firstChild.textContent = value);
+  let resolvedEvents = yield onEvents;
+
+  events.forEach(({ isInserted, str, offset }, idx) =>
+    checkTextChangeEvent(resolvedEvents[idx],
+      id, str, offset, offset + str.length, isInserted, false));
+}
+
+function* removeTextFromInput(browser, id, value, start, end) {
+  let onTextRemoved = waitForEvent(EVENT_TEXT_REMOVED, id);
+  // Select text and delete it.
+  yield ContentTask.spawn(browser, { id, start, end }, ({ id, start, end }) => {
+    let el = content.document.getElementById(id);
+    el.focus();
+    el.setSelectionRange(start, end);
+  });
+  yield BrowserTestUtils.sendChar('VK_DELETE', browser);
+
+  let event = yield onTextRemoved;
+  checkTextChangeEvent(event, id, value, start, end, false, true);
+}
+
+/**
+ * Test text change event and its interface:
+ *   - start
+ *   - length
+ *   - isInserted
+ *   - modifiedText
+ *   - isFromUserInput
+ */
+addAccessibleTask(`
+  <p id="p">abc</p>
+  <input id="input" value="input" />`, function*(browser) {
+  let events = [
+    { isInserted: false, str: 'abc', offset: 0 },
+    { isInserted: true, str: 'def', offset: 0 }
+  ];
+  yield changeText(browser, 'p', 'def', events);
+
+  events = [{ isInserted: true, str: 'DEF', offset: 2 }];
+  yield changeText(browser, 'p', 'deDEFf', events);
+
+  // Test isFromUserInput property.
+  yield removeTextFromInput(browser, 'input', 'n', 1, 2);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_ariadialog.js
@@ -0,0 +1,42 @@
+/* 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';
+
+/* global EVENT_SHOW, ROLE_DIALOG, ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, ROLE_ENTRY,
+          ROLE_DOCUMENT */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+// Test ARIA Dialog
+addAccessibleTask('doc_treeupdate_ariadialog.html', function*(browser, accDoc) {
+  testAccessibleTree(accDoc, {
+    role: ROLE_DOCUMENT,
+    children: [ ]
+  });
+
+  // Make dialog visible and update its inner content.
+  let onShow = waitForEvent(EVENT_SHOW, 'dialog');
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById('dialog').style.display = 'block');
+  yield onShow;
+
+  testAccessibleTree(accDoc, {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_DIALOG,
+        children: [
+          {
+            role: ROLE_PUSHBUTTON,
+            children: [ { role: ROLE_TEXT_LEAF } ]
+          },
+          {
+            role: ROLE_ENTRY
+          }
+        ]
+      }
+    ]
+  });
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_ariaowns.js
@@ -0,0 +1,317 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testContainer1(browser, accDoc) {
+  const id = 't1_container';
+  const docID = getAccessibleDOMNodeID(accDoc);
+  const acc = findAccessibleChildByID(accDoc, id);
+
+  /* ================= Initial tree test ==================================== */
+  // children are swapped by ARIA owns
+  let tree = {
+    SECTION: [
+      { CHECKBUTTON: [
+        { SECTION: [] }
+      ] },
+      { PUSHBUTTON: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Change ARIA owns ====================================== */
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv');
+  yield onReorder;
+
+  // children are swapped again, button and subdiv are appended to
+  // the children.
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }, // checkbox, native order
+      { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+      { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Remove ARIA owns ====================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetAttribute(browser, id, 'aria-owns');
+  yield onReorder;
+
+  // children follow the DOM order
+  tree = {
+    SECTION: [
+      { PUSHBUTTON: [ ] },
+      { CHECKBUTTON: [
+          { SECTION: [] }
+      ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Set ARIA owns ========================================= */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetAttribute(browser, id, 'aria-owns', 't1_button t1_subdiv');
+  yield onReorder;
+
+  // children are swapped again, button and subdiv are appended to
+  // the children.
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }, // checkbox
+      { PUSHBUTTON: [ ] }, // button, rearranged by ARIA own
+      { SECTION: [ ] } // subdiv from the subtree, ARIA owned
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Add ID to ARIA owns =================================== */
+  onReorder = waitForEvent(EVENT_REORDER, docID);
+  yield invokeSetAttribute(browser, id, 'aria-owns',
+    't1_button t1_subdiv t1_group');
+  yield onReorder;
+
+  // children are swapped again, button and subdiv are appended to
+  // the children.
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }, // t1_checkbox
+      { PUSHBUTTON: [ ] }, // button, t1_button
+      { SECTION: [ ] }, // subdiv from the subtree, t1_subdiv
+      { GROUPING: [ ] } // group from outside, t1_group
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Append element ======================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let div = content.document.createElement('div');
+    div.setAttribute('id', 't1_child3');
+    div.setAttribute('role', 'radio');
+    content.document.getElementById(id).appendChild(div);
+  });
+  yield onReorder;
+
+  // children are invalidated, they includes aria-owns swapped kids and
+  // newly inserted child.
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }, // existing explicit, t1_checkbox
+      { RADIOBUTTON: [ ] }, // new explicit, t1_child3
+      { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+      { SECTION: [ ] }, // ARIA owned, t1_subdiv
+      { GROUPING: [ ] } // ARIA owned, t1_group
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Remove element ======================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById('t1_span').parentNode.removeChild(
+      content.document.getElementById('t1_span')));
+  yield onReorder;
+
+  // subdiv should go away
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }, // explicit, t1_checkbox
+      { RADIOBUTTON: [ ] }, // explicit, t1_child3
+      { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+      { GROUPING: [ ] } // ARIA owned, t1_group
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Remove ID ============================================= */
+  onReorder = waitForEvent(EVENT_REORDER, docID);
+  yield invokeSetAttribute(browser, 't1_group', 'id');
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] },
+      { RADIOBUTTON: [ ] },
+      { PUSHBUTTON: [ ] } // ARIA owned, t1_button
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================ Set ID ================================================ */
+  onReorder = waitForEvent(EVENT_REORDER, docID);
+  yield invokeSetAttribute(browser, 't1_grouptmp', 'id', 't1_group');
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] },
+      { RADIOBUTTON: [ ] },
+      { PUSHBUTTON: [ ] }, // ARIA owned, t1_button
+      { GROUPING: [ ] } // ARIA owned, t1_group, previously t1_grouptmp
+    ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+function* removeContainer(browser, accDoc) {
+  const id = 't2_container1';
+  const acc = findAccessibleChildByID(accDoc, id);
+
+  let tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] } // ARIA owned, 't2_owned'
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.getElementById('t2_container2').removeChild(
+      content.document.getElementById('t2_container3')));
+  yield onReorder;
+
+  tree = {
+    SECTION: [ ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+function* stealAndRecacheChildren(browser, accDoc) {
+  const id1 = 't3_container1';
+  const id2 = 't3_container2';
+  const acc1 = findAccessibleChildByID(accDoc, id1);
+  const acc2 = findAccessibleChildByID(accDoc, id2);
+
+  /* ================ Steal from other ARIA owns ============================ */
+  let onReorder = waitForEvent(EVENT_REORDER, id2);
+  yield invokeSetAttribute(browser, id2, 'aria-owns', 't3_child');
+  yield onReorder;
+
+  let tree = {
+    SECTION: [ ]
+  };
+  testAccessibleTree(acc1, tree);
+
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [ ] }
+    ]
+  };
+  testAccessibleTree(acc2, tree);
+
+  /* ================ Append element to recache children ==================== */
+  onReorder = waitForEvent(EVENT_REORDER, id2);
+  yield ContentTask.spawn(browser, id2, id => {
+    let div = content.document.createElement('div');
+    div.setAttribute('role', 'radio');
+    content.document.getElementById(id).appendChild(div);
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [ ]
+  };
+  testAccessibleTree(acc1, tree);
+
+  tree = {
+    SECTION: [
+      { RADIOBUTTON: [ ] },
+      { CHECKBUTTON: [ ] } // ARIA owned
+    ]
+  };
+  testAccessibleTree(acc2, tree);
+}
+
+function* showHiddenElement(browser, accDoc) {
+  const id = 't4_container1';
+  const acc = findAccessibleChildByID(accDoc, id);
+
+  let tree = {
+    SECTION: [
+      { RADIOBUTTON: [] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetStyle(browser, 't4_child1', 'display', 'block');
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { CHECKBUTTON: [] },
+      { RADIOBUTTON: [] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+function* rearrangeARIAOwns(browser, accDoc) {
+  const id = 't5_container';
+  const acc = findAccessibleChildByID(accDoc, id);
+  const tests = [{
+    val: 't5_checkbox t5_radio t5_button',
+    roleList: [ 'CHECKBUTTON', 'RADIOBUTTON', 'PUSHBUTTON' ]
+  }, {
+    val: 't5_radio t5_button t5_checkbox',
+    roleList: [ 'RADIOBUTTON', 'PUSHBUTTON', 'CHECKBUTTON' ]
+  }];
+
+  for (let { val, roleList } of tests) {
+    let onReorder = waitForEvent(EVENT_REORDER, id);
+    yield invokeSetAttribute(browser, id, 'aria-owns', val);
+    yield onReorder;
+
+    let tree = { SECTION: [ ] };
+    for (let role of roleList) {
+      let ch = {};
+      ch[role] = [];
+      tree.SECTION.push(ch);
+    }
+    testAccessibleTree(acc, tree);
+  }
+}
+
+function* removeNotARIAOwnedEl(browser, accDoc) {
+  const id = 't6_container';
+  const acc = findAccessibleChildByID(accDoc, id);
+
+  let tree = {
+    SECTION: [
+      { TEXT_LEAF: [ ] },
+      { GROUPING: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id =>
+    content.document.getElementById(id).removeChild(
+      content.document.getElementById('t6_span')));
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { GROUPING: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask('doc_treeupdate_ariaowns.html', function*(browser, accDoc) {
+  yield testContainer1(browser, accDoc);
+  yield removeContainer(browser, accDoc);
+  yield stealAndRecacheChildren(browser, accDoc);
+  yield showHiddenElement(browser, accDoc);
+  yield rearrangeARIAOwns(browser, accDoc);
+  yield removeNotARIAOwnedEl(browser, accDoc);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_canvas.js
@@ -0,0 +1,25 @@
+/* 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';
+
+/* global EVENT_SHOW */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+  <canvas id="canvas">
+    <div id="dialog" role="dialog" style="display: none;"></div>
+  </canvas>`, function*(browser, accDoc) {
+  let canvas = findAccessibleChildByID(accDoc, 'canvas');
+  let dialog = findAccessibleChildByID(accDoc, 'dialog');
+
+  testAccessibleTree(canvas, { CANVAS: [] });
+
+  let onShow = waitForEvent(EVENT_SHOW, 'dialog');
+  yield invokeSetStyle(browser, 'dialog', 'display', 'block');
+  yield onShow;
+
+  testAccessibleTree(dialog, { DIALOG: [] });
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_cssoverflow.js
@@ -0,0 +1,64 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+  <div id="container"><div id="scrollarea" style="overflow:auto;"><input>
+  </div></div>
+  <div id="container2"><div id="scrollarea2" style="overflow:hidden;">
+  </div></div>`, function*(browser, accDoc) {
+  const id1 = 'container';
+  const id2 = 'container2';
+  const container = findAccessibleChildByID(accDoc, id1);
+  const container2 = findAccessibleChildByID(accDoc, id2);
+
+  /* ================= Change scroll range ================================== */
+  let tree = {
+    SECTION: [ {// container
+      SECTION: [ {// scroll area
+        ENTRY: [ ] // child content
+      } ]
+    } ]
+  };
+  testAccessibleTree(container, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, id1);
+  yield ContentTask.spawn(browser, id1, id => {
+    let doc = content.document;
+    doc.getElementById('scrollarea').style.width = '20px';
+    doc.getElementById(id).appendChild(doc.createElement('input'));
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [ {// container
+      SECTION: [ {// scroll area
+        ENTRY: [ ] // child content
+      } ]
+    }, {
+      ENTRY: [ ] // inserted input
+    } ]
+  };
+  testAccessibleTree(container, tree);
+
+  /* ================= Change scrollbar styles ============================== */
+  tree = { SECTION: [ ] };
+  testAccessibleTree(container2, tree);
+
+  onReorder = waitForEvent(EVENT_REORDER, id2);
+  yield invokeSetStyle(browser, 'scrollarea2', 'overflow', 'auto');
+  yield onReorder;
+
+  tree = {
+    SECTION: [ // container
+      { SECTION: [] } // scroll area
+    ]
+  };
+  testAccessibleTree(container2, tree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_doc.js
@@ -0,0 +1,303 @@
+/* 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';
+
+/* global ROLE_PUSHBUTTON, ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_DOCUMENT,
+          nsIAccessibleDocument */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+const iframeSrc = `data:text/html,
+  <html>
+    <head>
+      <meta charset='utf-8'/>
+      <title>Inner Iframe</title>
+    </head>
+    <body id='inner-iframe'></body>
+  </html>`;
+
+addAccessibleTask(`
+  <iframe id="iframe" src="${iframeSrc}"></iframe>`, function*(browser, accDoc) {
+  // ID of the iframe that is being tested
+  const id = 'inner-iframe';
+
+  let iframe = findAccessibleChildByID(accDoc, id);
+
+  /* ================= Initial tree check =================================== */
+  let tree = {
+    role: ROLE_DOCUMENT,
+    children: [ ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Write iframe document ================================ */
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let newHTMLNode = docNode.createElement('html');
+    let newBodyNode = docNode.createElement('body');
+    let newTextNode = docNode.createTextNode('New Wave');
+    newBodyNode.id = id;
+    newBodyNode.appendChild(newTextNode);
+    newHTMLNode.appendChild(newBodyNode);
+    docNode.replaceChild(newHTMLNode, docNode.documentElement);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'New Wave'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Replace iframe HTML element ========================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    // We can't use open/write/close outside of iframe document because of
+    // security error.
+    let script = docNode.createElement('script');
+    script.textContent = `
+      document.open();
+      document.write('<body id="${id}">hello</body>');
+      document.close();`;
+    docNode.body.appendChild(script);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'hello'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Replace iframe body ================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let newBodyNode = docNode.createElement('body');
+    let newTextNode = docNode.createTextNode('New Hello');
+    newBodyNode.id = id;
+    newBodyNode.appendChild(newTextNode);
+    newBodyNode.setAttribute('role', 'button');
+    docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_PUSHBUTTON,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'New Hello'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Open iframe document ================================= */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    // Open document.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let script = docNode.createElement('script');
+    script.textContent = `
+      function closeMe() {
+        document.write('Works?');
+        document.close();
+      }
+      window.closeMe = closeMe;
+      document.open();
+      document.write('<body id="${id}"></body>');`;
+    docNode.body.appendChild(script);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [ ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Close iframe document ================================ */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () => {
+    // Write and close document.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    docNode.write('Works?');
+    docNode.close();
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'Works?'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Remove HTML from iframe document ===================== */
+  onReorder = waitForEvent(EVENT_REORDER);
+  yield ContentTask.spawn(browser, {}, () => {
+    // Remove HTML element.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    docNode.removeChild(docNode.firstChild);
+  });
+  let event = yield onReorder;
+
+  ok(event.accessible instanceof nsIAccessibleDocument,
+    'Reorder should happen on the document');
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [ ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Insert HTML to iframe document ======================= */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    // Insert HTML element.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let html = docNode.createElement('html');
+    let body = docNode.createElement('body');
+    let text = docNode.createTextNode('Haha');
+    body.appendChild(text);
+    body.id = id;
+    html.appendChild(body);
+    docNode.appendChild(html);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'Haha'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Remove body from iframe document ===================== */
+  onReorder = waitForEvent(EVENT_REORDER);
+  yield ContentTask.spawn(browser, {}, () => {
+    // Remove body element.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    docNode.documentElement.removeChild(docNode.body);
+  });
+  event = yield onReorder;
+
+  ok(event.accessible instanceof nsIAccessibleDocument,
+    'Reorder should happen on the document');
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [ ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================ Insert element under document element while body missed */
+  onReorder = waitForEvent(EVENT_REORDER);
+  yield ContentTask.spawn(browser, {}, () => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let inputNode = content.window.inputNode = docNode.createElement('input');
+    docNode.documentElement.appendChild(inputNode);
+  });
+  event = yield onReorder;
+
+  ok(event.accessible instanceof nsIAccessibleDocument,
+    'Reorder should happen on the document');
+  tree = {
+    DOCUMENT: [
+      { ENTRY: [ ] }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  yield ContentTask.spawn(browser, {}, () => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    // Remove aftermath of this test before next test starts.
+    docNode.documentElement.removeChild(content.window.inputNode);
+  });
+
+  /* ================= Insert body to iframe document ======================= */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    // Write and close document.
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    // Insert body element.
+    let body = docNode.createElement('body');
+    let text = docNode.createTextNode('Yo ho ho i butylka roma!');
+    body.appendChild(text);
+    body.id = id;
+    docNode.documentElement.appendChild(body);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_DOCUMENT,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'Yo ho ho i butylka roma!'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+
+  /* ================= Change source ======================================== */
+  onReorder = waitForEvent(EVENT_REORDER, 'iframe');
+  yield invokeSetAttribute(browser, 'iframe', 'src',
+    `data:text/html,<html><body id="${id}"><input></body></html>`);
+  event = yield onReorder;
+
+  tree = {
+    INTERNAL_FRAME: [
+      { DOCUMENT: [
+        { ENTRY: [ ] }
+      ] }
+    ]
+  };
+  testAccessibleTree(event.accessible, tree);
+  iframe = findAccessibleChildByID(event.accessible, id);
+
+  /* ================= Replace iframe body on ARIA role body ================ */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let docNode = content.document.getElementById('iframe').contentDocument;
+    let newBodyNode = docNode.createElement('body');
+    let newTextNode = docNode.createTextNode('New Hello');
+    newBodyNode.appendChild(newTextNode);
+    newBodyNode.setAttribute('role', 'button');
+    newBodyNode.id = id;
+    docNode.documentElement.replaceChild(newBodyNode, docNode.body);
+  });
+  yield onReorder;
+
+  tree = {
+    role: ROLE_PUSHBUTTON,
+    children: [
+      {
+        role: ROLE_TEXT_LEAF,
+        name: 'New Hello'
+      }
+    ]
+  };
+  testAccessibleTree(iframe, tree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_gencontent.js
@@ -0,0 +1,78 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+  <style>
+    .gentext:before {
+      content: "START"
+    }
+    .gentext:after {
+      content: "END"
+    }
+  </style>
+  <div id="container1"></div>
+  <div id="container2"><div id="container2_child">text</div></div>`,
+  function*(browser, accDoc) {
+    const id1 = 'container1';
+    const id2 = 'container2';
+    let container1 = findAccessibleChildByID(accDoc, id1);
+    let container2 = findAccessibleChildByID(accDoc, id2);
+
+    let tree = {
+      SECTION: [ ] // container
+    };
+    testAccessibleTree(container1, tree);
+
+    tree = {
+      SECTION: [ { // container2
+        SECTION: [ { // container2 child
+          TEXT_LEAF: [ ] // primary text
+        } ]
+      } ]
+    };
+    testAccessibleTree(container2, tree);
+
+    let onReorder = waitForEvent(EVENT_REORDER, id1);
+    // Create and add an element with CSS generated content to container1
+    yield ContentTask.spawn(browser, id1, id => {
+      let node = content.document.createElement('div');
+      node.textContent = 'text';
+      node.setAttribute('class', 'gentext');
+      content.document.getElementById(id).appendChild(node);
+    });
+    yield onReorder;
+
+    tree = {
+      SECTION: [ // container
+        { SECTION: [ // inserted node
+          { STATICTEXT: [] }, // :before
+          { TEXT_LEAF: [] }, // primary text
+          { STATICTEXT: [] } // :after
+        ] }
+      ]
+    };
+    testAccessibleTree(container1, tree);
+
+    onReorder = waitForEvent(EVENT_REORDER, id2);
+    // Add CSS generated content to an element in container2's subtree
+    yield invokeSetAttribute(browser, 'container2_child', 'class', 'gentext');
+    yield onReorder;
+
+    tree = {
+      SECTION: [ // container2
+        { SECTION: [ // container2 child
+          { STATICTEXT: [] }, // :before
+          { TEXT_LEAF: [] }, // primary text
+          { STATICTEXT: [] } // :after
+        ] }
+      ]
+    };
+    testAccessibleTree(container2, tree);
+  });
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_hidden.js
@@ -0,0 +1,30 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* setHidden(browser, value) {
+  let onReorder = waitForEvent(EVENT_REORDER, 'container');
+  yield invokeSetAttribute(browser, 'child', 'hidden', value);
+  yield onReorder;
+}
+
+addAccessibleTask('<div id="container"><input id="child"></div>',
+  function*(browser, accDoc) {
+    let container = findAccessibleChildByID(accDoc, 'container');
+
+    testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] });
+
+    // Set @hidden attribute
+    yield setHidden(browser, 'true');
+    testAccessibleTree(container, { SECTION: [ ] });
+
+    // Remove @hidden attribute
+    yield setHidden(browser);
+    testAccessibleTree(container, { SECTION: [ { ENTRY: [ ] } ] });
+  });
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_imagemap.js
@@ -0,0 +1,176 @@
+/* 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';
+
+/* global EVENT_REORDER, ROLE_LINK */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testImageMap(browser, accDoc) {
+  const id = 'imgmap';
+  const acc = findAccessibleChildByID(accDoc, id);
+
+  /* ================= Initial tree test ==================================== */
+  let tree = {
+    IMAGE_MAP: [
+      { role: ROLE_LINK, name: 'b', children: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Insert area ========================================== */
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () => {
+    let areaElm = content.document.createElement('area');
+    let mapNode = content.document.getElementById('map');
+    areaElm.setAttribute('href',
+                         'http://www.bbc.co.uk/radio4/atoz/index.shtml#a');
+    areaElm.setAttribute('coords', '0,0,13,14');
+    areaElm.setAttribute('alt', 'a');
+    areaElm.setAttribute('shape', 'rect');
+    mapNode.insertBefore(areaElm, mapNode.firstChild);
+  });
+  yield onReorder;
+
+  tree = {
+    IMAGE_MAP: [
+      { role: ROLE_LINK, name: 'a', children: [ ] },
+      { role: ROLE_LINK, name: 'b', children: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Append area ========================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () => {
+    let areaElm = content.document.createElement('area');
+    let mapNode = content.document.getElementById('map');
+    areaElm.setAttribute('href',
+                         'http://www.bbc.co.uk/radio4/atoz/index.shtml#c');
+    areaElm.setAttribute('coords', '34,0,47,14');
+    areaElm.setAttribute('alt', 'c');
+    areaElm.setAttribute('shape', 'rect');
+    mapNode.appendChild(areaElm);
+  });
+  yield onReorder;
+
+  tree = {
+    IMAGE_MAP: [
+      { role: ROLE_LINK, name: 'a', children: [ ] },
+      { role: ROLE_LINK, name: 'b', children: [ ] },
+      { role: ROLE_LINK, name: 'c', children: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Remove area ========================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () => {
+    let mapNode = content.document.getElementById('map');
+    mapNode.removeChild(mapNode.firstElementChild);
+  });
+  yield onReorder;
+
+  tree = {
+    IMAGE_MAP: [
+      { role: ROLE_LINK, name: 'b', children: [ ] },
+      { role: ROLE_LINK, name: 'c', children: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+function* testContainer(browser) {
+  const id = 'container';
+  /* ================= Remove name on map =================================== */
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetAttribute(browser, 'map', 'name');
+  let event = yield onReorder;
+  const acc = event.accessible;
+
+  let tree = {
+    SECTION: [
+      { GRAPHIC: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Restore name on map ================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetAttribute(browser, 'map', 'name', 'atoz_map');
+  // XXX: force repainting of the image (see bug 745788 for details).
+  yield BrowserTestUtils.synthesizeMouse('#imgmap', 10, 10,
+    { type: 'mousemove' }, browser);
+  yield onReorder;
+
+  tree = {
+    SECTION: [ {
+      IMAGE_MAP: [
+        { LINK: [ ] },
+        { LINK: [ ] }
+      ]
+    } ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Remove map =========================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, {}, () => {
+    let mapNode = content.document.getElementById('map');
+    mapNode.parentNode.removeChild(mapNode);
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { GRAPHIC: [ ] }
+    ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Insert map =========================================== */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id => {
+    let map = content.document.createElement('map');
+    let area = content.document.createElement('area');
+
+    map.setAttribute('name', 'atoz_map');
+    map.setAttribute('id', 'map');
+
+    area.setAttribute('href',
+                      'http://www.bbc.co.uk/radio4/atoz/index.shtml#b');
+    area.setAttribute('coords', '17,0,30,14');
+    area.setAttribute('alt', 'b');
+    area.setAttribute('shape', 'rect');
+
+    map.appendChild(area);
+    content.document.getElementById(id).appendChild(map);
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [ {
+      IMAGE_MAP: [
+        { LINK: [ ] }
+      ]
+    } ]
+  };
+  testAccessibleTree(acc, tree);
+
+  /* ================= Hide image map ======================================= */
+  onReorder = waitForEvent(EVENT_REORDER, id);
+  yield invokeSetStyle(browser, 'imgmap', 'display', 'none');
+  yield onReorder;
+
+  tree = {
+    SECTION: [ ]
+  };
+  testAccessibleTree(acc, tree);
+}
+
+addAccessibleTask('doc_treeupdate_imagemap.html', function*(browser, accDoc) {
+  yield testImageMap(browser, accDoc);
+  yield testContainer(browser);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_list.js
@@ -0,0 +1,43 @@
+/* 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';
+
+/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_STATICTEXT, ROLE_LISTITEM */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* setDisplayAndWaitForReorder(browser, value) {
+  let onReorder = waitForEvent(EVENT_REORDER, 'ul');
+  yield invokeSetStyle(browser, 'li', 'display', value);
+  return yield onReorder;
+}
+
+addAccessibleTask(`
+  <ul id="ul">
+    <li id="li">item1</li>
+  </ul>`, function*(browser, accDoc) {
+  let li = findAccessibleChildByID(accDoc, 'li');
+  let bullet = li.firstChild;
+  let accTree = {
+    role: ROLE_LISTITEM,
+    children: [ {
+      role: ROLE_STATICTEXT,
+      children: []
+    }, {
+      role: ROLE_TEXT_LEAF,
+      children: []
+    } ]
+  };
+  testAccessibleTree(li, accTree);
+
+  yield setDisplayAndWaitForReorder(browser, 'none');
+
+  ok(isDefunct(li), 'Check that li is defunct.');
+  ok(isDefunct(bullet), 'Check that bullet is defunct.');
+
+  let event = yield setDisplayAndWaitForReorder(browser, 'list-item');
+
+  testAccessibleTree(findAccessibleChildByID(event.accessible, 'li'), accTree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_list_editabledoc.js
@@ -0,0 +1,39 @@
+/* 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';
+
+/* global ROLE_TEXT_LEAF, EVENT_REORDER, ROLE_LISTITEM, ROLE_LIST,
+          ROLE_STATICTEXT */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<ol id="list"></ol>', function*(browser, accDoc) {
+  let list = findAccessibleChildByID(accDoc, 'list');
+
+  testAccessibleTree(list, {
+    role: ROLE_LIST,
+    children: [ ]
+  });
+
+  yield invokeSetAttribute(browser, 'body', 'contentEditable', 'true');
+  let onReorder = waitForEvent(EVENT_REORDER, 'list');
+  yield ContentTask.spawn(browser, {}, () => {
+    let li = content.document.createElement('li');
+    li.textContent = 'item';
+    content.document.getElementById('list').appendChild(li);
+  });
+  yield onReorder;
+
+  testAccessibleTree(list, {
+    role: ROLE_LIST,
+    children: [ {
+      role: ROLE_LISTITEM,
+      children: [
+        { role: ROLE_STATICTEXT, name: "1. ", children: [] },
+        { role: ROLE_TEXT_LEAF, children: [] }
+      ]
+    } ]
+  });
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_listener.js
@@ -0,0 +1,43 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<span id="parent"><span id="child"></span></span>',
+  function*(browser, accDoc) {
+    is(findAccessibleChildByID(accDoc, 'parent'), null,
+      'Check that parent is not accessible.');
+    is(findAccessibleChildByID(accDoc, 'child'), null,
+      'Check that child is not accessible.');
+
+    let onReorder = waitForEvent(EVENT_REORDER, 'body');
+    // Add an event listener to parent.
+    yield ContentTask.spawn(browser, {}, () => {
+      content.window.dummyListener = () => {};
+      content.document.getElementById('parent').addEventListener(
+        'click', content.window.dummyListener);
+    });
+    yield onReorder;
+
+    let tree = { TEXT: [] };
+    testAccessibleTree(findAccessibleChildByID(accDoc, 'parent'), tree);
+
+    onReorder = waitForEvent(EVENT_REORDER, 'body');
+    // Remove an event listener from parent.
+    yield ContentTask.spawn(browser, {}, () => {
+      content.document.getElementById('parent').removeEventListener(
+        'click', content.window.dummyListener);
+      delete content.window.dummyListener;
+    });
+    yield onReorder;
+
+    is(findAccessibleChildByID(accDoc, 'parent'), null,
+      'Check that parent is not accessible.');
+    is(findAccessibleChildByID(accDoc, 'child'), null,
+      'Check that child is not accessible.');
+  });
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_optgroup.js
@@ -0,0 +1,91 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('<select id="select"></select>', function*(browser, accDoc) {
+  let select = findAccessibleChildByID(accDoc, 'select');
+
+  let onEvent = waitForEvent(EVENT_REORDER, 'select');
+  // Create a combobox with grouping and 2 standalone options
+  yield ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    let select = doc.getElementById('select');
+    let optGroup = doc.createElement('optgroup');
+
+    for (let i = 0; i < 2; i++) {
+      let opt = doc.createElement('option');
+      opt.value = i;
+      opt.text = 'Option: Value ' + i;
+      optGroup.appendChild(opt);
+    }
+    select.add(optGroup, null);
+
+    for (let i = 0; i < 2; i++) {
+      let opt = doc.createElement('option');
+      select.add(opt, null);
+    }
+    select.firstChild.firstChild.id = 'option1Node';
+  });
+  let event = yield onEvent;
+  let option1Node = findAccessibleChildByID(event.accessible, 'option1Node');
+
+  let tree = {
+    COMBOBOX: [ {
+      COMBOBOX_LIST: [ {
+        GROUPING: [
+          { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] },
+          { COMBOBOX_OPTION: [ { TEXT_LEAF: [] } ] }
+        ]
+      }, {
+        COMBOBOX_OPTION: []
+      }, {
+        COMBOBOX_OPTION: []
+      } ]
+    } ]
+  };
+  testAccessibleTree(select, tree);
+  ok(!isDefunct(option1Node), 'option shouldn\'t be defunct');
+
+  onEvent = waitForEvent(EVENT_REORDER, 'select');
+  // Remove grouping from combobox
+  yield ContentTask.spawn(browser, {}, () => {
+    let select = content.document.getElementById('select');
+    select.removeChild(select.firstChild);
+  });
+  yield onEvent;
+
+  tree = {
+    COMBOBOX: [ {
+      COMBOBOX_LIST: [
+        { COMBOBOX_OPTION: [] },
+        { COMBOBOX_OPTION: [] }
+      ]
+    } ]
+  };
+  testAccessibleTree(select, tree);
+  ok(isDefunct(option1Node),
+    'removed option shouldn\'t be accessible anymore!');
+
+  onEvent = waitForEvent(EVENT_REORDER, 'select');
+  // Remove all options from combobox
+  yield ContentTask.spawn(browser, {}, () => {
+    let select = content.document.getElementById('select');
+    while (select.length) {
+      select.remove(0);
+    }
+  });
+  yield onEvent;
+
+  tree = {
+    COMBOBOX: [ {
+      COMBOBOX_LIST: [ ]
+    } ]
+  };
+  testAccessibleTree(select, tree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_removal.js
@@ -0,0 +1,39 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('doc_treeupdate_removal.xhtml', function*(browser, accDoc) {
+  ok(isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+    'table should be accessible');
+
+  // Move the_table element into hidden subtree.
+  let onReorder = waitForEvent(EVENT_REORDER, 'body');
+  yield ContentTask.spawn(browser, {}, () => content.document.getElementById(
+    'the_displaynone').appendChild(content.document.getElementById(
+      'the_table')));
+  yield onReorder;
+
+  ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+    'table in display none tree shouldn\'t be accessible');
+  ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')),
+    'row shouldn\'t be accessible');
+
+  // Remove the_row element (since it did not have accessible, no event needed).
+  yield ContentTask.spawn(browser, {}, () =>
+    content.document.body.removeChild(
+      content.document.getElementById('the_row')));
+
+  // make sure no accessibles have stuck around.
+  ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_row')),
+    'row shouldn\'t be accessible');
+  ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_table')),
+    'table shouldn\'t be accessible');
+  ok(!isAccessible(findAccessibleChildByID(accDoc, 'the_displayNone')),
+    'display none things shouldn\'t be accessible');
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_table.js
@@ -0,0 +1,51 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask(`
+  <table id="table">
+    <tr>
+      <td>cell1</td>
+      <td>cell2</td>
+    </tr>
+  </table>`, function*(browser, accDoc) {
+  let table = findAccessibleChildByID(accDoc, 'table');
+
+  let tree = {
+    TABLE: [
+      { ROW: [
+        { CELL: [ {TEXT_LEAF: [] }]},
+        { CELL: [ {TEXT_LEAF: [] }]}
+      ] }
+    ]
+  };
+  testAccessibleTree(table, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, 'table');
+  yield ContentTask.spawn(browser, {}, () => {
+    // append a caption, it should appear as a first element in the
+    // accessible tree.
+    let doc = content.document;
+    let caption = doc.createElement('caption');
+    caption.textContent = 'table caption';
+    doc.getElementById('table').appendChild(caption);
+  });
+  yield onReorder;
+
+  tree = {
+    TABLE: [
+      { CAPTION: [ { TEXT_LEAF: [] } ] },
+      { ROW: [
+        { CELL: [ {TEXT_LEAF: [] }]},
+        { CELL: [ {TEXT_LEAF: [] }]}
+      ] }
+    ]
+  };
+  testAccessibleTree(table, tree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_textleaf.js
@@ -0,0 +1,34 @@
+/* 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';
+
+/* global EVENT_REORDER, ROLE_TEXT_CONTAINER ROLE_PARAGRAPH, ROLE_TEXT_LEAF */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* removeTextData(browser, accessible, id, role) {
+  let tree = {
+    role: role,
+    children: [ { role: ROLE_TEXT_LEAF, name: "text" } ]
+  };
+  testAccessibleTree(accessible, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, id);
+  yield ContentTask.spawn(browser, id, id =>
+    content.document.getElementById(id).firstChild.textContent = '');
+  yield onReorder;
+
+  tree = { role: role, children: [] };
+  testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask(`
+  <p id="p">text</p>
+  <pre id="pre">text</pre>`, function*(browser, accDoc) {
+  let p = findAccessibleChildByID(accDoc, 'p');
+  let pre = findAccessibleChildByID(accDoc, 'pre');
+  yield removeTextData(browser, p, 'p', ROLE_PARAGRAPH);
+  yield removeTextData(browser, pre, 'pre', ROLE_TEXT_CONTAINER);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_visibility.js
@@ -0,0 +1,196 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+function* testTreeOnHide(browser, accDoc, containerID, id, before, after) {
+  let acc = findAccessibleChildByID(accDoc, containerID);
+  testAccessibleTree(acc, before);
+
+  let onReorder = waitForEvent(EVENT_REORDER, containerID);
+  yield invokeSetStyle(browser, id, 'visibility', 'hidden');
+  yield onReorder;
+
+  testAccessibleTree(acc, after);
+}
+
+function* test3(browser, accessible) {
+  let tree = {
+    SECTION: [ // container
+      { SECTION: [ // parent
+        { SECTION: [ // child
+          { TEXT_LEAF: [] }
+        ] }
+      ] },
+      { SECTION: [ // parent2
+        { SECTION: [ // child2
+          { TEXT_LEAF: [] }
+        ] }
+      ] }
+    ] };
+  testAccessibleTree(accessible, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, 't3_container');
+  yield ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    doc.getElementById('t3_container').style.color = 'red';
+    doc.getElementById('t3_parent').style.visibility = 'hidden';
+    doc.getElementById('t3_parent2').style.visibility = 'hidden';
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [ // container
+      { SECTION: [ // child
+        { TEXT_LEAF: [] }
+      ] },
+      { SECTION: [ // child2
+        { TEXT_LEAF: [] }
+      ] }
+    ] };
+  testAccessibleTree(accessible, tree);
+}
+
+function* test4(browser, accessible) {
+  let tree = {
+    SECTION: [
+      { TABLE: [
+        { ROW: [
+          { CELL: [ ] }
+        ] }
+      ] }
+    ] };
+  testAccessibleTree(accessible, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, 't4_parent');
+  yield ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    doc.getElementById('t4_container').style.color = 'red';
+    doc.getElementById('t4_child').style.visibility = 'visible';
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [{
+      TABLE: [{
+        ROW: [{
+          CELL: [{
+            SECTION: [{
+              TEXT_LEAF: []
+            }]
+          }]
+        }]
+      }]
+    }]
+  };
+  testAccessibleTree(accessible, tree);
+}
+
+addAccessibleTask('doc_treeupdate_visibility.html', function*(browser, accDoc) {
+  let t3Container = findAccessibleChildByID(accDoc, 't3_container');
+  let t4Container = findAccessibleChildByID(accDoc, 't4_container');
+
+  yield testTreeOnHide(browser, accDoc, 't1_container', 't1_parent', {
+    SECTION: [{
+      SECTION: [{
+        SECTION: [ { TEXT_LEAF: [] } ]
+      }]
+    }]
+  }, {
+    SECTION: [ {
+      SECTION: [ { TEXT_LEAF: [] } ]
+    } ]
+  });
+
+  yield testTreeOnHide(browser, accDoc, 't2_container', 't2_grandparent', {
+    SECTION: [{ // container
+      SECTION: [{ // grand parent
+        SECTION: [{
+          SECTION: [{ // child
+            TEXT_LEAF: []
+          }]
+        }, {
+          SECTION: [{ // child2
+            TEXT_LEAF: []
+          }]
+        }]
+      }]
+    }]
+  }, {
+    SECTION: [{ // container
+      SECTION: [{ // child
+        TEXT_LEAF: []
+      }]
+    }, {
+      SECTION: [{ // child2
+        TEXT_LEAF: []
+      }]
+    }]
+  });
+
+  yield test3(browser, t3Container);
+  yield test4(browser, t4Container);
+
+  yield testTreeOnHide(browser, accDoc, 't5_container', 't5_subcontainer', {
+    SECTION: [{ // container
+      SECTION: [{ // subcontainer
+        TABLE: [{
+          ROW: [{
+            CELL: [{
+              SECTION: [{ // child
+                TEXT_LEAF: []
+              }]
+            }]
+          }]
+        }]
+      }]
+    }]
+  }, {
+    SECTION: [{ // container
+      SECTION: [{ // child
+        TEXT_LEAF: []
+      }]
+    }]
+  });
+
+  yield testTreeOnHide(browser, accDoc, 't6_container', 't6_subcontainer', {
+    SECTION: [{ // container
+      SECTION: [{ // subcontainer
+        TABLE: [{
+          ROW: [{
+            CELL: [{
+              TABLE: [{ // nested table
+                ROW: [{
+                  CELL: [{
+                    SECTION: [{ // child
+                      TEXT_LEAF: []
+                    }]
+                  }]
+                }]
+              }]
+            }]
+          }]
+        }]
+      }, {
+        SECTION: [{ // child2
+          TEXT_LEAF: []
+        }]
+      }]
+    }]
+  }, {
+    SECTION: [{ // container
+      SECTION: [{ // child
+        TEXT_LEAF: []
+      }]
+    }, {
+      SECTION: [{ // child2
+        TEXT_LEAF: []
+      }]
+    }]
+  });
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/browser_treeupdate_whitespace.js
@@ -0,0 +1,71 @@
+/* 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';
+
+/* global EVENT_REORDER */
+
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR });
+
+addAccessibleTask('doc_treeupdate_whitespace.html', function*(browser, accDoc) {
+  let container1 = findAccessibleChildByID(accDoc, 'container1');
+  let container2Parent = findAccessibleChildByID(accDoc, 'container2-parent');
+
+  let tree = {
+    SECTION: [
+      { GRAPHIC: [] },
+      { TEXT_LEAF: [] },
+      { GRAPHIC: [] },
+      { TEXT_LEAF: [] },
+      { GRAPHIC: [] }
+    ]
+  };
+  testAccessibleTree(container1, tree);
+
+  let onReorder = waitForEvent(EVENT_REORDER, 'container1');
+  // Remove img1 from container1
+  yield ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    doc.getElementById('container1').removeChild(
+      doc.getElementById('img1'));
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { GRAPHIC: [] },
+      { TEXT_LEAF: [] },
+      { GRAPHIC: [] }
+    ]
+  };
+  testAccessibleTree(container1, tree);
+
+  tree = {
+    SECTION: [
+      { LINK: [] },
+      { LINK: [ { GRAPHIC: [] } ] }
+    ]
+  };
+  testAccessibleTree(container2Parent, tree);
+
+  onReorder = waitForEvent(EVENT_REORDER, 'container2-parent');
+  // Append an img with valid src to container2
+  yield ContentTask.spawn(browser, {}, () => {
+    let doc = content.document;
+    let img = doc.createElement('img');
+    img.setAttribute('src',
+      'http://example.com/a11y/accessible/tests/mochitest/moz.png');
+    doc.getElementById('container2').appendChild(img);
+  });
+  yield onReorder;
+
+  tree = {
+    SECTION: [
+      { LINK: [ { GRAPHIC: [ ] } ] },
+      { TEXT_LEAF: [ ] },
+      { LINK: [ { GRAPHIC: [ ] } ] }
+    ]
+  };
+  testAccessibleTree(container2Parent, tree);
+});
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_ariadialog.html
@@ -0,0 +1,23 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tree Update ARIA Dialog Test</title>
+  </head>
+  <body id="body">
+    <div id="dialog" role="dialog" style="display: none;">
+      <table id="table" role="presentation"
+             style="display: block; position: fixed; top: 88px; left: 312.5px; z-index: 10010;">
+        <tbody>
+          <tr>
+            <td role="presentation">
+              <div role="presentation">
+                <a id="a" role="button">text</a>
+              </div>
+              <input id="input">
+            </td>
+          </tr>
+        </tbody>
+      </table>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_ariaowns.html
@@ -0,0 +1,44 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tree Update ARIA Owns Test</title>
+  </head>
+  <body id="body">
+    <div id="t1_container" aria-owns="t1_checkbox t1_button">
+      <div role="button" id="t1_button"></div>
+      <div role="checkbox" id="t1_checkbox">
+        <span id="t1_span">
+          <div id="t1_subdiv"></div>
+        </span>
+      </div>
+    </div>
+    <div id="t1_group" role="group"></div>
+    <div id="t1_grouptmp" role="group"></div>
+
+    <div id="t2_container1" aria-owns="t2_owned"></div>
+    <div id="t2_container2">
+      <div id="t2_container3"><div id="t2_owned" role="checkbox"></div></div>
+    </div>
+
+    <div id="t3_container1" aria-owns="t3_child"></div>
+    <div id="t3_child" role="checkbox"></div>
+    <div id="t3_container2"></div>
+
+    <div id="t4_container1" aria-owns="t4_child1 t4_child2"></div>
+    <div id="t4_container2">
+      <div id="t4_child1" style="display:none" role="checkbox"></div>
+      <div id="t4_child2" role="radio"></div>
+    </div>
+
+    <div id="t5_container">
+      <div role="button" id="t5_button"></div>
+      <div role="checkbox" id="t5_checkbox"></div>
+      <div role="radio" id="t5_radio"></div>
+    </div>
+
+    <div id="t6_container" aria-owns="t6_fake">
+      <span id="t6_span">hey</span>
+    </div>
+    <div id="t6_fake" role="group"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_imagemap.html
@@ -0,0 +1,21 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tree Update Imagemap Test</title>
+  </head>
+  <body id="body">
+    <map name="atoz_map" id="map">
+      <area href="http://www.bbc.co.uk/radio4/atoz/index.shtml#b"
+            coords="17,0,30,14" alt="b" shape="rect">
+    </map>
+
+    <div id="container">
+      <img id="imgmap" width="447" height="15"
+           usemap="#atoz_map"
+           src="http://example.com/a11y/accessible/tests/mochitest/letters.gif"><!--
+      Important: no whitespace between the <img> and the </div>, so we
+      don't end up with textframes there, because those would be reflected
+      in our accessible tree in some cases.
+      --></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_removal.xhtml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tree Update Removal Test</title>
+  </head>
+  <body id="body">
+    <div id="the_displaynone" style="display: none;"></div>
+    <table id="the_table"></table>
+    <tr id="the_row"></tr>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_visibility.html
@@ -0,0 +1,78 @@
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Tree Update Visibility Test</title>
+  </head>
+  <body id="body">
+    <!-- hide parent while child stays visible -->
+    <div id="t1_container">
+      <div id="t1_parent">
+        <div id="t1_child" style="visibility: visible">text</div>
+      </div>
+    </div>
+
+    <!-- hide grandparent while its children stay visible -->
+    <div id="t2_container">
+      <div id="t2_grandparent">
+        <div>
+          <div id="t2_child" style="visibility: visible">text</div>
+          <div id="t2_child2" style="visibility: visible">text</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- change container style, hide parents while their children stay visible -->
+    <div id="t3_container">
+      <div id="t3_parent">
+        <div id="t3_child" style="visibility: visible">text</div>
+      </div>
+      <div id="t3_parent2">
+        <div id="t3_child2" style="visibility: visible">text</div>
+      </div>
+    </div>
+
+    <!-- change container style, show child inside the table -->
+    <div id="t4_container">
+      <table>
+        <tr>
+          <td id="t4_parent">
+            <div id="t4_child" style="visibility: hidden;">text</div>
+          </td>
+        </tr>
+      </table>
+    </div>
+
+    <!-- hide subcontainer while child inside the table stays visible -->
+    <div id="t5_container">
+      <div id="t5_subcontainer">
+        <table>
+          <tr>
+            <td>
+              <div id="t5_child" style="visibility: visible;">text</div>
+            </td>
+          </tr>
+        </table>
+      </div>
+    </div>
+
+    <!-- hide subcontainer while its child and child inside the nested table stays visible -->
+    <div id="t6_container">
+      <div id="t6_subcontainer">
+        <table>
+          <tr>
+            <td>
+              <table>
+                <tr>
+                  <td>
+                    <div id="t6_child" style="visibility: visible;">text</div>
+                  </td>
+                </tr>
+              </table>
+            </td>
+          </tr>
+        </table>
+        <div id="t6_child2" style="visibility: visible">text</div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/doc_treeupdate_whitespace.html
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <meta charset="utf-8"/>
+    <title>Whitespace text accessible creation/desctruction</title>
+  </head>
+  <body id="body">
+    <div id="container1">  <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png">  <img id="img1" src="http://example.com/a11y/accessible/tests/mochitest/moz.png">  <img src="http://example.com/a11y/accessible/tests/mochitest/moz.png">  </div>
+    <div id="container2-parent"> <a id="container2"></a> <a><img src="http://example.com/a11y/accessible/tests/mochitest/moz.png"></a> </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/events.js
@@ -0,0 +1,100 @@
+/* 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';
+
+/* global nsIAccessibleEvent, nsIAccessibleDocument,
+          nsIAccessibleStateChangeEvent, nsIAccessibleTextChangeEvent */
+
+/* exported EVENT_REORDER, EVENT_SHOW, EVENT_TEXT_INSERTED, EVENT_TEXT_REMOVED,
+            EVENT_DOCUMENT_LOAD_COMPLETE, EVENT_HIDE, EVENT_TEXT_CARET_MOVED,
+            EVENT_STATE_CHANGE, waitForEvent, waitForMultipleEvents */
+
+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;
+const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
+const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+
+/**
+ * Describe an event in string format.
+ * @param  {nsIAccessibleEvent}  event  event to strigify
+ */
+function eventToString(event) {
+  let type = eventTypeToString(event.eventType);
+  let info = `Event type: ${type}`;
+
+  if (event instanceof nsIAccessibleStateChangeEvent) {
+    let stateStr = statesToString(event.isExtraState ? 0 : event.state,
+                                  event.isExtraState ? event.state : 0);
+    info += `, state: ${stateStr}, is enabled: ${event.isEnabled}`;
+  } else if (event instanceof nsIAccessibleTextChangeEvent) {
+    let tcType = event.isInserted ? 'inserted' : 'removed';
+    info += `, start: ${event.start}, length: ${event.length}, ${tcType} text: ${event.modifiedText}`;
+  }
+
+  info += `. Target: ${prettyName(event.accessible)}`;
+  return info;
+}
+
+/**
+ * A helper function that waits for an accessible event of certain type that
+ * belongs to a certain DOMNode (defined by its id).
+ * @param  {String}  id         expected content element id for the event
+ * @param  {Number}  eventType  expected accessible event type
+ * @return {Promise}            promise that resolves to an event
+ */
+function waitForEvent(eventType, id) {
+  return new Promise(resolve => {
+    let eventObserver = {
+      observe(subject, topic, data) {
+        if (topic !== 'accessible-event') {
+          return;
+        }
+
+        let event = subject.QueryInterface(nsIAccessibleEvent);
+        Logger.log(eventToString(event));
+
+        let domID = getAccessibleDOMNodeID(event.accessible);
+        // If event's accessible does not match expected event type or DOMNode
+        // id, skip thie event.
+        if (domID === id && event.eventType === eventType) {
+          Logger.log(`Correct event DOMNode id: ${id}`);
+          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', false);
+  });
+}
+
+/**
+ * A helper function that waits for a sequence of accessible events in
+ * specified order.
+ * @param {Array} events        a list of events to wait (same format as
+ *                              waitForEvent arguments)
+ */
+function waitForMultipleEvents(events) {
+  // Next expected event index.
+  let currentIdx = 0;
+
+  return Promise.all(events.map(({ eventType, id }, idx) =>
+    // In addition to waiting for an event, attach an order checker for the
+    // event.
+    waitForEvent(eventType, id).then(resolvedEvent => {
+      // Verify that event happens in order and increment expected index.
+      is(idx, currentIdx++,
+        `Unexpected event order: ${eventToString(resolvedEvent)}`);
+      return resolvedEvent;
+    })
+  ));
+}
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/head.js
@@ -0,0 +1,297 @@
+/* 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';
+
+/* global EVENT_DOCUMENT_LOAD_COMPLETE */
+
+/* exported Logger, MOCHITESTS_DIR, isDefunct, addAccessibleTask,
+            invokeSetAttribute, invokeFocus, invokeSetStyle,
+            findAccessibleChildByID, getAccessibleDOMNodeID */
+
+const { interfaces: Ci, utils: Cu } = Components;
+
+Cu.import('resource://gre/modules/Services.jsm');
+
+/**
+ * Current browser test directory path used to load subscripts.
+ */
+const CURRENT_DIR =
+  'chrome://mochitests/content/browser/accessible/tests/browser/';
+/**
+ * A11y mochitest directory where we find common files used in both browser and
+ * plain tests.
+ */
+const MOCHITESTS_DIR =
+  'chrome://mochitests/content/a11y/accessible/tests/mochitest/';
+/**
+ * A base URL for test files used in content.
+ */
+const CURRENT_CONTENT_DIR =
+  'http://example.com/browser/accessible/tests/browser/';
+
+/**
+ * Used to dump debug information.
+ */
+let Logger = {
+  /**
+   * Set up this variable to dump log messages into console.
+   */
+  dumpToConsole: false,
+
+  /**
+   * Set up this variable to dump log messages into error console.
+   */
+  dumpToAppConsole: false,
+
+  /**
+   * Return true if dump is enabled.
+   */
+  get enabled() {
+    return this.dumpToConsole || this.dumpToAppConsole;
+  },
+
+  /**
+   * Dump information into console if applicable.
+   */
+  log(msg) {
+    if (this.enabled) {
+      this.logToConsole(msg);
+      this.logToAppConsole(msg);
+    }
+  },
+
+  /**
+   * Log message to console.
+   */
+  logToConsole(msg) {
+    if (this.dumpToConsole) {
+      dump(`\n${msg}\n`);
+    }
+  },
+
+  /**
+   * Log message to error console.
+   */
+  logToAppConsole(msg) {
+    if (this.dumpToAppConsole) {
+      Services.console.logStringMessage(`${msg}`);
+    }
+  }
+};
+
+/**
+ * Check if an accessible object has a defunct test.
+ * @param  {nsIAccessible}  accessible object to test defunct state for
+ * @return {Boolean}        flag indicating defunct state
+ */
+function isDefunct(accessible) {
+  try {
+    let extState = {};
+    accessible.getState({}, extState);
+    return extState.value & Ci.nsIAccessibleStates.EXT_STATE_DEFUNCT;
+  } catch (x) {
+    return true;
+  }
+}
+
+/**
+ * Asynchronously set or remove content element's attribute (in content process
+ * if e10s is enabled).
+ * @param  {Object}  browser  current "tabbrowser" element
+ * @param  {String}  id       content element id
+ * @param  {String}  attr     attribute name
+ * @param  {String?} value    optional attribute value, if not present, remove
+ *                            attribute
+ * @return {Promise}          promise indicating that attribute is set/removed
+ */
+function invokeSetAttribute(browser, id, attr, value) {
+  if (value) {
+    Logger.log(`Setting ${attr} attribute to ${value} for node with id: ${id}`);
+  } else {
+    Logger.log(`Removing ${attr} attribute from node with id: ${id}`);
+  }
+  return ContentTask.spawn(browser, { id, attr, value },
+    ({ id, attr, value }) => {
+      let elm = content.document.getElementById(id);
+      if (value) {
+        elm.setAttribute(attr, value);
+      } else {
+        elm.removeAttribute(attr);
+      }
+    });
+}
+
+/**
+ * Asynchronously set or remove content element's style (in content process if
+ * e10s is enabled).
+ * @param  {Object}  browser  current "tabbrowser" element
+ * @param  {String}  id       content element id
+ * @param  {String}  aStyle   style property name
+ * @param  {String?} aValue   optional style property value, if not present,
+ *                            remove style
+ * @return {Promise}          promise indicating that style is set/removed
+ */
+function invokeSetStyle(browser, id, style, value) {
+  if (value) {
+    Logger.log(`Setting ${style} style to ${value} for node with id: ${id}`);
+  } else {
+    Logger.log(`Removing ${style} style from node with id: ${id}`);
+  }
+  return ContentTask.spawn(browser, { id, style, value },
+    ({ id, style, value }) => {
+      let elm = content.document.getElementById(id);
+      if (value) {
+        elm.style[style] = value;
+      } else {
+        delete elm.style[style];
+      }
+    });
+}
+
+/**
+ * Asynchronously set focus on a content element (in content process if e10s is
+ * enabled).
+ * @param  {Object}  browser  current "tabbrowser" element
+ * @param  {String}  id       content element id
+ * @return {Promise} promise  indicating that focus is set
+ */
+function invokeFocus(browser, id) {
+  Logger.log(`Setting focus on a node with id: ${id}`);
+  return ContentTask.spawn(browser, id, id => {
+    let elm = content.document.getElementById(id);
+    if (elm instanceof Ci.nsIDOMNSEditableElement && elm.editor ||
+        elm instanceof Ci.nsIDOMXULTextBoxElement) {
+      elm.selectionStart = elm.selectionEnd = elm.value.length;
+    }
+    elm.focus();
+  });
+}
+
+/**
+ * Traverses the accessible tree starting from a given accessible as a root and
+ * looks for an accessible that matches based on its DOMNode id.
+ * @param  {nsIAccessible}  accessible root accessible
+ * @param  {String}         id         id to look up accessible for
+ * @return {nsIAccessible?}            found accessible if any
+ */
+function findAccessibleChildByID(accessible, id) {
+  if (getAccessibleDOMNodeID(accessible) === id) {
+    return accessible;
+  }
+  for (let i = 0; i < accessible.children.length; ++i) {
+    let found = findAccessibleChildByID(accessible.getChildAt(i), id);
+    if (found) {
+      return found;
+    }
+  }
+}
+
+/**
+ * Load a list of scripts into the test
+ * @param {Array} scripts  a list of scripts to load
+ */
+function loadScripts(...scripts) {
+  for (let script of scripts) {
+    let path = typeof script === 'string' ? `${CURRENT_DIR}${script}` :
+      `${script.dir}${script.name}`;
+    Services.scriptloader.loadSubScript(path, this);
+  }
+}
+
+/**
+ * Load a list of frame scripts into test's content.
+ * @param {Object} browser   browser element that content belongs to
+ * @param {Array}  scripts   a list of scripts to load into content
+ */
+function loadFrameScripts(browser, ...scripts) {
+  let mm = browser.messageManager;
+  for (let script of scripts) {
+    let frameScript;
+    if (typeof script === 'string') {
+      if (script.includes('.js')) {
+        // If script string includes a .js extention, assume it is a script
+        // path.
+        frameScript = `${CURRENT_DIR}${script}`;
+      } else {
+        // Otherwise it is a serealized script.
+        frameScript = `data:,${script}`;
+      }
+    } else {
+      // Script is a object that has { dir, name } format.
+      frameScript = `${script.dir}${script.name}`;
+    }
+    mm.loadFrameScript(frameScript, false, true);
+  }
+}
+
+/**
+ * A wrapper around browser test add_task that triggers an accessible test task
+ * as a new browser test task with given document, data URL or markup snippet.
+ * @param  {String}             doc    URL (relative to current directory) or
+ *                                     data URL or markup snippet that is used
+ *                                     to test content with
+ * @param  {Function|Function*} task   a generator or a function with tests to
+ *                                     run
+ */
+function addAccessibleTask(doc, task) {
+  add_task(function*() {
+    let url;
+    if (doc.includes('doc_')) {
+      url = `${CURRENT_CONTENT_DIR}${doc}`;
+    } else {
+      // Assume it's a markup snippet.
+      url = `data:text/html,
+        <html>
+          <head>
+            <meta charset="utf-8"/>
+            <title>Accessibility Test</title>
+          </head>
+          <body id="body">${doc}</body>
+        </html>`;
+    }
+
+    registerCleanupFunction(() => {
+      let observers = Services.obs.enumerateObservers('accessible-event');
+      while (observers.hasMoreElements()) {
+        Services.obs.removeObserver(
+          observers.getNext().QueryInterface(Ci.nsIObserver),
+          'accessible-event');
+      }
+    });
+
+    let onDocLoad = waitForEvent(EVENT_DOCUMENT_LOAD_COMPLETE, 'body');
+
+    yield BrowserTestUtils.withNewTab({
+      gBrowser,
+      url: url
+    }, function*(browser) {
+      registerCleanupFunction(() => {
+        if (browser) {
+          let tab = gBrowser.getTabForBrowser(browser);
+          if (tab && !tab.closing && tab.linkedBrowser) {
+            gBrowser.removeTab(tab);
+          }
+        }
+      });
+
+      yield SimpleTest.promiseFocus(browser);
+
+      loadFrameScripts(browser,
+        'let { document, window, navigator } = content;',
+        { name: 'common.js', dir: MOCHITESTS_DIR });
+
+      Logger.log(
+        `e10s enabled: ${Services.appinfo.browserTabsRemoteAutostart}`);
+      Logger.log(`Actually remote browser: ${browser.isRemoteBrowser}`);
+
+      let event = yield onDocLoad;
+      yield task(browser, event.accessible);
+    });
+  });
+}
+
+// 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');
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -84,16 +84,21 @@ const kEmbedChar = String.fromCharCode(0
 const kDiscBulletChar = String.fromCharCode(0x2022);
 const kDiscBulletText = kDiscBulletChar + " ";
 const kCircleBulletText = String.fromCharCode(0x25e6) + " ";
 const kSquareBulletText = String.fromCharCode(0x25fe) + " ";
 
 const MAX_TRIM_LENGTH = 100;
 
 /**
+ * Services to determine if e10s is enabled.
+ */
+Components.utils.import('resource://gre/modules/Services.jsm');
+
+/**
  * nsIAccessibleRetrieval service.
  */
 var gAccRetrieval = Components.classes["@mozilla.org/accessibleRetrieval;1"].
   getService(nsIAccessibleRetrieval);
 
 /**
  * Enable/disable logging.
  */
@@ -733,16 +738,41 @@ function getTextFromClipboard()
     str = str.value.QueryInterface(Components.interfaces.nsISupportsString);
   if (str)
     return str.data.substring(0, strLength.value / 2);
 
   return "";
 }
 
 /**
+ * Extract DOMNode id from an accessible. If e10s is enabled, DOMNode is not
+ * present in parent process but, if available, DOMNode id is attached to an
+ * accessible object.
+ * @param  {nsIAccessible} accessible  accessible
+ * @return {String?}                   DOMNode id if available
+ */
+function getAccessibleDOMNodeID(accessible) {
+  if (accessible instanceof nsIAccessibleDocument) {
+    // If accessible is a document, trying to find its document body id.
+    try {
+      return accessible.DOMNode.body.id;
+    } catch (e) { /* This only works if accessible is not a proxy. */ }
+  }
+  try {
+    return accessible.DOMNode.id;
+  } catch (e) { /* This will fail if DOMNode is in different process. */ }
+  try {
+    // When e10s is enabled, accessible will have an "id" property if its
+    // corresponding DOMNode has an id. If accessible is a document, its "id"
+    // property corresponds to the "id" of its body element.
+    return accessible.id;
+  } catch (e) { /* This will fail if accessible is not a proxy. */ }
+}
+
+/**
  * Return pretty name for identifier, it may be ID, DOM node or accessible.
  */
 function prettyName(aIdentifier)
 {
   if (aIdentifier instanceof Array) {
     var msg = "";
     for (var idx = 0; idx < aIdentifier.length; idx++) {
       if (msg != "")
@@ -750,20 +780,27 @@ function prettyName(aIdentifier)
 
       msg += prettyName(aIdentifier[idx]);
     }
     return msg;
   }
 
   if (aIdentifier instanceof nsIAccessible) {
     var acc = getAccessible(aIdentifier);
+    var domID = getAccessibleDOMNodeID(acc);
     var msg = "[";
     try {
-      msg += getNodePrettyName(acc.DOMNode);
-      msg += ", role: " + roleToString(acc.role);
+      if (Services.appinfo.browserTabsRemoteAutostart) {
+        if (domID) {
+          msg += `DOM node id: ${domID}, `;
+        }
+      } else {
+        msg += `${getNodePrettyName(acc.DOMNode)}, `;
+      }
+      msg += "role: " + roleToString(acc.role);
       if (acc.name)
         msg += ", name: '" + shortenString(acc.name) + "'";
     } catch (e) {
       msg += "defunct";
     }
 
     if (acc)
       msg += ", address: " + getObjAddress(acc);