Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 04 May 2016 12:01:17 +0200
changeset 335051 7e47812f913f9a374a2577c11aa4608dd7a01e21
parent 335050 e02797aae3cea963d11f2bffefe397b1f3159554 (current diff)
parent 335040 311c7ea8803d326cec715404256c86beb8af3804 (diff)
child 335052 dbde8615b303aeba7486b82074715ffd1ee196da
push id6249
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 13:59:36 +0000
treeherdermozilla-beta@bad9d4f5bf7e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone49.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to fx-team
.eslintignore
js/src/proxy/ScriptedDirectProxyHandler.cpp
js/src/proxy/ScriptedDirectProxyHandler.h
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.pattern.image.incomplete.immediate.html.ini
toolkit/components/telemetry/Histograms.json
--- 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/base/TextAttrs.cpp
+++ b/accessible/base/TextAttrs.cpp
@@ -73,28 +73,29 @@ TextAttrsMgr::GetAttributes(nsIPersisten
 
   // Get the content and frame of the accessible. In the case of document
   // accessible it's role content and root frame.
   nsIContent* hyperTextElm = mHyperTextAcc->GetContent();
   if (!hyperTextElm)
     return; // XXX: we don't support text attrs on document with no body
 
   nsIFrame* rootFrame = mHyperTextAcc->GetFrame();
-  NS_ASSERTION(rootFrame, "No frame for accessible!");
+  MOZ_ASSERT(rootFrame, "No frame for accessible!");
   if (!rootFrame)
     return;
 
   nsIContent *offsetNode = nullptr, *offsetElm = nullptr;
   nsIFrame *frame = nullptr;
   if (mOffsetAcc) {
     offsetNode = mOffsetAcc->GetContent();
     offsetElm = nsCoreUtils::GetDOMElementFor(offsetNode);
-    NS_ASSERTION(offsetElm, "No element for offset accessible!");
+    MOZ_ASSERT(offsetElm, "No element for offset accessible!");
     if (!offsetElm)
       return;
+
     frame = offsetElm->GetPrimaryFrame();
   }
 
   // "language" text attribute
   LangTextAttr langTextAttr(mHyperTextAcc, hyperTextElm, offsetNode);
 
   // "aria-invalid" text attribute
   InvalidTextAttr invalidTextAttr(hyperTextElm, offsetNode);
@@ -160,16 +161,19 @@ TextAttrsMgr::GetRange(TextAttr* aAttrAr
   for (int32_t childIdx = mOffsetAccIdx - 1; childIdx >= 0; childIdx--) {
     Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
 
     // Stop on embedded accessible since embedded accessibles are combined into
     // own range.
     if (!currAcc->IsText())
       break;
 
+    MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+               "Text accessible has to have an associated DOM element");
+
     bool offsetFound = false;
     for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
       TextAttr* textAttr = aAttrArray[attrIdx];
       if (!textAttr->Equal(currAcc)) {
         offsetFound = true;
         break;
       }
     }
@@ -182,16 +186,19 @@ TextAttrsMgr::GetRange(TextAttr* aAttrAr
 
   // Navigate forward from anchor accessible to find end offset.
   uint32_t childLen = mHyperTextAcc->ChildCount();
   for (uint32_t childIdx = mOffsetAccIdx + 1; childIdx < childLen; childIdx++) {
     Accessible* currAcc = mHyperTextAcc->GetChildAt(childIdx);
     if (!currAcc->IsText())
       break;
 
+    MOZ_ASSERT(nsCoreUtils::GetDOMElementFor(currAcc->GetContent()),
+               "Text accessible has to have an associated DOM element");
+
     bool offsetFound = false;
     for (uint32_t attrIdx = 0; attrIdx < aAttrArrayLen; attrIdx++) {
       TextAttr* textAttr = aAttrArray[attrIdx];
 
       // Alter the end offset when text attribute changes its value and stop
       // the search.
       if (!textAttr->Equal(currAcc)) {
         offsetFound = true;
@@ -339,18 +346,23 @@ TextAttrsMgr::BGColorTextAttr::
     mIsDefined = GetColor(aFrame, &mNativeValue);
 }
 
 bool
 TextAttrsMgr::BGColorTextAttr::
   GetValueFor(Accessible* aAccessible, nscolor* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  return frame ? GetColor(frame, aValue) : false;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      return GetColor(frame, aValue);
+    }
+  }
+  return false;
 }
 
 void
 TextAttrsMgr::BGColorTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
 {
   nsAutoString formattedValue;
   StyleInfo::FormatColor(aValue, formattedValue);
@@ -402,22 +414,23 @@ TextAttrsMgr::ColorTextAttr::
   }
 }
 
 bool
 TextAttrsMgr::ColorTextAttr::
   GetValueFor(Accessible* aAccessible, nscolor* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  if (frame) {
-    *aValue = frame->StyleColor()->mColor;
-    return true;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      *aValue = frame->StyleColor()->mColor;
+      return true;
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::ColorTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const nscolor& aValue)
 {
   nsAutoString formattedValue;
@@ -440,18 +453,23 @@ TextAttrsMgr::FontFamilyTextAttr::
     mIsDefined = GetFontFamily(aFrame, mNativeValue);
 }
 
 bool
 TextAttrsMgr::FontFamilyTextAttr::
   GetValueFor(Accessible* aAccessible, nsString* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  return frame ? GetFontFamily(frame, *aValue) : false;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      return GetFontFamily(frame, *aValue);
+    }
+  }
+  return false;
 }
 
 void
 TextAttrsMgr::FontFamilyTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const nsString& aValue)
 {
   nsAccUtils::SetAccAttr(aAttributes, nsGkAtoms::font_family, aValue);
 }
@@ -489,23 +507,24 @@ TextAttrsMgr::FontSizeTextAttr::
     mIsDefined = true;
   }
 }
 
 bool
 TextAttrsMgr::FontSizeTextAttr::
   GetValueFor(Accessible* aAccessible, nscoord* aValue)
 {
-  nsIContent* content = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = content->GetPrimaryFrame();
-  if (frame) {
-    *aValue = frame->StyleFont()->mSize;
-    return true;
+  nsIContent* el = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
+  if (el) {
+    nsIFrame* frame = el->GetPrimaryFrame();
+    if (frame) {
+      *aValue = frame->StyleFont()->mSize;
+      return true;
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::FontSizeTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
 {
   // Convert from nscoord to pt.
@@ -546,22 +565,23 @@ TextAttrsMgr::FontStyleTextAttr::
   }
 }
 
 bool
 TextAttrsMgr::FontStyleTextAttr::
   GetValueFor(Accessible* aAccessible, nscoord* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  if (frame) {
-    *aValue = frame->StyleFont()->mFont.style;
-    return true;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      *aValue = frame->StyleFont()->mFont.style;
+      return true;
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::FontStyleTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const nscoord& aValue)
 {
   nsAutoString formattedValue;
@@ -588,22 +608,23 @@ TextAttrsMgr::FontWeightTextAttr::
   }
 }
 
 bool
 TextAttrsMgr::FontWeightTextAttr::
   GetValueFor(Accessible* aAccessible, int32_t* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  if (frame) {
-    *aValue = GetFontWeight(frame);
-    return true;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      *aValue = GetFontWeight(frame);
+      return true;
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::FontWeightTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const int32_t& aValue)
 {
   nsAutoString formattedValue;
@@ -722,22 +743,23 @@ TextAttrsMgr::TextDecorTextAttr::
   }
 }
 
 bool
 TextAttrsMgr::TextDecorTextAttr::
   GetValueFor(Accessible* aAccessible, TextDecorValue* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  if (frame) {
-    *aValue = TextDecorValue(frame);
-    return aValue->IsDefined();
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      *aValue = TextDecorValue(frame);
+      return aValue->IsDefined();
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::TextDecorTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const TextDecorValue& aValue)
 {
   if (aValue.IsUnderline()) {
@@ -785,22 +807,23 @@ TextAttrsMgr::TextPosTextAttr::
   }
 }
 
 bool
 TextAttrsMgr::TextPosTextAttr::
   GetValueFor(Accessible* aAccessible, TextPosValue* aValue)
 {
   nsIContent* elm = nsCoreUtils::GetDOMElementFor(aAccessible->GetContent());
-  nsIFrame* frame = elm->GetPrimaryFrame();
-  if (frame) {
-    *aValue = GetTextPosValue(frame);
-    return *aValue != eTextPosNone;
+  if (elm) {
+    nsIFrame* frame = elm->GetPrimaryFrame();
+    if (frame) {
+      *aValue = GetTextPosValue(frame);
+      return *aValue != eTextPosNone;
+    }
   }
-
   return false;
 }
 
 void
 TextAttrsMgr::TextPosTextAttr::
   ExposeValue(nsIPersistentProperties* aAttributes, const TextPosValue& aValue)
 {
   switch (aValue) {
--- 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);
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -382,19 +382,30 @@ var gPrivacyPane = {
       }
 
       const Cc = Components.classes, Ci = Components.interfaces;
       let brandName = document.getElementById("bundleBrand").getString("brandShortName");
       let bundle = document.getElementById("bundlePreferences");
       let msg = bundle.getFormattedString(autoStart.checked ?
                                           "featureEnableRequiresRestart" : "featureDisableRequiresRestart",
                                           [brandName]);
+      let restartText = bundle.getFormattedString("okToRestartButton", [brandName]);
+      let revertText = bundle.getString("revertNoRestartButton");
+
       let title = bundle.getFormattedString("shouldRestartTitle", [brandName]);
       let prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
-      let shouldProceed = prompts.confirm(window, title, msg)
+      let buttonFlags = (Services.prompt.BUTTON_POS_0 *
+			 Services.prompt.BUTTON_TITLE_IS_STRING) +
+                        (Services.prompt.BUTTON_POS_1 *
+			 Services.prompt.BUTTON_TITLE_IS_STRING) +
+                        Services.prompt.BUTTON_POS_0_DEFAULT;
+
+      let shouldProceed = prompts.confirmEx(window, title, msg,
+					    buttonFlags, revertText, restartText,
+					    null, null, {});
       if (shouldProceed) {
         let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
                            .createInstance(Ci.nsISupportsPRBool);
         Services.obs.notifyObservers(cancelQuit, "quit-application-requested",
                                      "restart");
         shouldProceed = !cancelQuit.data;
 
         if (shouldProceed) {
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -214,16 +214,23 @@ var SessionHistoryListener = {
     // We will likely only collect once since we are batching collection on
     // a delay.
     docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.
       addSHistoryListener(this);
 
     // Collect data if we start with a non-empty shistory.
     if (!SessionHistory.isEmpty(docShell)) {
       this.collect();
+      // When a tab is detached from the window, for the new window there is a
+      // new SessionHistoryListener created. Normally it is empty at this point
+      // but in a test env. the initial about:blank might have a children in which
+      // case we fire off a history message here with about:blank in it. If we
+      // don't do it ASAP then there is going to be a browser swap and the parent
+      // will be all confused by that message.
+      MessageQueue.send();
     }
 
     // Listen for page title changes.
     addEventListener("DOMTitleChanged", this);
   },
 
   uninit: function () {
     let sessionHistory = docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory;
@@ -708,16 +715,18 @@ var MessageQueue = {
         for (let histogramId of Object.keys(value)) {
           telemetry[histogramId] = value[histogramId];
         }
       } else {
         data[key] = value;
       }
     }
 
+    this._data.clear();
+
     durationMs = Date.now() - durationMs;
     telemetry.FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS = durationMs;
 
     try {
       // Send all data to the parent process.
       sendAsyncMessage("SessionStore:update", {
         data, telemetry, flushID,
         isFinal: options.isFinal || false,
--- a/browser/locales/en-US/chrome/browser/preferences/preferences.properties
+++ b/browser/locales/en-US/chrome/browser/preferences/preferences.properties
@@ -165,16 +165,18 @@ actualAppCacheSize=Your application cach
 syncUnlink.title=Do you want to unlink your device?
 syncUnlink.label=This device will no longer be associated with your Sync account. All of your personal data, both on this device and in your Sync account, will remain intact.
 syncUnlinkConfirm.label=Unlink
 
 # LOCALIZATION NOTE (featureEnableRequiresRestart, featureDisableRequiresRestart, restartTitle): %S = brandShortName
 featureEnableRequiresRestart=%S must restart to enable this feature.
 featureDisableRequiresRestart=%S must restart to disable this feature.
 shouldRestartTitle=Restart %S
+okToRestartButton=Restart %S now
+revertNoRestartButton=Revert
 
 restartNow=Restart Now
 restartLater=Restart Later
 
 #### e10S
 # LOCALIZATION NOTE (e10sFeedbackAfterRestart): This message appears when the user
 # unchecks "Enable multi-process" on the "General" preferences tab.
 e10sFeedbackAfterRestart=After restart, a tab will open to input.mozilla.org where you can provide us feedback about your e10s experience.
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -70,17 +70,17 @@ def android_ndk_include(compile_env, bui
 include(android_ndk_include)
 
 
 # MacOS deployment target version
 # ==============================================================
 # This needs to happen before any compilation test is done.
 
 option('--enable-macos-target', env='MACOSX_DEPLOYMENT_TARGET', nargs=1,
-       default='10.6', help='Set the minimum MacOS version needed at runtime')
+       default='10.7', help='Set the minimum MacOS version needed at runtime')
 
 @depends('--enable-macos-target', target)
 @imports(_from='os', _import='environ')
 def macos_target(value, target):
     if value and target.os == 'OSX':
         # Ensure every compiler process we spawn uses this value.
         environ['MACOSX_DEPLOYMENT_TARGET'] = value[0]
         return value[0]
@@ -400,30 +400,35 @@ def default_cxx_compilers(c_compiler):
             return (os.path.join(dir, file.replace('clang', 'clang++')),)
 
         return (c_compiler.compiler,)
 
     return default_cxx_compilers
 
 
 @template
-def compiler(language, host_or_target, c_compiler=None, other_compiler=None):
+def compiler(language, host_or_target, c_compiler=None, other_compiler=None,
+             other_c_compiler=None):
     '''Template handling the generic base checks for the compiler for the
     given `language` on the given platform (`host_or_target`).
     `host_or_target` is either `host` or `target` (the @depends functions
     from init.configure.
-    When the language in 'C++', `c_compiler` is the result of the `compiler`
+    When the language is 'C++', `c_compiler` is the result of the `compiler`
     template for the language 'C' for the same `host_or_target`.
     When `host_or_target` is `host`, `other_compiler` is the result of the
     `compiler` template for the same `language` for `target`.
+    When `host_or_target` is `host` and the language is 'C++',
+    `other_c_compiler` is the result of the `compiler` template for the
+    language 'C' for `target`.
     '''
     assert host_or_target in (host, target)
     assert language in ('C', 'C++')
     assert language == 'C' or c_compiler
     assert host_or_target == target or other_compiler
+    assert language == 'C' or host_or_target == target or other_c_compiler
 
     host_or_target_str = {
         host: 'host',
         target: 'target',
     }[host_or_target]
 
     var = {
         ('C', target): 'CC',
@@ -457,24 +462,29 @@ def compiler(language, host_or_target, c
         without_flags = list(takewhile(lambda x: not x.startswith('-'), cmd))
 
         return namespace(
             wrapper=without_flags[:-1],
             compiler=without_flags[-1],
             flags=cmd[len(without_flags):],
         )
 
-    # Derive the host C compiler from the target C compiler when no explicit
-    # compiler was given and we're not cross compiling.
-    if language == 'C' and host_or_target == host:
-        @depends(provided_compiler, other_compiler, cross_compiling)
-        def provided_compiler(value, other_compiler, cross_compiling):
+    # Derive the host compiler from the corresponding target compiler when no
+    # explicit compiler was given and we're not cross compiling. For the C++
+    # compiler, though, prefer to derive from the host C compiler when it
+    # doesn't match the target C compiler.
+    if host_or_target == host:
+        args = (c_compiler, other_c_compiler) if other_c_compiler else ()
+
+        @depends(provided_compiler, other_compiler, cross_compiling, *args)
+        def provided_compiler(value, other_compiler, cross_compiling, *args):
             if value:
                 return value
-            if not cross_compiling:
+            c_compiler, other_c_compiler = args if args else (None, None)
+            if not cross_compiling and c_compiler == other_c_compiler:
                 return other_compiler
 
     # Normally, we'd use `var` instead of `_var`, but the interaction with
     # old-configure complicates things, and for now, we a) can't take the plain
     # result from check_prog as CC/CXX/HOST_CC/HOST_CXX and b) have to let
     # old-configure AC_SUBST it (because it's autoconf doing it, not us)
     compiler = check_prog('_%s' % var, what=what, progs=default_compilers,
                           input=delayed_getattr(provided_compiler, 'compiler'))
@@ -602,9 +612,10 @@ def compiler(language, host_or_target, c
 
     return valid_compiler
 
 
 c_compiler = compiler('C', target)
 cxx_compiler = compiler('C++', target, c_compiler=c_compiler)
 host_c_compiler = compiler('C', host, other_compiler=c_compiler)
 host_cxx_compiler = compiler('C++', host, c_compiler=host_c_compiler,
-                             other_compiler=cxx_compiler)
+                             other_compiler=cxx_compiler,
+                             other_c_compiler=c_compiler)
--- a/devtools/client/framework/toolbox-options.xhtml
+++ b/devtools/client/framework/toolbox-options.xhtml
@@ -11,17 +11,17 @@
     <title>Toolbox option</title>
     <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
     <link rel="stylesheet" href="chrome://devtools/content/framework/options-panel.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
   </head>
   <body role="application" class="theme-body">
   <form id="options-panel">
     <div id="tools-box" class="options-vertical-pane">
-      <fieldset id="default-tools-box" class="options-groupbox" tabindex="0">
+      <fieldset id="default-tools-box" class="options-groupbox">
         <legend>&options.selectDefaultTools.label;</legend>
       </fieldset>
 
       <fieldset id="additional-tools-box" class="options-groupbox">
         <legend>&options.selectAdditionalTools.label;</legend>
       </fieldset>
 
       <fieldset id="enabled-toolbox-buttons-box" class="options-groupbox">
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -259,8 +259,34 @@
   cursor: pointer;
 }
 
 .devtools-source-link > .line-number {
   flex: none;
   margin: 2px 0px;
   cursor: pointer;
 }
+
+/* Keyboard focus highlight styles */
+
+:-moz-focusring {
+  outline: var(--theme-focus-outline);
+  outline-offset: -1px;
+}
+
+textbox[focused="true"] {
+  border-color: var(--theme-focus-border-color-textbox);
+  box-shadow: var(--theme-focus-box-shadow-textbox);
+  transition: all 0.2s ease-in-out
+}
+
+textbox :-moz-focusring {
+  box-shadow: none;
+  outline: none;
+}
+
+/* Form fields should already have box-shadow hightlight */
+select:-moz-focusring,
+input[type="radio"]:-moz-focusring,
+input[type="checkbox"]:-moz-focusring,
+checkbox:-moz-focusring {
+  outline: none;
+}
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -145,23 +145,16 @@
 .devtools-menulist,
 .devtools-toolbarbutton {
   -moz-box-align: center;
   min-width: 78px;
   padding: 1px;
   margin: 2px 1px;
 }
 
-.devtools-menulist:-moz-focusring,
-.devtools-toolbarbutton:-moz-focusring,
-.devtools-button:-moz-focusring {
-  outline: 1px dotted hsla(210,30%,85%,0.7);
-  outline-offset: -1px;
-}
-
 .devtools-toolbarbutton:not([label]) > .toolbarbutton-icon,
 .devtools-button::before {
   width: 16px;
   height: 16px;
   transition: opacity 0.05s ease-in-out;
 }
 
 /* HTML buttons */
@@ -180,16 +173,20 @@
   left: 50%;
   top: 50%;
   margin: -8px 0 0 -8px;
   background-size: cover;
   background-repeat: no-repeat;
   transition: opacity 0.05s ease-in-out;
 }
 
+.devtools-button:-moz-focusring {
+  outline: none;
+}
+
 /* Standalone buttons */
 .devtools-button[standalone],
 .devtools-button[data-standalone],
 .devtools-toolbarbutton[standalone],
 .devtools-toolbarbutton[data-standalone] {
   border-width: 1px;
   border-style: solid;
   min-height: 32px;
@@ -431,16 +428,24 @@
 
 /* The spacing is accomplished with a padding on the searchbox */
 .devtools-searchbox > .devtools-textinput,
 .devtools-searchbox > .devtools-searchinput {
   margin-left: 0;
   margin-right: 0;
 }
 
+.devtools-searchbox > .devtools-textinput:-moz-focusring,
+.devtools-searchbox > .devtools-searchinput:-moz-focusring {
+  border-color: var(--theme-focus-border-color-textbox);
+  box-shadow: var(--theme-focus-box-shadow-textbox);
+  transition: all 0.2s ease-in-out;
+  outline: none;
+}
+
 /* Don't add 'double spacing' for inputs that are at beginning / end
    of a toolbar (since the toolbar has it's own spacing). */
 .devtools-toolbar > .devtools-textinput:first-child,
 .devtools-toolbar > .devtools-searchinput:first-child {
   -moz-margin-start: 0;
 }
 .devtools-toolbar > .devtools-textinput:last-child,
 .devtools-toolbar > .devtools-searchinput:last-child {
--- a/devtools/client/themes/variables.css
+++ b/devtools/client/themes/variables.css
@@ -38,16 +38,20 @@
   --theme-highlight-blue: #0088cc;
   --theme-highlight-bluegrey: #0072ab;
   --theme-highlight-purple: #5b5fff;
   --theme-highlight-lightorange: #d97e00;
   --theme-highlight-orange: #f13c00;
   --theme-highlight-red: #ed2655;
   --theme-highlight-pink: #b82ee5;
 
+  /* For accessibility purposes we want to enhance the focus styling. This
+   * should improve keyboard navigation usability. */
+  --theme-focus-outline-color: #000000;
+
   /* Colors used in Graphs, like performance tools. Similar colors to Chrome's timeline. */
   --theme-graphs-green: #85d175;
   --theme-graphs-blue: #83b7f6;
   --theme-graphs-bluegrey: #0072ab;
   --theme-graphs-purple: #b693eb;
   --theme-graphs-yellow: #efc052;
   --theme-graphs-orange: #d97e00;
   --theme-graphs-red: #e57180;
@@ -84,16 +88,20 @@
   --theme-highlight-blue: #46afe3;
   --theme-highlight-bluegrey: #5e88b0;
   --theme-highlight-purple: #6b7abb;
   --theme-highlight-lightorange: #d99b28;
   --theme-highlight-orange: #d96629;
   --theme-highlight-red: #eb5368;
   --theme-highlight-pink: #df80ff;
 
+  /* For accessibility purposes we want to enhance the focus styling. This
+   * should improve keyboard navigation usability. */
+  --theme-focus-outline-color: #ced3d9;
+
   /* Colors used in Graphs, like performance tools. Mostly similar to some "highlight-*" colors. */
   --theme-graphs-green: #70bf53;
   --theme-graphs-blue: #46afe3;
   --theme-graphs-bluegrey: #5e88b0;
   --theme-graphs-purple: #df80ff;
   --theme-graphs-yellow: #d99b28;
   --theme-graphs-orange: #d96629;
   --theme-graphs-red: #eb5368;
@@ -153,8 +161,20 @@
   /* Font size */
   --theme-toolbar-font-size: 12px;
 
   /* Header */
   --theme-header-background: #F0F0F0 linear-gradient(to top,
                                                      rgba(0, 0, 0, 0.1),
                                                      transparent) repeat-x;
 }
+
+:root {
+  --theme-focus-border-color-textbox: #0675d3;
+  --theme-textbox-box-shadow: rgba(97,181,255,.75);
+
+  /* For accessibility purposes we want to enhance the focus styling. This
+   * should improve keyboard navigation usability. */
+  --theme-focus-outline: 1px dotted var(--theme-focus-outline-color);
+  --theme-focus-box-shadow-textbox: 0 0 0 1px var(--theme-textbox-box-shadow);
+  --theme-focus-box-shadow-inset-bottom: 0 -2px 1px var(--theme-textbox-box-shadow) inset,
+    0px -2px var(--theme-highlight-blue) inset;
+}
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -401,32 +401,39 @@ a {
 
 .theme-firebug .jsterm-input-container {
   border-top: 1px solid #ccc;
 }
 
 .jsterm-input-node,
 .jsterm-complete-node {
   border: none;
-  padding: 0 0 0 16px;
+  padding: 4px;
+  padding-inline-start: 20px;
+  margin: 0;
   -moz-appearance: none;
   background-color: transparent;
 }
 
+.jsterm-input-node[focused="true"] {
+  box-shadow: var(--theme-focus-box-shadow-inset-bottom);
+}
+
 .jsterm-complete-node {
   color: var(--theme-comment);
 }
 
 .jsterm-input-node {
   /* Always allow scrolling on input - it auto expands in js by setting height,
      but don't want it to get bigger than the window. 24px = toolbar height. */
   max-height: calc(90vh - 24px);
   background-image: var(--command-line-image);
   background-repeat: no-repeat;
   background-size: 16px 16px;
+  background-position: 4px 50%;
   color: var(--theme-content-color1);
 }
 
 @media (min-resolution: 1.1dppx) {
   .jsterm-input-node {
     background-image: var(--command-line-image-2x);
   }
 }
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -244,16 +244,25 @@
   min-width: 65px;
   margin: 0;
   padding: 0 8px 0 20px;
   border: none;
   outline: none;
   color: hsl(210,30%,85%);
 }
 
+.breadcrumbs-widget-item:-moz-focusring {
+  outline: none;
+}
+
+.breadcrumbs-widget-item[checked]:-moz-focusring > .button-box {
+  outline: var(--theme-focus-outline);
+  outline-offset: -1px;
+}
+
 .breadcrumbs-widget-item > .button-box {
   border: none;
   padding-top: 0;
   padding-bottom: 0;
 }
 
 :root[platform="win"] .breadcrumbs-widget-item:-moz-focusring > .button-box {
   border-width: 0;
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -36,16 +36,17 @@ interface nsIContentViewer : nsISupports
 {
   [noscript] void init(in nsIWidgetPtr aParentWidget,
                        [const] in nsIntRectRef aBounds);
 
   attribute nsIDocShell container;
 
   [noscript,notxpcom,nostdcall] void loadStart(in nsIDocument aDoc);
   void loadComplete(in nsresult aStatus);
+  [noscript] readonly attribute boolean loadCompleted;
 
   /**
    * Checks if the document wants to prevent unloading by firing beforeunload on
    * the document, and if it does, prompts the user. The result is returned.
    */
   boolean permitUnload();
 
   /**
--- a/dom/base/WindowNamedPropertiesHandler.h
+++ b/dom/base/WindowNamedPropertiesHandler.h
@@ -32,19 +32,19 @@ public:
                  JS::ObjectOpResult &result) const override;
   virtual bool
   ownPropNames(JSContext* aCx, JS::Handle<JSObject*> aProxy, unsigned flags,
                JS::AutoIdVector& aProps) const override;
   virtual bool
   delete_(JSContext* aCx, JS::Handle<JSObject*> aProxy, JS::Handle<jsid> aId,
           JS::ObjectOpResult &aResult) const override;
 
-  // No need for getPrototypeIfOrdinary here: this object shouldn't have a
-  // lazy prototype, so this trap would never be called (and the inherited
-  // version, from BaseProxyHandler, just crashes).
+  // No need for getPrototypeIfOrdinary here: window named-properties objects
+  // have static prototypes, so the version inherited from BaseDOMProxyHandler
+  // will do the right thing.
 
   virtual bool
   preventExtensions(JSContext* aCx, JS::Handle<JSObject*> aProxy,
                     JS::ObjectOpResult& aResult) const override
   {
     return aResult.failCantPreventExtensions();
   }
   virtual bool
--- a/dom/base/nsContentSink.cpp
+++ b/dom/base/nsContentSink.cpp
@@ -9,16 +9,17 @@
  * DOM based on information from the parser.
  */
 
 #include "nsContentSink.h"
 #include "nsScriptLoader.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "mozilla/css/Loader.h"
+#include "mozilla/dom/SRILogHelper.h"
 #include "nsStyleLinkElement.h"
 #include "nsIDocShell.h"
 #include "nsILoadContext.h"
 #include "nsCPrefetchService.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsIMIMEHeaderParam.h"
 #include "nsIProtocolHandler.h"
@@ -48,23 +49,16 @@
 #include "nsHTMLDNSPrefetch.h"
 #include "nsIObserverService.h"
 #include "mozilla/Preferences.h"
 #include "nsParserConstants.h"
 #include "nsSandboxFlags.h"
 
 using namespace mozilla;
 
-static LogModule*
-GetSriLog()
-{
-  static LazyLogModule gSriPRLog("SRI");
-  return gSriPRLog;
-}
-
 LazyLogModule gContentSinkLogModuleInfo("nscontentsink");
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsContentSink)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsContentSink)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsContentSink)
   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
@@ -757,17 +751,17 @@ nsContentSink::ProcessStyleLink(nsIConte
                aElement->NodeType() == nsIDOMNode::PROCESSING_INSTRUCTION_NODE,
                "We only expect processing instructions here");
 
   nsAutoString integrity;
   if (aElement) {
     aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
   }
   if (!integrity.IsEmpty()) {
-    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+    MOZ_LOG(dom::SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
             ("nsContentSink::ProcessStyleLink, integrity=%s",
              NS_ConvertUTF16toUTF8(integrity).get()));
   }
 
   // If this is a fragment parser, we don't want to observe.
   // We don't support CORS for processing instructions
   bool isAlternate;
   rv = mCSSLoader->LoadStyleLink(aElement, url, aTitle, aMedia, aAlternate,
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7373,37 +7373,41 @@ nsContentUtils::TransferableToIPCTransfe
           }
 
           // Images to be placed on the clipboard are imgIContainers.
           nsCOMPtr<imgIContainer> image(do_QueryInterface(data));
           if (image) {
             RefPtr<mozilla::gfx::SourceSurface> surface =
               image->GetFrame(imgIContainer::FRAME_CURRENT,
                               imgIContainer::FLAG_SYNC_DECODE);
-            if (surface) {
-              RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
-                surface->GetDataSurface();
-              size_t length;
-              int32_t stride;
-              mozilla::UniquePtr<char[]> surfaceData =
-                nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
-
-              IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-              item->flavor() = flavorStr;
-              // Turn item->data() into an nsCString prior to accessing it.
-              item->data() = EmptyCString();
-              item->data().get_nsCString().Adopt(surfaceData.release(), length);
-
-              IPCDataTransferImage& imageDetails = item->imageDetails();
-              mozilla::gfx::IntSize size = dataSurface->GetSize();
-              imageDetails.width() = size.width;
-              imageDetails.height() = size.height;
-              imageDetails.stride() = stride;
-              imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
+            if (!surface) {
+              continue;
+            }
+            RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+              surface->GetDataSurface();
+            if (!dataSurface) {
+              continue;
             }
+            size_t length;
+            int32_t stride;
+            mozilla::UniquePtr<char[]> surfaceData =
+              nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
+
+            IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+            item->flavor() = flavorStr;
+            // Turn item->data() into an nsCString prior to accessing it.
+            item->data() = EmptyCString();
+            item->data().get_nsCString().Adopt(surfaceData.release(), length);
+
+            IPCDataTransferImage& imageDetails = item->imageDetails();
+            mozilla::gfx::IntSize size = dataSurface->GetSize();
+            imageDetails.width() = size.width;
+            imageDetails.height() = size.height;
+            imageDetails.stride() = stride;
+            imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
 
             continue;
           }
 
           // Otherwise, handle this as a file.
           nsCOMPtr<BlobImpl> blobImpl;
           nsCOMPtr<nsIFile> file = do_QueryInterface(data);
           if (file) {
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -1015,20 +1015,38 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   ourFrameFrame->EndSwapDocShells(otherFrame);
 
   ourShell->BackingScaleFactorChanged();
   otherShell->BackingScaleFactorChanged();
 
   ourDoc->FlushPendingNotifications(Flush_Layout);
   otherDoc->FlushPendingNotifications(Flush_Layout);
 
+  // Initialize browser API if needed now that owner content has changed.
+  InitializeBrowserAPI();
+  aOther->InitializeBrowserAPI();
+
   mInSwap = aOther->mInSwap = false;
 
-  Unused << mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
-  Unused << aOther->mRemoteBrowser->SendSwappedWithOtherRemoteLoader();
+  // Send an updated tab context since owner content type may have changed.
+  MutableTabContext ourContext;
+  rv = GetNewTabContext(&ourContext);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  MutableTabContext otherContext;
+  rv = aOther->GetNewTabContext(&otherContext);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  Unused << mRemoteBrowser->SendSwappedWithOtherRemoteLoader(
+    ourContext.AsIPCTabContext());
+  Unused << aOther->mRemoteBrowser->SendSwappedWithOtherRemoteLoader(
+    otherContext.AsIPCTabContext());
   return NS_OK;
 }
 
 class MOZ_RAII AutoResetInFrameSwap final
 {
 public:
   AutoResetInFrameSwap(nsFrameLoader* aThisFrameLoader,
                        nsFrameLoader* aOtherFrameLoader,
@@ -1424,16 +1442,20 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their
   // backing scale factor may have changed. (Bug 822266)
   ourShell->BackingScaleFactorChanged();
   otherShell->BackingScaleFactorChanged();
 
   ourParentDocument->FlushPendingNotifications(Flush_Layout);
   otherParentDocument->FlushPendingNotifications(Flush_Layout);
 
+  // Initialize browser API if needed now that owner content has changed
+  InitializeBrowserAPI();
+  aOther->InitializeBrowserAPI();
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameLoader::Destroy()
 {
   StartDestroy();
   return NS_OK;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -920,23 +920,24 @@ nsOuterWindowProxy::getPrototypeIfOrdina
                                            JS::Handle<JSObject*> proxy,
                                            bool* isOrdinary,
                                            JS::MutableHandle<JSObject*> protop) const
 {
   // Window's [[GetPrototypeOf]] trap isn't the ordinary definition:
   //
   //   https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof
   //
-  // We nonetheless can implement it here using a non-"lazy" [[Prototype]],
-  // because wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp)
-  // supply all the non-ordinary behavior.
+  // We nonetheless can implement it with a static [[Prototype]], because
+  // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply
+  // all non-ordinary behavior.
   //
   // But from a spec point of view, it's the exact same object in both cases --
-  // only the observer's changed.  So both cases *must* report non-ordinary,
-  // even if non-"lazy" [[Prototype]] usually means ordinary.
+  // only the observer's changed.  So this getPrototypeIfOrdinary trap on the
+  // non-wrapper object *must* report non-ordinary, even if static [[Prototype]]
+  // usually means ordinary.
   *isOrdinary = false;
   return true;
 }
 
 bool
 nsOuterWindowProxy::preventExtensions(JSContext* cx,
                                       JS::Handle<JSObject*> proxy,
                                       JS::ObjectOpResult& result) const
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -13,16 +13,17 @@
 #include "prsystem.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
 #include "xpcpublic.h"
 #include "nsIContent.h"
 #include "nsJSUtils.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/SRILogHelper.h"
 #include "nsGkAtoms.h"
 #include "nsNetUtil.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptContext.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsJSPrincipals.h"
 #include "nsContentPolicyUtils.h"
@@ -55,23 +56,16 @@
 #include "mozilla/unused.h"
 #include "nsIScriptError.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 static LazyLogModule gCspPRLog("CSP");
 
-static LogModule*
-GetSriLog()
-{
-  static LazyLogModule gSriPRLog("SRI");
-  return gSriPRLog;
-}
-
 NS_IMPL_ISUPPORTS0(nsScriptLoadRequest)
 
 nsScriptLoadRequestList::~nsScriptLoadRequestList()
 {
   Clear();
 }
 
 void
@@ -545,17 +539,17 @@ nsScriptLoader::ProcessScriptElement(nsI
       // no usable preload
 
       SRIMetadata sriMetadata;
       {
         nsAutoString integrity;
         scriptContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity,
                                integrity);
         if (!integrity.IsEmpty()) {
-          MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+          MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
                  ("nsScriptLoader::ProcessScriptElement, integrity=%s",
                   NS_ConvertUTF16toUTF8(integrity).get()));
           SRICheck::IntegrityMetadata(integrity, mDocument, &sriMetadata);
         }
       }
 
       request = new nsScriptLoadRequest(aElement, version, ourCORSMode,
                                         sriMetadata);
@@ -1426,17 +1420,17 @@ nsScriptLoader::OnStreamComplete(nsIIncr
       rv = NS_ERROR_SRI_CORRUPT;
     }
   } else {
     nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
 
     bool enforceSRI = false;
     loadInfo->GetEnforceSRI(&enforceSRI);
     if (enforceSRI) {
-      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+      MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
              ("nsScriptLoader::OnStreamComplete, required SRI not found"));
       rv = NS_ERROR_SRI_CORRUPT;
     }
   }
 
   if (NS_SUCCEEDED(rv)) {
     rv = PrepareLoadedRequest(request, aLoader, aChannelStatus, aString);
   }
@@ -1652,17 +1646,17 @@ nsScriptLoader::PreloadURI(nsIURI *aURI,
 {
   // Check to see if scripts has been turned off.
   if (!mEnabled || !mDocument->IsScriptEnabled()) {
     return;
   }
 
   SRIMetadata sriMetadata;
   if (!aIntegrity.IsEmpty()) {
-    MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+    MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
            ("nsScriptLoader::PreloadURI, integrity=%s",
             NS_ConvertUTF16toUTF8(aIntegrity).get()));
     SRICheck::IntegrityMetadata(aIntegrity, mDocument, &sriMetadata);
   }
 
   RefPtr<nsScriptLoadRequest> request =
     new nsScriptLoadRequest(nullptr, 0,
                             Element::StringToCORSMode(aCrossOrigin),
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -13,16 +13,17 @@
 #include "nsStyleLinkElement.h"
 
 #include "mozilla/StyleSheetHandle.h"
 #include "mozilla/StyleSheetHandleInlines.h"
 #include "mozilla/css/Loader.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FragmentOrElement.h"
 #include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/SRILogHelper.h"
 #include "mozilla/Preferences.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsIDOMComment.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
@@ -30,23 +31,16 @@
 #include "nsUnicharInputStream.h"
 #include "nsContentUtils.h"
 #include "nsStyleUtil.h"
 #include "nsQueryObject.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-static LogModule*
-GetSriLog()
-{
-  static LazyLogModule gSriPRLog("SRI");
-  return gSriPRLog;
-}
-
 nsStyleLinkElement::nsStyleLinkElement()
   : mDontLoadStyle(false)
   , mUpdatesEnabled(true)
   , mLineNumber(1)
 {
 }
 
 nsStyleLinkElement::~nsStyleLinkElement()
@@ -430,17 +424,17 @@ nsStyleLinkElement::DoUpdateStyleSheet(n
     rv = doc->CSSLoader()->
       LoadInlineStyle(thisContent, text, mLineNumber, title, media,
                       scopeElement, aObserver, &doneLoading, &isAlternate);
   }
   else {
     nsAutoString integrity;
     thisContent->GetAttr(kNameSpaceID_None, nsGkAtoms::integrity, integrity);
     if (!integrity.IsEmpty()) {
-      MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug,
+      MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug,
               ("nsStyleLinkElement::DoUpdateStyleSheet, integrity=%s",
                NS_ConvertUTF16toUTF8(integrity).get()));
     }
 
     // XXXbz clone the URI here to work around content policies modifying URIs.
     nsCOMPtr<nsIURI> clonedURI;
     uri->Clone(getter_AddRefs(clonedURI));
     NS_ENSURE_TRUE(clonedURI, NS_ERROR_OUT_OF_MEMORY);
--- a/dom/base/test/chrome/test_swapFrameLoaders.xul
+++ b/dom/base/test/chrome/test_swapFrameLoaders.xul
@@ -13,14 +13,13 @@ Test swapFrameLoaders with different fra
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1242644"
      target="_blank">Mozilla Bug 1242644</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
   SimpleTest.waitForExplicitFinish();
-  SimpleTest.requestLongerTimeout(100);
 
   window.open("window_swapFrameLoaders.xul", "bug1242644",
               "chrome,width=600,height=600");
   ]]></script>
 </window>
--- a/dom/base/test/chrome/window_swapFrameLoaders.xul
+++ b/dom/base/test/chrome/window_swapFrameLoaders.xul
@@ -31,16 +31,30 @@ Test swapFrameLoaders with different fra
     ["html", "xul"],
     ["html", "html"],
     ["xul", "xul", "remote"],
     ["xul", "html", "remote"],
     ["html", "xul", "remote"],
     ["html", "html", "remote"],
   ];
 
+  const HEIGHTS = [
+    200,
+    400
+  ];
+
+  function frameScript() {
+    addEventListener("load", function onLoad() {
+      sendAsyncMessage("test:load");
+    }, true);
+  }
+
+  // Watch for loads in new frames
+  window.messageManager.loadFrameScript(`data:,(${frameScript})();`, true);
+
   function once(target, eventName, useCapture = false) {
     info("Waiting for event: '" + eventName + "' on " + target + ".");
 
     return new Promise(resolve => {
       for (let [add, remove] of [
         ["addEventListener", "removeEventListener"],
         ["addMessageListener", "removeMessageListener"],
       ]) {
@@ -51,51 +65,57 @@ Test swapFrameLoaders with different fra
             resolve(aArgs);
           }, useCapture);
           break;
         }
       }
     });
   }
 
-  function* addFrame(type, remote) {
+  function* addFrame(type, remote, height) {
     let frame = document.createElementNS(NS[type], TAG[type]);
     frame.setAttribute("remote", remote);
     if (remote && type == "xul") {
       frame.setAttribute("style", "-moz-binding: none;");
     }
     if (type == "html") {
       frame.setAttribute("mozbrowser", "true");
       frame.setAttribute("noisolation", "true");
       frame.setAttribute("allowfullscreen", "true");
     } else if (type == "xul") {
       frame.setAttribute("type", "content");
     }
-    frame.setAttribute("src", "about:blank");
+    let src = `data:text/html,<!doctype html>` +
+              `<body style="height:${height}px"/>`;
+    frame.setAttribute("src", src);
     document.documentElement.appendChild(frame);
+    let mm = frame.frameLoader.messageManager;
+    yield once(mm, "test:load");
     return frame;
   }
 
   add_task(function*() {
     yield new Promise(resolve => {
       SpecialPowers.pushPrefEnv(
         { "set": [["dom.mozBrowserFramesEnabled", true]] },
         resolve);
     });
   });
 
   add_task(function*() {
     for (let scenario of SCENARIOS) {
       let [ typeA, typeB, remote ] = scenario;
       remote = !!remote;
-      info(`Adding frame A, type ${typeA}, remote ${remote}`);
-      let frameA = yield addFrame(typeA, remote);
+      let heightA = HEIGHTS[0];
+      info(`Adding frame A, type ${typeA}, remote ${remote}, height ${heightA}`);
+      let frameA = yield addFrame(typeA, remote, heightA);
 
-      info(`Adding frame B, type ${typeB}, remote ${remote}`);
-      let frameB = yield addFrame(typeB, remote);
+      let heightB = HEIGHTS[1];
+      info(`Adding frame B, type ${typeB}, remote ${remote}, height ${heightB}`);
+      let frameB = yield addFrame(typeB, remote, heightB);
 
       let frameScriptFactory = function(name) {
         return `function() {
           addMessageListener("ping", function() {
             sendAsyncMessage("pong", "${name}");
           });
         }`;
       }
@@ -123,16 +143,28 @@ Test swapFrameLoaders with different fra
         is(pongA, "A", "Frame A message manager gets reply A before swap");
 
         info("Ping message manager for frame B");
         mmB.sendAsyncMessage("ping");
         let [ { data: pongB } ] = yield inflightB;
         is(pongB, "B", "Frame B message manager gets reply B before swap");
       }
 
+      // Check height before swap
+      {
+        if (frameA.getContentDimensions) {
+          let { height } = yield frameA.getContentDimensions();
+          is(height, heightA, "Frame A's content height is 200px before swap");
+        }
+        if (frameB.getContentDimensions) {
+          let { height } = yield frameB.getContentDimensions();
+          is(height, heightB, "Frame B's content height is 400px before swap");
+        }
+      }
+
       // Ping after swap using message managers acquired before
       {
         let mmA = frameA.frameLoader.messageManager;
         let mmB = frameB.frameLoader.messageManager;
 
         info("swapFrameLoaders");
         frameA.swapFrameLoaders(frameB);
 
@@ -145,16 +177,28 @@ Test swapFrameLoaders with different fra
         is(pongA, "B", "Frame A message manager acquired before swap gets reply B after swap");
 
         info("Ping message manager for frame B");
         mmB.sendAsyncMessage("ping");
         let [ { data: pongB } ] = yield inflightB;
         is(pongB, "A", "Frame B message manager acquired before swap gets reply A after swap");
       }
 
+      // Check height after swap
+      {
+        if (frameA.getContentDimensions) {
+          let { height } = yield frameA.getContentDimensions();
+          is(height, heightB, "Frame A's content height is 400px after swap");
+        }
+        if (frameB.getContentDimensions) {
+          let { height } = yield frameB.getContentDimensions();
+          is(height, heightA, "Frame B's content height is 200px after swap");
+        }
+      }
+
       // Ping after swap using message managers acquired after
       {
         let mmA = frameA.frameLoader.messageManager;
         let mmB = frameB.frameLoader.messageManager;
 
         let inflightA = once(mmA, "pong");
         let inflightB = once(mmB, "pong");
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -11661,16 +11661,33 @@ class CGDOMJSProxyHandler_getInstance(Cl
 
     def getBody(self):
         return dedent("""
             static const DOMProxyHandler instance;
             return &instance;
             """)
 
 
+class CGDOMJSProxyHandler_getPrototypeIfOrdinary(ClassMethod):
+    def __init__(self):
+        args = [Argument('JSContext*', 'cx'),
+                Argument('JS::Handle<JSObject*>', 'proxy'),
+                Argument('bool*', 'isOrdinary'),
+                Argument('JS::MutableHandle<JSObject*>', 'proto')]
+
+        ClassMethod.__init__(self, "getPrototypeIfOrdinary", "bool", args,
+                             virtual=True, override=True, const=True)
+
+    def getBody(self):
+        return dedent("""
+            *isOrdinary = false;
+            return true;
+            """)
+
+
 class CGDOMJSProxyHandler_call(ClassMethod):
     def __init__(self):
         args = [Argument('JSContext*', 'cx'),
                 Argument('JS::Handle<JSObject*>', 'proxy'),
                 Argument('const JS::CallArgs&', 'args')]
 
         ClassMethod.__init__(self, "call", "bool", args, virtual=True, override=True, const=True)
 
@@ -11692,17 +11709,18 @@ class CGDOMJSProxyHandler_isCallable(Cla
         return dedent("""
             return true;
         """)
 
 
 class CGDOMJSProxyHandler(CGClass):
     def __init__(self, descriptor):
         assert (descriptor.supportsIndexedProperties() or
-                descriptor.supportsNamedProperties())
+                descriptor.supportsNamedProperties() or
+                descriptor.hasNonOrdinaryGetPrototypeOf())
         methods = [CGDOMJSProxyHandler_getOwnPropDescriptor(descriptor),
                    CGDOMJSProxyHandler_defineProperty(descriptor),
                    ClassUsingDeclaration("mozilla::dom::DOMProxyHandler",
                                          "defineProperty"),
                    CGDOMJSProxyHandler_ownPropNames(descriptor),
                    CGDOMJSProxyHandler_hasOwn(descriptor),
                    CGDOMJSProxyHandler_get(descriptor),
                    CGDOMJSProxyHandler_className(descriptor),
@@ -11719,16 +11737,18 @@ class CGDOMJSProxyHandler(CGClass):
         ]
 
         if descriptor.supportsIndexedProperties():
             methods.append(CGDOMJSProxyHandler_getElements(descriptor))
         if (descriptor.operations['IndexedSetter'] is not None or
             (descriptor.operations['NamedSetter'] is not None and
              descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
             methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
+        if descriptor.hasNonOrdinaryGetPrototypeOf():
+            methods.append(CGDOMJSProxyHandler_getPrototypeIfOrdinary())
         if descriptor.operations['LegacyCaller']:
             methods.append(CGDOMJSProxyHandler_call())
             methods.append(CGDOMJSProxyHandler_isCallable())
 
         CGClass.__init__(self, 'DOMProxyHandler',
                          bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
                          constructors=constructors,
                          methods=methods)
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -510,17 +510,18 @@ class Descriptor(DescriptorProvider):
                                         "non-leaf interface %s.\n%s" %
                                         (iface, iface.location))
 
                 iface.setUserData('hasConcreteDescendant', True)
                 iface = iface.parent
 
             self.proxy = (self.supportsIndexedProperties() or
                           (self.supportsNamedProperties() and
-                           not self.hasNamedPropertiesObject))
+                           not self.hasNamedPropertiesObject) or
+                          self.hasNonOrdinaryGetPrototypeOf())
 
             if self.proxy:
                 if (not self.operations['IndexedGetter'] and
                     (self.operations['IndexedSetter'] or
                      self.operations['IndexedDeleter'] or
                      self.operations['IndexedCreator'])):
                     raise SyntaxError("%s supports indexed properties but does "
                                       "not have an indexed getter.\n%s" %
@@ -742,16 +743,19 @@ class Descriptor(DescriptorProvider):
         return attrs
 
     def supportsIndexedProperties(self):
         return self.operations['IndexedGetter'] is not None
 
     def supportsNamedProperties(self):
         return self.operations['NamedGetter'] is not None
 
+    def hasNonOrdinaryGetPrototypeOf(self):
+        return self.interface.getExtendedAttribute("NonOrdinaryGetPrototypeOf")
+
     def needsConstructHookHolder(self):
         assert self.interface.hasInterfaceObject()
         return False
 
     def needsHeaderInclude(self):
         """
         An interface doesn't need a header file if it is not concrete, not
         pref-controlled, has no prototype object, has no static methods or
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -247,16 +247,26 @@ bool
 BaseDOMProxyHandler::ownPropertyKeys(JSContext* cx,
                                      JS::Handle<JSObject*> proxy,
                                      JS::AutoIdVector& props) const
 {
   return ownPropNames(cx, proxy, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
 }
 
 bool
+BaseDOMProxyHandler::getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                            bool* isOrdinary,
+                                            JS::MutableHandle<JSObject*> proto) const
+{
+  *isOrdinary = true;
+  proto.set(GetStaticPrototype(proxy));
+  return true;
+}
+
+bool
 BaseDOMProxyHandler::getOwnEnumerablePropertyKeys(JSContext* cx,
                                                   JS::Handle<JSObject*> proxy,
                                                   JS::AutoIdVector& props) const
 {
   return ownPropNames(cx, proxy, JSITER_OWNONLY, props);
 }
 
 bool
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -54,16 +54,20 @@ public:
   // Implementations of methods that can be implemented in terms of
   // other lower-level methods.
   bool getOwnPropertyDescriptor(JSContext* cx, JS::Handle<JSObject*> proxy,
                                 JS::Handle<jsid> id,
                                 JS::MutableHandle<JS::PropertyDescriptor> desc) const override;
   virtual bool ownPropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                                JS::AutoIdVector &props) const override;
 
+  virtual bool getPrototypeIfOrdinary(JSContext* cx, JS::Handle<JSObject*> proxy,
+                                      bool* isOrdinary,
+                                      JS::MutableHandle<JSObject*> proto) const override;
+
   // We override getOwnEnumerablePropertyKeys() and implement it directly
   // instead of using the default implementation, which would call
   // ownPropertyKeys and then filter out the non-enumerable ones. This avoids
   // unnecessary work during enumeration.
   virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, JS::Handle<JSObject*> proxy,
                                             JS::AutoIdVector &props) const override;
 
   bool watch(JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -1459,17 +1459,18 @@ class IDLInterface(IDLObjectWithScope, I
                 self.parentScope.globalNameMapping[self.identifier.name].add(self.identifier.name)
                 self._isOnGlobalProtoChain = True
             elif (identifier == "NeedResolve" or
                   identifier == "OverrideBuiltins" or
                   identifier == "ChromeOnly" or
                   identifier == "Unforgeable" or
                   identifier == "UnsafeInPrerendering" or
                   identifier == "LegacyEventInit" or
-                  identifier == "ProbablyShortLivingObject"):
+                  identifier == "ProbablyShortLivingObject" or
+                  identifier == "NonOrdinaryGetPrototypeOf"):
                 # Known extended attributes that do not take values
                 if not attr.noArguments():
                     raise WebIDLError("[%s] must take no arguments" % identifier,
                                       [attr.location])
             elif identifier == "Exposed":
                 convertExposedAttrToGlobalNameSet(attr,
                                                   self._exposureGlobalNames)
             elif (identifier == "Pref" or
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -28,52 +28,53 @@ function isTopBrowserElement(docShell) {
     docShell = parentDocShell(docShell);
     if (docShell && docShell.isMozBrowserOrApp) {
       return false;
     }
   }
   return true;
 }
 
-if (!('BrowserElementIsPreloaded' in this)) {
-  if (isTopBrowserElement(docShell)) {
-    if (Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) {
-      try {
-        Services.scriptloader.loadSubScript("chrome://global/content/forms.js");
-      } catch (e) {
+if (!BrowserElementIsReady) {
+  if (!('BrowserElementIsPreloaded' in this)) {
+    if (isTopBrowserElement(docShell)) {
+      if (Services.prefs.getBoolPref("dom.mozInputMethod.enabled")) {
+        try {
+          Services.scriptloader.loadSubScript("chrome://global/content/forms.js");
+        } catch (e) {
+        }
       }
     }
-  }
 
-  if(Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
-    // general content apps
-    if (isTopBrowserElement(docShell)) {
+    if(Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_CONTENT) {
+      // general content apps
+      if (isTopBrowserElement(docShell)) {
+        Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
+      }
+    } else {
+      // rocketbar in system app and other in-process case (ex. B2G desktop client)
       Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
     }
-  } else {
-    // rocketbar in system app and other in-process case (ex. B2G desktop client)
-    Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementCopyPaste.js");
-  }
 
-  if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
-    if (docShell.asyncPanZoomEnabled === false) {
-      Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js");
-      ContentPanningAPZDisabled.init();
+    if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
+      if (docShell.asyncPanZoomEnabled === false) {
+        Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanningAPZDisabled.js");
+        ContentPanningAPZDisabled.init();
+      }
+
+      Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
+      ContentPanning.init();
     }
 
-    Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementPanning.js");
-    ContentPanning.init();
-  }
-
-  Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
-} else {
-  if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
-    if (docShell.asyncPanZoomEnabled === false) {
-      ContentPanningAPZDisabled.init();
+    Services.scriptloader.loadSubScript("chrome://global/content/BrowserElementChildPreload.js");
+  } else {
+    if (Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 0) {
+      if (docShell.asyncPanZoomEnabled === false) {
+        ContentPanningAPZDisabled.init();
+      }
+      ContentPanning.init();
     }
-    ContentPanning.init();
   }
 }
 
 var BrowserElementIsReady = true;
 
-
 sendAsyncMessage('browser-element-api:call', { 'msg_name': 'hello' });
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2169,17 +2169,16 @@ CanvasRenderingContext2D::CreatePattern(
 
   // The canvas spec says that createPattern should use the first frame
   // of animated images
   nsLayoutUtils::SurfaceFromElementResult res =
     nsLayoutUtils::SurfaceFromElement(htmlElement,
       nsLayoutUtils::SFE_WANT_FIRST_FRAME, mTarget);
 
   if (!res.GetSourceSurface()) {
-    aError.Throw(NS_ERROR_NOT_AVAILABLE);
     return nullptr;
   }
 
   RefPtr<CanvasPattern> pat = new CanvasPattern(this, res.GetSourceSurface(), repeatMode,
                                                 res.mPrincipal, res.mIsWriteOnly,
                                                 res.mCORSUsed);
   return pat.forget();
 }
--- a/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
+++ b/dom/canvas/compiledtest/TestWebGLElementArrayCache.cpp
@@ -3,30 +3,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/. */
 
 #include "mozilla/Assertions.h"
 
 #include "WebGLElementArrayCache.cpp"
 
+#include <cstdio>
 #include <cstdlib>
-#include <iostream>
 #include "nscore.h"
 #include "nsTArray.h"
 
 int gTestsPassed = 0;
 
 void
 VerifyImplFunction(bool condition, const char* file, int line)
 {
   if (condition) {
     gTestsPassed++;
   } else {
-    std::cerr << "Test failed at " << file << ":" << line << std::endl;
+    std::fprintf(stderr, "Test failed at %s:%d\n", file, line);
     abort();
   }
 }
 
 #define VERIFY(condition) \
     VerifyImplFunction((condition), __FILE__, __LINE__)
 
 void
@@ -221,12 +221,12 @@ main(int argc, char* argv[])
               CheckValidateAllTypes(b, offset, subsize);
             }
           } // validateCalls
         } // bufferSubDataCalls
       } // j
     } // i
   } // maxBufferSize
 
-  std::cerr << argv[0] << ": all " << gTestsPassed << " tests passed" << std::endl;
+  std::fprintf(stderr, "%s: all %d tests passed\n", argv[0], gTestsPassed);
   return 0;
 }
 
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -40,17 +40,17 @@ support-files = test_bug336682.js
 skip-if = buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_bug412567.html]
 [test_bug418986-3.html]
 # Sometimes fails to finish after tests pass on 'B2G ICS Emulator'.
 skip-if = (os == 'b2g')
 [test_bug422132.html]
 skip-if = buildapp == 'b2g' # b2g(2 failures out of 8, mousewheel test) b2g-debug(2 failures out of 8, mousewheel test) b2g-desktop(2 failures out of 8, mousewheel test)
 [test_bug426082.html]
-skip-if = buildapp == 'mulet' || buildapp == 'b2g' || os == "win" || toolkit == 'android' || e10s # Intermittent failures, bug 921693 # b2g(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-debug(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-desktop(1 failure out of 6, Moving the mouse down from the label should have unpressed the button)
+skip-if = buildapp == 'mulet' || buildapp == 'b2g' || os == "win" || toolkit == 'android' # b2g(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-debug(1 failure out of 6, Moving the mouse down from the label should have unpressed the button) b2g-desktop(1 failure out of 6, Moving the mouse down from the label should have unpressed the button)
 [test_bug427537.html]
 [test_bug1003432.html]
 support-files = test_bug1003432.js
 [test_bug428988.html]
 [test_bug432698.html]
 skip-if = buildapp == 'mulet'
 [test_bug443985.html]
 [test_bug447736.html]
@@ -100,17 +100,17 @@ skip-if = toolkit == 'android' #CRASH_DU
 [test_bug635465.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug641477.html]
 [test_bug648573.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug650493.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug656379-1.html]
-skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' || e10s #TIMED_OUT #Bug 931116, b2g desktop specific, initial triage
+skip-if = (buildapp == 'b2g' && toolkit != 'gonk') || toolkit == 'android' #Bug 931116, b2g desktop specific, initial triage
 [test_bug656379-2.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug656954.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
 [test_bug659071.html]
 skip-if = buildapp == 'b2g' # b2g(1 failure out of 2, mousewheel zoom test) b2g-debug(1 failure out of 2, mousewheel zoom test) b2g-desktop(1 failure out of 2, mousewheel zoom test)
 [test_bug659350.html]
 skip-if = toolkit == 'android' #CRASH_DUMP, RANDOM
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -835,37 +835,37 @@ ContentChild::ProvideWindowCommon(TabChi
   }
 
   Unused << SendPBrowserConstructor(
     // We release this ref in DeallocPBrowserChild
     RefPtr<TabChild>(newChild).forget().take(),
     tabId, *ipcContext, aChromeFlags,
     GetID(), IsForApp(), IsForBrowser());
 
-  nsAutoCString url;
-  if (aURI) {
-    aURI->GetSpec(url);
-  } else {
-    // We can't actually send a nullptr up as the URI, since IPDL doesn't let us
-    // send nullptr's for primitives. We indicate that the nsString for the URI
-    // should be converted to a nullptr by voiding the string.
-    url.SetIsVoid(true);
-  }
-
   nsString name(aName);
   nsAutoCString features(aFeatures);
   nsTArray<FrameScriptInfo> frameScripts;
   nsCString urlToLoad;
 
   PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor();
   TextureFactoryIdentifier textureFactoryIdentifier;
   uint64_t layersId = 0;
 
   if (aIframeMoz) {
     MOZ_ASSERT(aTabOpener);
+    nsAutoCString url;
+    if (aURI) {
+      aURI->GetSpec(url);
+    } else {
+      // We can't actually send a nullptr up as the URI, since IPDL doesn't let us
+      // send nullptr's for primitives. We indicate that the nsString for the URI
+      // should be converted to a nullptr by voiding the string.
+      url.SetIsVoid(true);
+    }
+
     newChild->SendBrowserFrameOpenWindow(aTabOpener, renderFrame, NS_ConvertUTF8toUTF16(url),
                                          name, NS_ConvertUTF8toUTF16(features),
                                          aWindowIsNew, &textureFactoryIdentifier,
                                          &layersId);
   } else {
     nsAutoCString baseURIString;
     if (aTabOpener) {
       auto* opener = nsPIDOMWindowOuter::From(aParent);
@@ -890,17 +890,17 @@ ContentChild::ProvideWindowCommon(TabChi
       if (cv) {
         cv->GetFullZoom(&fullZoom);
       }
     }
 
     nsresult rv;
     if (!SendCreateWindow(aTabOpener, newChild, renderFrame,
                           aChromeFlags, aCalledFromJS, aPositionSpecified,
-                          aSizeSpecified, url,
+                          aSizeSpecified,
                           name, features,
                           baseURIString,
                           openerDocShell
                             ? openerDocShell->GetOriginAttributes()
                             : DocShellOriginAttributes(),
                           fullZoom,
                           &rv,
                           aWindowIsNew,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -5358,17 +5358,16 @@ ContentParent::DeallocPWebBrowserPersist
 bool
 ContentParent::RecvCreateWindow(PBrowserParent* aThisTab,
                                 PBrowserParent* aNewTab,
                                 PRenderFrameParent* aRenderFrame,
                                 const uint32_t& aChromeFlags,
                                 const bool& aCalledFromJS,
                                 const bool& aPositionSpecified,
                                 const bool& aSizeSpecified,
-                                const nsCString& aURI,
                                 const nsString& aName,
                                 const nsCString& aFeatures,
                                 const nsCString& aBaseURI,
                                 const DocShellOriginAttributes& aOpenerOriginAttributes,
                                 const float& aFullZoom,
                                 nsresult* aResult,
                                 bool* aWindowIsNew,
                                 InfallibleTArray<FrameScriptInfo>* aFrameScripts,
@@ -5491,54 +5490,23 @@ ContentParent::RecvCreateWindow(PBrowser
     if (!newTab->SetRenderFrame(rfp) ||
         !newTab->GetRenderFrameInfo(aTextureFactoryIdentifier, aLayersId)) {
       *aResult = NS_ERROR_FAILURE;
     }
 
     return true;
   }
 
-  // WindowWatcher is going to expect a valid URI to open a window
-  // to. If it can't find one, it's going to attempt to figure one
-  // out on its own, which is problematic because it can't access
-  // the document for the remote browser we're opening. Luckily,
-  // TabChild has sent us a baseURI with which we can ensure that
-  // the URI we pass to WindowWatcher is valid.
-  nsCOMPtr<nsIURI> baseURI;
-  *aResult = NS_NewURI(getter_AddRefs(baseURI), aBaseURI);
-
-  if (NS_WARN_IF(NS_FAILED(*aResult))) {
-    return true;
-  }
-
-  nsAutoCString finalURIString;
-  if (!aURI.IsEmpty()) {
-    nsCOMPtr<nsIURI> finalURI;
-    *aResult = NS_NewURI(getter_AddRefs(finalURI), aURI.get(), baseURI);
-
-    if (NS_WARN_IF(NS_FAILED(*aResult))) {
-      return true;
-    }
-
-    finalURI->GetSpec(finalURIString);
-  }
-
   nsCOMPtr<mozIDOMWindowProxy> window;
-
   TabParent::AutoUseNewTab aunt(newTab, aWindowIsNew, aURLToLoad);
 
-  // If nsWindowWatcher::OpenWindowInternal was passed a nullptr for the URI
-  // to open in the content process, we indicate that by sending up a voided
-  // nsString (since primitives are not nullable). If we detect the voided
-  // nsString, we know that we need to send OpenWindow2 a nullptr for the URI.
-  const char* uri = aURI.IsVoid() ? nullptr : finalURIString.get();
   const char* name = aName.IsVoid() ? nullptr : NS_ConvertUTF16toUTF8(aName).get();
   const char* features = aFeatures.IsVoid() ? nullptr : aFeatures.get();
 
-  *aResult = pwwatch->OpenWindow2(parent, uri, name, features, aCalledFromJS,
+  *aResult = pwwatch->OpenWindow2(parent, nullptr, name, features, aCalledFromJS,
                                   false, false, thisTabParent, nullptr,
                                   aFullZoom, 1, getter_AddRefs(window));
 
   if (NS_WARN_IF(!window)) {
     return true;
   }
 
   *aResult = NS_ERROR_FAILURE;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -497,17 +497,16 @@ public:
 
   virtual bool RecvCreateWindow(PBrowserParent* aThisTabParent,
                                 PBrowserParent* aOpener,
                                 layout::PRenderFrameParent* aRenderFrame,
                                 const uint32_t& aChromeFlags,
                                 const bool& aCalledFromJS,
                                 const bool& aPositionSpecified,
                                 const bool& aSizeSpecified,
-                                const nsCString& aURI,
                                 const nsString& aName,
                                 const nsCString& aFeatures,
                                 const nsCString& aBaseURI,
                                 const DocShellOriginAttributes& aOpenerOriginAttributes,
                                 const float& aFullZoom,
                                 nsresult* aResult,
                                 bool* aWindowIsNew,
                                 InfallibleTArray<FrameScriptInfo>* aFrameScripts,
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -14,16 +14,17 @@ include protocol PDocumentRenderer;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
 include DOMTypes;
 include JavaScriptTypes;
 include URIParams;
 include BrowserConfiguration;
+include PTabContext;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
 using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
 using CSSSize from "Units.h";
 using mozilla::LayoutDeviceIntRect from "Units.h";
@@ -719,17 +720,17 @@ child:
      * Tell the child of an app's offline status
      */
     async AppOfflineStatus(uint32_t id, bool offline);
 
     /**
      * Tell the browser that its frame loader has been swapped
      * with another.
      */
-    async SwappedWithOtherRemoteLoader();
+    async SwappedWithOtherRemoteLoader(IPCTabContext context);
 
     /**
      * A potential accesskey was just pressed. Look for accesskey targets
      * using the list of provided charCodes.
      *
      * @param charCode array of potential character codes
      * @param isTrusted true if triggered by a trusted key event
      * @param modifierMask indicates which accesskey modifiers are pressed
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -1132,17 +1132,16 @@ parent:
 
     sync CreateWindow(nullable PBrowser aThisTab,
                       PBrowser aNewTab,
                       PRenderFrame aRenderFrame,
                       uint32_t aChromeFlags,
                       bool aCalledFromJS,
                       bool aPositionSpecified,
                       bool aSizeSpecified,
-                      nsCString aURI,
                       nsString aName,
                       nsCString aFeatures,
                       nsCString aBaseURI,
                       DocShellOriginAttributes aOpenerOriginAttributes,
                       float aFullZoom)
       returns (nsresult rv,
                bool windowOpened,
                FrameScriptInfo[] frameScripts,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2282,17 +2282,17 @@ TabChild::RecvAppOfflineStatus(const uin
   if (gIOService && ioService) {
     gIOService->SetAppOfflineInternal(aId, aOffline ?
       nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
   }
   return true;
 }
 
 bool
-TabChild::RecvSwappedWithOtherRemoteLoader()
+TabChild::RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext)
 {
   nsCOMPtr<nsIDocShell> ourDocShell = do_GetInterface(WebNavigation());
   if (NS_WARN_IF(!ourDocShell)) {
     return true;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> ourWindow = ourDocShell->GetWindow();
   if (NS_WARN_IF(!ourWindow)) {
@@ -2302,16 +2302,39 @@ TabChild::RecvSwappedWithOtherRemoteLoad
   RefPtr<nsDocShell> docShell = static_cast<nsDocShell*>(ourDocShell.get());
 
   nsCOMPtr<EventTarget> ourEventTarget = ourWindow->GetParentTarget();
 
   docShell->SetInFrameSwap(true);
 
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, false);
   nsContentUtils::FirePageHideEvent(ourDocShell, ourEventTarget);
+
+  // Owner content type may have changed, so store the possibly updated context
+  // and notify others.
+  MaybeInvalidTabContext maybeContext(aContext);
+  if (!maybeContext.IsValid()) {
+    NS_ERROR(nsPrintfCString("Received an invalid TabContext from "
+                             "the parent process. (%s)",
+                             maybeContext.GetInvalidReason()).get());
+    MOZ_CRASH("Invalid TabContext received from the parent process.");
+  }
+
+  if (!UpdateTabContextAfterSwap(maybeContext.GetTabContext())) {
+    MOZ_CRASH("Update to TabContext after swap was denied.");
+  }
+  NotifyTabContextUpdated();
+
+  // Ignore previous value of mTriedBrowserInit since owner content has changed.
+  mTriedBrowserInit = true;
+  // Initialize the child side of the browser element machinery, if appropriate.
+  if (IsMozBrowserOrApp()) {
+    RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT, true);
+  }
+
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, true);
 
   docShell->SetInFrameSwap(false);
 
   return true;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -412,17 +412,18 @@ public:
   virtual bool RecvAsyncMessage(const nsString& aMessage,
                                 InfallibleTArray<CpowEntry>&& aCpows,
                                 const IPC::Principal& aPrincipal,
                                 const ClonedMessageData& aData) override;
 
   virtual bool RecvAppOfflineStatus(const uint32_t& aId,
                                     const bool& aOffline) override;
 
-  virtual bool RecvSwappedWithOtherRemoteLoader() override;
+  virtual bool
+  RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext) override;
 
   virtual PDocAccessibleChild*
   AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&) override;
 
   virtual bool DeallocPDocAccessibleChild(PDocAccessibleChild*) override;
 
   virtual PDocumentRendererChild*
   AllocPDocumentRendererChild(const nsRect& aDocumentRect,
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -154,16 +154,35 @@ TabContext::SetTabContext(const TabConte
   NS_ENSURE_FALSE(mInitialized, false);
 
   *this = aContext;
   mInitialized = true;
 
   return true;
 }
 
+bool
+TabContext::UpdateTabContextAfterSwap(const TabContext& aContext)
+{
+  // This is only used after already initialized.
+  MOZ_ASSERT(mInitialized);
+
+  // The only permissable change is to `mIsMozBrowserElement`.  All other fields
+  // must match for the change to be accepted.
+  if (aContext.OwnAppId() != OwnAppId() ||
+      aContext.mContainingAppId != mContainingAppId ||
+      aContext.mOriginAttributes != mOriginAttributes ||
+      aContext.mSignedPkgOriginNoSuffix != mSignedPkgOriginNoSuffix) {
+    return false;
+  }
+
+  mIsMozBrowserElement = aContext.mIsMozBrowserElement;
+  return true;
+}
+
 const DocShellOriginAttributes&
 TabContext::OriginAttributesRef() const
 {
   return mOriginAttributes;
 }
 
 const nsACString&
 TabContext::SignedPkgOriginNoSuffix() const
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -155,16 +155,27 @@ protected:
    *  - a non-browser, non-app frame. Both own app and owner app should be null.
    */
   bool SetTabContext(bool aIsMozBrowserElement,
                      mozIApplication* aOwnApp,
                      mozIApplication* aAppFrameOwnerApp,
                      const DocShellOriginAttributes& aOriginAttributes,
                      const nsACString& aSignedPkgOriginNoSuffix);
 
+  /**
+   * Modify this TabContext to match the given TabContext.  This is a special
+   * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have
+   * caused the owner content to change.
+   *
+   * This special case only allows the field `mIsMozBrowserElement` to be
+   * changed.  If any other fields have changed, the update is ignored and
+   * returns false.
+   */
+  bool UpdateTabContextAfterSwap(const TabContext& aContext);
+
 private:
   /**
    * Has this TabContext been initialized?  If so, mutator methods will fail.
    */
   bool mInitialized;
 
   /**
    * Whether this TabContext corresponds to a mozbrowser.
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -22,16 +22,17 @@ mozilla::LazyLogModule gMP3DemuxerLog("M
 #define MP3LOGV(msg, ...) \
   MOZ_LOG(gMP3DemuxerLog, LogLevel::Verbose, ("MP3Demuxer " msg, ##__VA_ARGS__))
 #else
 #define MP3LOG(msg, ...)
 #define MP3LOGV(msg, ...)
 #endif
 
 using mozilla::media::TimeUnit;
+using mozilla::media::TimeInterval;
 using mozilla::media::TimeIntervals;
 using mp4_demuxer::ByteReader;
 
 namespace mozilla {
 namespace mp3 {
 
 // MP3Demuxer
 
@@ -320,23 +321,43 @@ MP3TrackDemuxer::SkipToNextRandomAccessP
 
 int64_t
 MP3TrackDemuxer::GetResourceOffset() const {
   return mOffset;
 }
 
 TimeIntervals
 MP3TrackDemuxer::GetBuffered() {
-  TimeUnit duration = Duration();
+  AutoPinned<MediaResource> stream(mSource.GetResource());
+  TimeIntervals buffered;
+
+  if (Duration() > TimeUnit() && stream->IsDataCachedToEndOfResource(0)) {
+    // Special case completely cached files. This also handles local files.
+    buffered += TimeInterval(TimeUnit(), Duration());
+    MP3LOGV("buffered = [[%" PRId64 ", %" PRId64 "]]",
+            TimeUnit().ToMicroseconds(), Duration().ToMicroseconds());
+    return buffered;
+  }
 
-  if (duration <= TimeUnit()) {
-    return TimeIntervals();
+  MediaByteRangeSet ranges;
+  nsresult rv = stream->GetCachedRanges(ranges);
+  NS_ENSURE_SUCCESS(rv, buffered);
+
+  for (const auto& range: ranges) {
+    if (range.IsEmpty()) {
+      continue;
+    }
+    TimeUnit start = Duration(FrameIndexFromOffset(range.mStart));
+    TimeUnit end = Duration(FrameIndexFromOffset(range.mEnd));
+    MP3LOGV("buffered += [%" PRId64 ", %" PRId64 "]",
+            start.ToMicroseconds(), end.ToMicroseconds());
+    buffered += TimeInterval(start, end);
   }
-  AutoPinned<MediaResource> stream(mSource.GetResource());
-  return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds());
+
+  return buffered;
 }
 
 int64_t
 MP3TrackDemuxer::StreamLength() const {
   return mSource.GetLength();
 }
 
 TimeUnit
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -689,16 +689,17 @@ skip-if = (toolkit == 'gonk') # B2G emul
 tags=msg
 [test_mediarecorder_creation.html]
 tags=msg capturestream
 [test_mediarecorder_creation_fail.html]
 tags=msg
 [test_mediarecorder_getencodeddata.html]
 tags=msg
 [test_mediarecorder_principals.html]
+skip-if = (os == 'linux' && bits == 64) # See bug 1266345
 tags=msg
 [test_mediarecorder_record_4ch_audiocontext.html]
 tags=msg
 [test_mediarecorder_record_audiocontext.html]
 tags=msg
 [test_mediarecorder_record_audiocontext_mlk.html]
 tags=msg
 [test_mediarecorder_record_audionode.html]
--- a/dom/push/test/test_unregister.html
+++ b/dom/push/test/test_unregister.html
@@ -5,85 +5,75 @@ Bug 1170817: Push tests.
 
 Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/licenses/publicdomain/
 
 -->
 <head>
   <title>Test for Bug 1170817</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
   <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
 </head>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1170817">Mozilla Bug 1170817</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 
 <script class="testbody" type="text/javascript">
 
-  var registration;
-
-  function start() {
-    return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
-    .then(swr => { registration = swr; return swr; });
-  }
-
-  function unregisterSW() {
-    return registration.unregister().then(function(result) {
-      ok(result, "Unregister should return true.");
-    }, function(e) {
-      dump("Unregistering the SW failed with " + e + "\n");
-    });
-  }
-
-  function setupPushNotification(swr) {
-    return swr.pushManager.subscribe().then(
-      pushSubscription => {
-        ok(true, "successful registered for push notification");
-        return pushSubscription;
-      }, error => {
-        ok(false, "could not register for push notification");
-      });
+  function generateURL() {
+    return "worker.js" + "?" + (Math.random());
   }
 
-  function unregisterPushNotification(pushSubscription) {
-    return pushSubscription.unsubscribe().then(
-      result => {
-      ok(result, "unsubscribe() on existing subscription should return true.");
-      return pushSubscription;
-    }, error => {
-      ok(false, "unsubscribe() should never fail.");
-    });
-  }
+  var registration;
+  add_task(function* start() {
+    yield setupPrefsAndMockSocket(new MockWebSocket());
+    yield setPushPermission(true);
+
+    registration = yield navigator.serviceWorker.register(
+      generateURL(), {scope: "."});
+  });
+
+  var pushSubscription;
+  add_task(function* setupPushNotification() {
+    pushSubscription = yield registration.pushManager.subscribe();
+    ok(pushSubscription, "successful registered for push notification");
+  });
+
+  add_task(function* unregisterPushNotification() {
+    var result = yield pushSubscription.unsubscribe();
+    ok(result, "unsubscribe() on existing subscription should return true.");
+  });
 
-  function unregisterAgain(pushSubscription) {
-    return pushSubscription.unsubscribe().then(
-      result => {
-      ok(!result, "unsubscribe() on previously unsubscribed subscription should return false.");
-      return pushSubscription;
-    }, error => {
-      ok(false, "unsubscribe() should never fail.");
-    });
-  }
+  add_task(function* unregisterAgain() {
+    var result = yield pushSubscription.unsubscribe();
+    ok(!result, "unsubscribe() on previously unsubscribed subscription should return false.");
+  });
+
+  add_task(function* subscribeAgain() {
+    pushSubscription = yield registration.pushManager.subscribe();
+    ok(pushSubscription, "Should create a new push subscription");
+
+    var result = yield registration.unregister();
+    ok(result, "Should unregister the service worker");
 
-  function runTest() {
-    start()
-    .then(setupPushNotification)
-    .then(unregisterPushNotification)
-    .then(unregisterAgain)
-    .then(unregisterSW)
-    .catch(function(e) {
-      ok(false, "Some test failed with error " + e);
-    }).then(SimpleTest.finish);
-  }
+    registration = yield navigator.serviceWorker.register(
+      generateURL(), {scope: "."});
+    var pushSubscription = yield registration.pushManager.getSubscription();
+    ok(!pushSubscription,
+      "Unregistering a service worker should drop its subscription");
+  });
 
-  setupPrefsAndMockSocket(new MockWebSocket()).then(_ => runTest());
-  SpecialPowers.addPermission("desktop-notification", true, document);
-  SimpleTest.waitForExplicitFinish();
+  add_task(function* unregister() {
+    var result = yield registration.unregister();
+    ok(result, "Unregister should return true.");
+  });
+
 </script>
 </body>
 </html>
 
--- a/dom/security/SRICheck.cpp
+++ b/dom/security/SRICheck.cpp
@@ -4,37 +4,33 @@
  * 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/. */
 
 #include "SRICheck.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/dom/SRILogHelper.h"
 #include "nsContentUtils.h"
 #include "nsIChannel.h"
 #include "nsIDocument.h"
 #include "nsIProtocolHandler.h"
 #include "nsIScriptError.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIIncrementalStreamLoader.h"
 #include "nsIUnicharStreamLoader.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsWhitespaceTokenizer.h"
 
-static mozilla::LogModule*
-GetSriLog()
-{
-  static mozilla::LazyLogModule gSriPRLog("SRI");
-  return gSriPRLog;
-}
-
-#define SRILOG(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Debug, args)
-#define SRIERROR(args) MOZ_LOG(GetSriLog(), mozilla::LogLevel::Error, args)
+#define SRILOG(args)                                                           \
+  MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug, args)
+#define SRIERROR(args)                                                         \
+  MOZ_LOG(SRILogHelper::GetSriLog(), mozilla::LogLevel::Error, args)
 
 namespace mozilla {
 namespace dom {
 
 /**
  * Returns whether or not the sub-resource about to be loaded is eligible
  * for integrity checks. If it's not, the checks will be skipped and the
  * sub-resource will be loaded.
@@ -61,17 +57,17 @@ IsEligible(nsIChannel* aChannel, const C
   NS_ENSURE_SUCCESS(rv, rv);
   nsCOMPtr<nsIURI> originalURI;
   rv = aChannel->GetOriginalURI(getter_AddRefs(originalURI));
   NS_ENSURE_SUCCESS(rv, rv);
   nsAutoCString requestSpec;
   rv = originalURI->GetSpec(requestSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString documentSpec, finalSpec;
     aDocument->GetDocumentURI()->GetAsciiSpec(documentSpec);
     if (finalURI) {
       finalURI->GetSpec(finalSpec);
     }
     SRILOG(("SRICheck::IsEligible, documentURI=%s; requestURI=%s; finalURI=%s",
             documentSpec.get(), requestSpec.get(), finalSpec.get()));
   }
@@ -145,32 +141,32 @@ SRICheck::IntegrityMetadata(const nsAStr
                                       NS_LITERAL_CSTRING("Sub-resource Integrity"),
                                       aDocument,
                                       nsContentUtils::eSECURITY_PROPERTIES,
                                       "UnsupportedHashAlg",
                                       params, ArrayLength(params));
     }
 
     nsAutoCString alg1, alg2;
-    if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+    if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
       outMetadata->GetAlgorithm(&alg1);
       metadata.GetAlgorithm(&alg2);
     }
     if (*outMetadata == metadata) {
       SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is the same as '%s'",
               alg1.get(), alg2.get()));
       *outMetadata += metadata; // add new hash to strongest metadata
     } else if (*outMetadata < metadata) {
       SRILOG(("SRICheck::IntegrityMetadata, alg '%s' is weaker than '%s'",
               alg1.get(), alg2.get()));
       *outMetadata = metadata; // replace strongest metadata with current
     }
   }
 
-  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     if (outMetadata->IsValid()) {
       nsAutoCString alg;
       outMetadata->GetAlgorithm(&alg);
       SRILOG(("SRICheck::IntegrityMetadata, using a '%s' hash", alg.get()));
     } else if (outMetadata->IsEmpty()) {
       SRILOG(("SRICheck::IntegrityMetadata, no metadata"));
     } else {
       SRILOG(("SRICheck::IntegrityMetadata, no valid metadata found"));
@@ -187,17 +183,17 @@ SRICheck::VerifyIntegrity(const SRIMetad
                           const nsIDocument* aDocument)
 {
   NS_ENSURE_ARG_POINTER(aLoader);
 
   NS_ConvertUTF16toUTF8 utf8Hash(aString);
   nsCOMPtr<nsIChannel> channel;
   aLoader->GetChannel(getter_AddRefs(channel));
 
-  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
     nsCOMPtr<nsIURI> originalURI;
     if (channel &&
         NS_SUCCEEDED(channel->GetOriginalURI(getter_AddRefs(originalURI))) &&
         originalURI) {
       originalURI->GetAsciiSpec(requestURL);
     }
     SRILOG(("SRICheck::VerifyIntegrity (unichar stream)"));
@@ -340,17 +336,17 @@ SRICheckDataVerifier::VerifyHash(const S
 nsresult
 SRICheckDataVerifier::Verify(const SRIMetadata& aMetadata,
                              nsIChannel* aChannel,
                              const CORSMode aCORSMode,
                              const nsIDocument* aDocument)
 {
   NS_ENSURE_ARG_POINTER(aDocument);
 
-  if (MOZ_LOG_TEST(GetSriLog(), mozilla::LogLevel::Debug)) {
+  if (MOZ_LOG_TEST(SRILogHelper::GetSriLog(), mozilla::LogLevel::Debug)) {
     nsAutoCString requestURL;
     nsCOMPtr<nsIRequest> request;
     request = do_QueryInterface(aChannel);
     request->GetName(requestURL);
     SRILOG(("SRICheckDataVerifier::Verify, url=%s (length=%lu)",
             requestURL.get(), mBytesHashed));
   }
 
new file mode 100644
--- /dev/null
+++ b/dom/security/SRILogHelper.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef mozilla_dom_SRILogHelper_h
+#define mozilla_dom_SRILogHelper_h
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+namespace dom {
+
+class SRILogHelper final
+{
+public:
+  static LogModule* GetSriLog()
+  {
+    static LazyLogModule gSriPRLog("SRI");
+    return gSriPRLog;
+  }
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_SRILogHelper_h
--- a/dom/security/moz.build
+++ b/dom/security/moz.build
@@ -9,16 +9,17 @@ TEST_DIRS += ['test']
 EXPORTS.mozilla.dom += [
     'ContentVerifier.h',
     'nsContentSecurityManager.h',
     'nsCSPContext.h',
     'nsCSPService.h',
     'nsCSPUtils.h',
     'nsMixedContentBlocker.h',
     'SRICheck.h',
+    'SRILogHelper.h',
     'SRIMetadata.h',
 ]
 
 EXPORTS += [
     'nsContentSecurityManager.h',
 ]
 
 UNIFIED_SOURCES += [
--- a/dom/svg/nsSVGViewBox.cpp
+++ b/dom/svg/nsSVGViewBox.cpp
@@ -65,16 +65,33 @@ nsSVGAttrTearoffTable<nsSVGViewBox, dom:
 
 void
 nsSVGViewBox::Init()
 {
   mHasBaseVal = false;
   mAnimVal = nullptr;
 }
 
+bool
+nsSVGViewBox::HasRect() const
+{
+  // Check mAnimVal if we have one; otherwise, check mBaseVal if we have one;
+  // otherwise, just return false (we clearly do not have a rect).
+  const nsSVGViewBoxRect* rect = mAnimVal;
+  if (!rect) {
+    if (!mHasBaseVal) {
+      // no anim val, no base val --> no viewbox rect
+      return false;
+    }
+    rect = &mBaseVal;
+  }
+
+  return !rect->none && rect->width >= 0 && rect->height >= 0;
+}
+
 void
 nsSVGViewBox::SetAnimValue(const nsSVGViewBoxRect& aRect,
                            nsSVGElement *aSVGElement)
 {
   if (!mAnimVal) {
     // it's okay if allocation fails - and no point in reporting that
     mAnimVal = new nsSVGViewBoxRect(aRect);
   } else {
--- a/dom/svg/nsSVGViewBox.h
+++ b/dom/svg/nsSVGViewBox.h
@@ -47,21 +47,17 @@ public:
 
   /**
    * Returns true if the corresponding "viewBox" attribute defined a rectangle
    * with finite values and nonnegative width/height.
    * Returns false if the viewBox was set to an invalid
    * string, or if any of the four rect values were too big to store in a
    * float, or the width/height are negative.
    */
-  bool HasRect() const
-    {
-      const nsSVGViewBoxRect& rect = GetAnimValue();
-      return !rect.none && rect.width >= 0 && rect.height >= 0;
-    }
+  bool HasRect() const;
 
   /**
    * Returns true if the corresponding "viewBox" attribute either defined a
    * rectangle with finite values or the special "none" value.
    */
   bool IsExplicitlySet() const
     {
       if (mAnimVal || mHasBaseVal) {
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -118,17 +118,17 @@ var legacyMozPrefixedInterfaces =
 // IMPORTANT: Do not change the list below without review from a DOM peer!
 var interfaceNamesInGlobalScope =
   [
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AlarmsManager", b2g: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnalyserNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    {name: "Animation", release: false},
+    {name: "Animation"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectReadOnly", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectTiming", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "AnimationEffectTimingReadOnly", release: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "AnimationEvent",
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -64,17 +64,17 @@ interface CanvasRenderingContext2D {
   // colors and styles (see also the CanvasDrawingStyles interface)
            attribute (DOMString or CanvasGradient or CanvasPattern) strokeStyle; // (default black)
            attribute (DOMString or CanvasGradient or CanvasPattern) fillStyle; // (default black)
   [NewObject]
   CanvasGradient createLinearGradient(double x0, double y0, double x1, double y1);
   [NewObject, Throws]
   CanvasGradient createRadialGradient(double x0, double y0, double r0, double x1, double y1, double r1);
   [NewObject, Throws]
-  CanvasPattern createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
+  CanvasPattern? createPattern(CanvasImageSource image, [TreatNullAs=EmptyString] DOMString repetition);
 
   // shadows
            [LenientFloat]
            attribute double shadowOffsetX; // (default 0)
            [LenientFloat]
            attribute double shadowOffsetY; // (default 0)
            [LenientFloat]
            attribute double shadowBlur; // (default 0)
--- a/dom/webidl/Location.webidl
+++ b/dom/webidl/Location.webidl
@@ -6,17 +6,17 @@
  * The origin of this IDL file is
  * http://www.whatwg.org/specs/web-apps/current-work/#the-location-interface
  *
  * © Copyright 2004-2011 Apple Computer, Inc., Mozilla Foundation, and
  * Opera Software ASA. You are granted a license to use, reproduce
  * and create derivative works of this document.
  */
 
-[Unforgeable]
+[Unforgeable, NonOrdinaryGetPrototypeOf]
 interface Location {
   // Bug 824857: no support for stringifier attributes yet.
   //  stringifier attribute USVString href;
 
   // Bug 824857 should remove this.
   [Throws]
   stringifier;
 
--- a/dom/workers/ServiceWorkerUnregisterJob.cpp
+++ b/dom/workers/ServiceWorkerUnregisterJob.cpp
@@ -1,20 +1,54 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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/. */
 
 #include "ServiceWorkerUnregisterJob.h"
 
+#include "nsIPushService.h"
+
 namespace mozilla {
 namespace dom {
 namespace workers {
 
+class ServiceWorkerUnregisterJob::PushUnsubscribeCallback final :
+        public nsIUnsubscribeResultCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  explicit PushUnsubscribeCallback(ServiceWorkerUnregisterJob* aJob)
+    : mJob(aJob)
+  {
+    AssertIsOnMainThread();
+  }
+
+  NS_IMETHODIMP
+  OnUnsubscribe(nsresult aStatus, bool) override
+  {
+    // Warn if unsubscribing fails, but don't prevent the worker from
+    // unregistering.
+    Unused << NS_WARN_IF(NS_FAILED(aStatus));
+    mJob->Unregister();
+    return NS_OK;
+  }
+
+private:
+  ~PushUnsubscribeCallback()
+  {
+  }
+
+  RefPtr<ServiceWorkerUnregisterJob> mJob;
+};
+
+NS_IMPL_ISUPPORTS(ServiceWorkerUnregisterJob::PushUnsubscribeCallback,
+                  nsIUnsubscribeResultCallback)
 
 ServiceWorkerUnregisterJob::ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
                                                        const nsACString& aScope,
                                                        bool aSendToParent)
   : ServiceWorkerJob(Type::Unregister, aPrincipal, aScope, EmptyCString())
   , mResult(false)
   , mSendToParent(aSendToParent)
 {
@@ -36,16 +70,45 @@ ServiceWorkerUnregisterJob::AsyncExecute
 {
   AssertIsOnMainThread();
 
   if (Canceled()) {
     Finish(NS_ERROR_DOM_ABORT_ERR);
     return;
   }
 
+  // Push API, section 5: "When a service worker registration is unregistered,
+  // any associated push subscription must be deactivated." To ensure the
+  // service worker registration isn't cleared as we're unregistering, we
+  // unsubscribe first.
+  nsCOMPtr<nsIPushService> pushService =
+    do_GetService("@mozilla.org/push/Service;1");
+  if (NS_WARN_IF(!pushService)) {
+    Unregister();
+    return;
+  }
+  nsCOMPtr<nsIUnsubscribeResultCallback> unsubscribeCallback =
+    new PushUnsubscribeCallback(this);
+  nsresult rv = pushService->Unsubscribe(NS_ConvertUTF8toUTF16(mScope),
+                                         mPrincipal, unsubscribeCallback);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    Unregister();
+  }
+}
+
+void
+ServiceWorkerUnregisterJob::Unregister()
+{
+  AssertIsOnMainThread();
+
+  if (Canceled()) {
+    Finish(NS_ERROR_DOM_ABORT_ERR);
+    return;
+  }
+
   // Step 1 of the Unregister algorithm requires checking that the
   // client origin matches the scope's origin.  We perform this in
   // registration->update() method directly since we don't have that
   // client information available here.
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
 
   // "Let registration be the result of running [[Get Registration]]
--- a/dom/workers/ServiceWorkerUnregisterJob.h
+++ b/dom/workers/ServiceWorkerUnregisterJob.h
@@ -19,21 +19,26 @@ public:
   ServiceWorkerUnregisterJob(nsIPrincipal* aPrincipal,
                              const nsACString& aScope,
                              bool aSendToParent);
 
   bool
   GetResult() const;
 
 private:
+  class PushUnsubscribeCallback;
+
   virtual ~ServiceWorkerUnregisterJob();
 
   virtual void
   AsyncExecute() override;
 
+  void
+  Unregister();
+
   bool mResult;
   bool mSendToParent;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
--- a/embedding/components/windowwatcher/nsWindowWatcher.cpp
+++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp
@@ -513,18 +513,20 @@ nsWindowWatcher::OpenWindowInternal(mozI
   nsAutoCString features;     // string version of aFeatures
   nsCOMPtr<nsIURI> uriToLoad; // from aUrl, if any
   nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner; // from the parent window, if any
   nsCOMPtr<nsIDocShellTreeItem> newDocShellItem; // from the new window
 
   nsCOMPtr<nsPIDOMWindowOuter> parent =
     aParent ? nsPIDOMWindowOuter::From(aParent) : nullptr;
 
-  MOZ_ASSERT_IF(openedFromRemoteTab,
-                XRE_IsParentProcess());
+  // When the opener is a remote tab, the url passed from the child process
+  // isn't actually used. This code needs some serious refactoring.
+  MOZ_ASSERT_IF(openedFromRemoteTab, !aUrl);
+  MOZ_ASSERT_IF(openedFromRemoteTab, XRE_IsParentProcess());
   NS_ENSURE_ARG_POINTER(aResult);
   *aResult = 0;
 
   if (!nsContentUtils::IsSafeToRunScript()) {
     nsContentUtils::WarnScriptWasIgnored(nullptr);
     return NS_ERROR_FAILURE;
   }
 
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -407,29 +407,28 @@ Compositor::SetInvalid()
 bool
 Compositor::IsValid() const
 {
   return !mParent;
 }
 
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 void
-Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
+Compositor::SetDispAcquireFence(Layer* aLayer)
 {
   // OpenGL does not provide ReleaseFence for rendering.
   // Instead use DispAcquireFence as layer buffer's ReleaseFence
   // to prevent flickering and tearing.
   // DispAcquireFence is DisplaySurface's AcquireFence.
   // AcquireFence will be signaled when a buffer's content is available.
   // See Bug 974152.
-
-  if (!aLayer || !aWidget) {
+  if (!aLayer || !mWidget) {
     return;
   }
-  nsWindow* window = static_cast<nsWindow*>(aWidget);
+  nsWindow* window = static_cast<nsWindow*>(mWidget->RealWidget());
   RefPtr<FenceHandle::FdObj> fence = new FenceHandle::FdObj(
       window->GetScreen()->GetPrevDispAcquireFd());
   mReleaseFenceHandle.Merge(FenceHandle(fence));
 }
 
 FenceHandle
 Compositor::GetReleaseFence()
 {
@@ -438,17 +437,17 @@ Compositor::GetReleaseFence()
   }
 
   RefPtr<FenceHandle::FdObj> fdObj = mReleaseFenceHandle.GetDupFdObj();
   return FenceHandle(fdObj);
 }
 
 #else
 void
-Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
+Compositor::SetDispAcquireFence(Layer* aLayer)
 {
 }
 
 FenceHandle
 Compositor::GetReleaseFence()
 {
   return FenceHandle();
 }
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -384,17 +384,17 @@ public:
                           gfx::Rect* aClipRectOut = nullptr,
                           gfx::Rect* aRenderBoundsOut = nullptr) = 0;
 
   /**
    * Flush the current frame to the screen and tidy up.
    */
   virtual void EndFrame() = 0;
 
-  virtual void SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget);
+  virtual void SetDispAcquireFence(Layer* aLayer);
 
   virtual FenceHandle GetReleaseFence();
 
   /**
    * Post-rendering stuff if the rendering is done outside of this Compositor
    * e.g., by Composer2D.
    * aTransform is the transform from user space to window space.
    */
@@ -528,16 +528,19 @@ public:
   TimeStamp GetCompositeUntilTime() const {
     return mCompositeUntilTime;
   }
 
   // A stale Compositor has no CompositorBridgeParent; it will not process
   // frames and should not be used.
   void SetInvalid();
   bool IsValid() const;
+  CompositorBridgeParent* GetCompositorBridgeParent() const {
+    return mParent;
+  }
 
 protected:
   void DrawDiagnosticsInternal(DiagnosticFlags aFlags,
                                const gfx::Rect& aVisibleRect,
                                const gfx::Rect& aClipRect,
                                const gfx::Matrix4x4& transform,
                                uint32_t aFlashCounter);
 
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -776,16 +776,26 @@ APZCTreeManager::ReceiveInputEvent(Input
       FlushRepaintsToClearScreenToGeckoTransform();
 
       PanGestureInput& panInput = aEvent.AsPanGestureInput();
       panInput.mHandledByAPZ = WillHandleInput(panInput);
       if (!panInput.mHandledByAPZ) {
         return result;
       }
 
+      // If/when we enable support for pan inputs off-main-thread, we'll need
+      // to duplicate this EventStateManager code or something. See the other
+      // call to GetUserPrefsForWheelEvent in this file for why these fields
+      // are stored separately.
+      MOZ_ASSERT(NS_IsMainThread());
+      WidgetWheelEvent wheelEvent = panInput.ToWidgetWheelEvent(nullptr);
+      EventStateManager::GetUserPrefsForWheelEvent(&wheelEvent,
+        &panInput.mUserDeltaMultiplierX,
+        &panInput.mUserDeltaMultiplierY);
+
       RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(panInput.mPanStartPoint,
                                                             &hitResult);
       if (apzc) {
         MOZ_ASSERT(hitResult == HitLayer || hitResult == HitDispatchToContentRegion);
 
         // For pan gesture events, the call to ReceiveInputEvent below may result in
         // scrolling, which changes the async transform. However, the event we
         // want to pass to gecko should be the pre-scroll event coordinates,
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -1745,17 +1745,17 @@ AsyncPanZoomController::GetScrollWheelDe
 bool
 AsyncPanZoomController::CanScroll(const InputData& aEvent) const
 {
   ParentLayerPoint delta;
   if (aEvent.mInputType == SCROLLWHEEL_INPUT) {
     delta = GetScrollWheelDelta(aEvent.AsScrollWheelInput());
   } else if (aEvent.mInputType == PANGESTURE_INPUT) {
     const PanGestureInput& panInput = aEvent.AsPanGestureInput();
-    delta = ToParentLayerCoordinates(panInput.mPanDisplacement, panInput.mPanStartPoint);
+    delta = ToParentLayerCoordinates(panInput.UserMultipliedPanDisplacement(), panInput.mPanStartPoint);
   }
   if (!delta.x && !delta.y) {
     return false;
   }
 
   return CanScrollWithWheel(delta);
 }
 
@@ -2014,43 +2014,51 @@ nsEventStatus AsyncPanZoomController::On
     if (!aFingersOnTouchpad) {
       return nsEventStatus_eConsumeNoDefault;
     }
     // Resume / restart the pan.
     // PanBegin will call back into this function with mState == PANNING.
     return OnPanBegin(aEvent);
   }
 
+  // Note that there is a multiplier that applies onto the "physical" pan
+  // displacement (how much the user's fingers moved) that produces the "logical"
+  // pan displacement (how much the page should move). For some of the code
+  // below it makes more sense to use the physical displacement rather than
+  // the logical displacement, and vice-versa.
+  ScreenPoint physicalPanDisplacement = aEvent.mPanDisplacement;
+  ParentLayerPoint logicalPanDisplacement = aEvent.UserMultipliedLocalPanDisplacement();
+
   // We need to update the axis velocity in order to get a useful display port
   // size and position. We need to do so even if this is a momentum pan (i.e.
   // aFingersOnTouchpad == false); in that case the "with touch" part is not
   // really appropriate, so we may want to rethink this at some point.
-  mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, aEvent.mLocalPanDisplacement.x, aEvent.mTime);
-  mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, aEvent.mLocalPanDisplacement.y, aEvent.mTime);
-
-  HandlePanningUpdate(aEvent.mPanDisplacement);
+  mX.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.x, logicalPanDisplacement.x, aEvent.mTime);
+  mY.UpdateWithTouchAtDevicePoint(aEvent.mLocalPanStartPoint.y, logicalPanDisplacement.y, aEvent.mTime);
+
+  HandlePanningUpdate(physicalPanDisplacement);
 
   mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
       (uint32_t) ScrollInputMethod::ApzPanGesture);
 
-  ScreenPoint panDistance(fabs(aEvent.mPanDisplacement.x), fabs(aEvent.mPanDisplacement.y));
+  ScreenPoint panDistance(fabs(physicalPanDisplacement.x), fabs(physicalPanDisplacement.y));
   OverscrollHandoffState handoffState(
       *CurrentPanGestureBlock()->GetOverscrollHandoffChain(),
       panDistance,
       ScrollSource::Wheel);
 
   // Create fake "touch" positions that will result in the desired scroll motion.
   // Note that the pan displacement describes the change in scroll position:
   // positive displacement values mean that the scroll position increases.
   // However, an increase in scroll position means that the scrolled contents
   // are moved to the left / upwards. Since our simulated "touches" determine
   // the motion of the scrolled contents, not of the scroll position, they need
   // to move in the opposite direction of the pan displacement.
   ParentLayerPoint startPoint = aEvent.mLocalPanStartPoint;
-  ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - aEvent.mLocalPanDisplacement;
+  ParentLayerPoint endPoint = aEvent.mLocalPanStartPoint - logicalPanDisplacement;
   CallDispatchScroll(startPoint, endPoint, handoffState);
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnPanEnd(const PanGestureInput& aEvent) {
   APZC_LOG("%p got a pan-end in state %d\n", this, mState);
 
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -63,16 +63,17 @@
 #include "ScopedGLHelpers.h"
 #endif
 #ifdef MOZ_WIDGET_GONK
 #include "nsScreenManagerGonk.h"
 #include "nsWindow.h"
 #endif
 #include "GeckoProfiler.h"
 #include "TextRenderer.h"               // for TextRenderer
+#include "mozilla/layers/CompositorBridgeParent.h"
 
 class gfxContext;
 
 namespace mozilla {
 namespace layers {
 
 class ImageLayer;
 
@@ -939,17 +940,17 @@ LayerManagerComposite::Render(const nsIn
 
   {
     PROFILER_LABEL("LayerManagerComposite", "EndFrame",
       js::ProfileEntry::Category::GRAPHICS);
 
     mCompositor->EndFrame();
 
     // Call after EndFrame()
-    mCompositor->SetDispAcquireFence(mRoot, mCompositor->GetWidget()->RealWidget());
+    mCompositor->SetDispAcquireFence(mRoot);
   }
 
   if (composer2D) {
     composer2D->Render(mCompositor->GetWidget()->RealWidget());
   }
 
   mCompositor->GetWidget()->PostRender(this);
 
@@ -1542,17 +1543,20 @@ LayerComposite::SetLayerManager(LayerMan
 {
   mCompositeManager = aManager;
   mCompositor = aManager->GetCompositor();
 }
 
 bool
 LayerManagerComposite::AsyncPanZoomEnabled() const
 {
-  return mCompositor->GetWidget()->RealWidget()->AsyncPanZoomEnabled();
+  if (CompositorBridgeParent* bridge = mCompositor->GetCompositorBridgeParent()) {
+    return bridge->AsyncPanZoomEnabled();
+  }
+  return false;
 }
 
 nsIntRegion
 LayerComposite::GetFullyRenderedRegion() {
   if (TiledContentHost* tiled = GetCompositableHost() ? GetCompositableHost()->AsTiledContentHost()
                                                         : nullptr) {
     nsIntRegion shadowVisibleRegion = GetShadowVisibleRegion().ToUnknownRegion();
     // Discard the region which hasn't been drawn yet when doing
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -300,38 +300,38 @@ CompositorVsyncScheduler::Observer::Noti
 
 void
 CompositorVsyncScheduler::Observer::Destroy()
 {
   MutexAutoLock lock(mMutex);
   mOwner = nullptr;
 }
 
-CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorBridgeParent* aCompositorBridgeParent, nsIWidget* aWidget)
+CompositorVsyncScheduler::CompositorVsyncScheduler(CompositorBridgeParent* aCompositorBridgeParent,
+                                                   widget::CompositorWidgetProxy* aWidgetProxy)
   : mCompositorBridgeParent(aCompositorBridgeParent)
   , mLastCompose(TimeStamp::Now())
   , mIsObservingVsync(false)
   , mNeedsComposite(0)
   , mVsyncNotificationsSkipped(0)
+  , mCompositorVsyncDispatcher(aWidgetProxy->GetCompositorVsyncDispatcher())
   , mCurrentCompositeTaskMonitor("CurrentCompositeTaskMonitor")
   , mCurrentCompositeTask(nullptr)
   , mSetNeedsCompositeMonitor("SetNeedsCompositeMonitor")
   , mSetNeedsCompositeTask(nullptr)
 #ifdef MOZ_WIDGET_GONK
 #if ANDROID_VERSION >= 19
   , mDisplayEnabled(false)
   , mSetDisplayMonitor("SetDisplayMonitor")
   , mSetDisplayTask(nullptr)
 #endif
 #endif
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aWidget != nullptr);
   mVsyncObserver = new Observer(this);
-  mCompositorVsyncDispatcher = aWidget->GetCompositorVsyncDispatcher();
 #ifdef MOZ_WIDGET_GONK
   GeckoTouchDispatcher::GetInstance()->SetCompositorVsyncScheduler(this);
 
 #if ANDROID_VERSION >= 19
   RefPtr<nsScreenManagerGonk> screenManager = nsScreenManagerGonk::GetInstance();
   screenManager->SetCompositorVsyncScheduler(this);
 #endif
 #endif
@@ -343,17 +343,16 @@ CompositorVsyncScheduler::CompositorVsyn
 }
 
 CompositorVsyncScheduler::~CompositorVsyncScheduler()
 {
   MOZ_ASSERT(!mIsObservingVsync);
   MOZ_ASSERT(!mVsyncObserver);
   // The CompositorVsyncDispatcher is cleaned up before this in the nsBaseWidget, which stops vsync listeners
   mCompositorBridgeParent = nullptr;
-  mCompositorVsyncDispatcher = nullptr;
 }
 
 #ifdef MOZ_WIDGET_GONK
 #if ANDROID_VERSION >= 19
 void
 CompositorVsyncScheduler::SetDisplay(bool aDisplayEnable)
 {
   // SetDisplay() is usually called from nsScreenManager at main thread. Post
@@ -728,17 +727,17 @@ CompositorBridgeParent::CompositorBridge
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
     sIndirectLayerTrees[mRootLayerTreeID].mParent = this;
   }
 
   if (aUseAPZ) {
     mApzcTreeManager = new APZCTreeManager();
   }
 
-  mCompositorScheduler = new CompositorVsyncScheduler(this, aWidget->RealWidget());
+  mCompositorScheduler = new CompositorVsyncScheduler(this, aWidget);
   LayerScope::SetPixelScale(aScale.scale);
 
   // mSelfRef is cleared in DeferredDestroy.
   mSelfRef = this;
 }
 
 bool
 CompositorBridgeParent::IsInCompositorThread()
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -105,17 +105,18 @@ private:
  * compositor when it need to paint.
  * Turns vsync notifications into scheduled composites.
  **/
 class CompositorVsyncScheduler
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorVsyncScheduler)
 
 public:
-  explicit CompositorVsyncScheduler(CompositorBridgeParent* aCompositorBridgeParent, nsIWidget* aWidget);
+  explicit CompositorVsyncScheduler(CompositorBridgeParent* aCompositorBridgeParent,
+                                    widget::CompositorWidgetProxy* aWidgetProxy);
 
 #ifdef MOZ_WIDGET_GONK
   // emulator-ics never trigger the display on/off, so compositor will always
   // skip composition request at that device. Only check the display status
   // with kk device and upon.
 #if ANDROID_VERSION >= 19
   // SetDisplay() and CancelSetDisplayTask() are used for the display on/off.
   // It will clear all composition related task and flag, and skip another
@@ -511,16 +512,19 @@ public:
    * aContentParent The ContentParent for the process that the TabChild for
    *                aTabId lives in.
    * aBrowserParent The toplevel TabParent for aTabId.
    */
   static bool UpdateRemoteContentController(uint64_t aLayersId,
                                             dom::ContentParent* aContentParent,
                                             const dom::TabId& aTabId,
                                             dom::TabParent* aBrowserParent);
+  bool AsyncPanZoomEnabled() const {
+    return !!mApzcTreeManager;
+  }
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~CompositorBridgeParent();
 
   void DeferredDestroy();
 
   virtual PLayerTransactionParent*
--- a/gfx/src/gfxTelemetry.h
+++ b/gfx/src/gfxTelemetry.h
@@ -12,18 +12,18 @@ namespace gfx {
 // Describes the status of a graphics feature, in terms of whether or not we've
 // attempted to initialize the feature, and if so, whether or not it succeeded
 // (and if not, why).
 enum class FeatureStatus
 {
   // This feature has not been requested.
   Unused,
 
-  // This feature is unavailable due to Safe Mode or not being included with
-  // the operating system.
+  // This feature is unavailable due to Safe Mode, not being included with
+  // the operating system, or a dependent feature being disabled.
   Unavailable,
 
   // This feature crashed immediately when we tried to initialize it, but we
   // were able to recover via SEH (or something similar).
   CrashedInHandler,
 
   // This feature was blocked for reasons outside the blacklist, such as a
   // runtime test failing.
--- a/gfx/thebes/gfxFontEntry.cpp
+++ b/gfx/thebes/gfxFontEntry.cpp
@@ -1093,16 +1093,54 @@ gfxFontEntry::AddSizeOfExcludingThis(Mal
     if (mCharacterMap && mCharacterMap->mBuildOnTheFly) {
         aSizes->mCharMapsSize +=
             mCharacterMap->SizeOfIncludingThis(aMallocSizeOf);
     }
     if (mFontTableCache) {
         aSizes->mFontTableCacheSize +=
             mFontTableCache->SizeOfIncludingThis(aMallocSizeOf);
     }
+
+    // If the font has UVS data, we count that as part of the character map.
+    if (mUVSData) {
+        aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get());
+    }
+
+    // The following, if present, are essentially cached forms of font table
+    // data, so we'll accumulate them together with the basic table cache.
+    if (mUserFontData) {
+        aSizes->mFontTableCacheSize +=
+            mUserFontData->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    if (mSVGGlyphs) {
+        aSizes->mFontTableCacheSize +=
+            mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    if (mMathTable) {
+        aSizes->mFontTableCacheSize +=
+            mMathTable->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    if (mSupportedFeatures) {
+        aSizes->mFontTableCacheSize +=
+            mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf);
+    }
+    if (mFeatureInputs) {
+        aSizes->mFontTableCacheSize +=
+            mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf);
+        for (auto iter = mFeatureInputs->ConstIter(); !iter.Done();
+             iter.Next()) {
+            // There's no API to get the real size of an hb_set, so we'll use
+            // an approximation based on knowledge of the implementation.
+            aSizes->mFontTableCacheSize += 8192; // vector of 64K bits
+        }
+    }
+    // We don't include the size of mCOLR/mCPAL here, because (depending on the
+    // font backend implementation) they will either wrap blocks of data owned
+    // by the system (and potentially shared), or tables that are in our font
+    // table cache and therefore already counted.
 }
 
 void
 gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                      FontListSizes* aSizes) const
 {
     aSizes->mFontListSize += aMallocSizeOf(this);
     AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
--- a/gfx/thebes/gfxMathTable.cpp
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -469,8 +469,18 @@ gfxMathTable::SelectGlyphConstruction(ui
   // Make mGlyphConstruction point to the desired glyph construction.
   start = reinterpret_cast<const char*>(mathvariants);
   if (!ValidStructure(start + offset, sizeof(MathGlyphConstruction))) {
     return;
   }
   mGlyphConstruction =
     reinterpret_cast<const MathGlyphConstruction*>(start + offset);
 }
+
+size_t
+gfxMathTable::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // We don't include the size of mMathTable here, because (depending on the
+  // font backend implementation) it will either wrap a block of data owned
+  // by the system (and potentially shared), or a table that's in our font
+  // table cache and therefore already counted.
+  return aMallocSizeOf(this);
+}
--- a/gfx/thebes/gfxMathTable.h
+++ b/gfx/thebes/gfxMathTable.h
@@ -71,16 +71,18 @@ public:
      * whether the operation was successful. The function returns false if
      * there is not any assembly for the character we want to stretch or if
      * the format is not supported by the nsMathMLChar code.
      *
      */
     bool GetMathVariantsParts(uint32_t aGlyphID, bool aVertical,
                               uint32_t aGlyphs[4]);
 
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 protected:
     friend class gfxFontEntry;
     // This allows gfxFontEntry to verify the validity of the main headers
     // before starting to use the MATH table.
     bool HasValidHeaders();
 
 private:
     // HarfBuzz blob where the MATH table is stored.
--- a/gfx/thebes/gfxSVGGlyphs.cpp
+++ b/gfx/thebes/gfxSVGGlyphs.cpp
@@ -251,16 +251,32 @@ gfxSVGGlyphs::GetGlyphElement(uint32_t a
 }
 
 bool
 gfxSVGGlyphs::HasSVGGlyph(uint32_t aGlyphId)
 {
     return !!GetGlyphElement(aGlyphId);
 }
 
+size_t
+gfxSVGGlyphs::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+    // We don't include the size of mSVGData here, because (depending on the
+    // font backend implementation) it will either wrap a block of data owned
+    // by the system (and potentially shared), or a table that's in our font
+    // table cache and therefore already counted.
+    size_t result = aMallocSizeOf(this)
+                    + mGlyphDocs.ShallowSizeOfExcludingThis(aMallocSizeOf)
+                    + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf);
+    for (auto iter = mGlyphDocs.ConstIter(); !iter.Done(); iter.Next()) {
+        result += iter.Data()->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    return result;
+}
+
 Element *
 gfxSVGGlyphsDocument::GetGlyphElement(uint32_t aGlyphId)
 {
     return mGlyphIdMap.Get(aGlyphId);
 }
 
 gfxSVGGlyphsDocument::gfxSVGGlyphsDocument(const uint8_t *aBuffer,
                                            uint32_t aBufLen,
@@ -432,16 +448,25 @@ gfxSVGGlyphsDocument::InsertGlyphId(Elem
         return;
       }
       id = id * 10 + (ch - '0');
     }
 
     mGlyphIdMap.Put(id, aGlyphElement);
 }
 
+size_t
+gfxSVGGlyphsDocument::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    return aMallocSizeOf(this)
+           + mGlyphIdMap.ShallowSizeOfExcludingThis(aMallocSizeOf)
+           + mSVGGlyphsDocumentURI.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+
+}
+
 void
 gfxTextContextPaint::InitStrokeGeometry(gfxContext *aContext,
                                         float devUnitsPerSVGUnit)
 {
     mStrokeWidth = aContext->CurrentLineWidth() / devUnitsPerSVGUnit;
     aContext->CurrentDash(mDashes, &mDashOffset);
     for (uint32_t i = 0; i < mDashes.Length(); i++) {
         mDashes[i] /= devUnitsPerSVGUnit;
--- a/gfx/thebes/gfxSVGGlyphs.h
+++ b/gfx/thebes/gfxSVGGlyphs.h
@@ -44,16 +44,18 @@ public:
                          gfxSVGGlyphs *aSVGGlyphs);
 
     Element *GetGlyphElement(uint32_t aGlyphId);
 
     ~gfxSVGGlyphsDocument();
 
     virtual void DidRefresh() override;
 
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     nsresult ParseDocument(const uint8_t *aBuffer, uint32_t aBufLen);
 
     nsresult SetupPresentation();
 
     void FindGlyphElements(Element *aElement);
 
     void InsertGlyphId(Element *aGlyphElement);
@@ -123,16 +125,18 @@ public:
     /**
      * Get the extents for the SVG glyph associated with |aGlyphId|
      * @param aSVGToAppSpace The matrix mapping the SVG glyph space to the
      *   target context space
      */
     bool GetGlyphExtents(uint32_t aGlyphId, const gfxMatrix& aSVGToAppSpace,
                          gfxRect *aResult);
 
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     Element *GetGlyphElement(uint32_t aGlyphId);
 
     nsClassHashtable<nsUint32HashKey, gfxSVGGlyphsDocument> mGlyphDocs;
     nsBaseHashtable<nsUint32HashKey, Element*, Element*> mGlyphIdMap;
 
     hb_blob_t *mSVGData;
     gfxFontEntry *mFontEntry;
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -47,21 +47,21 @@ public:
         : mLength(initial), mLimit(limit), mOff(0) {
         mPtr = moz_xmalloc(mLength);
     }
 
     ~ExpandingMemoryStream() {
         free(mPtr);
     }
 
-    // return the buffer, and give up ownership of it
-    // so the caller becomes responsible to call free
-    // when finished with it
+    // Return the buffer, resized to fit its contents (as it may have been
+    // over-allocated during growth), and give up ownership of it so the
+    // caller becomes responsible to call free() when finished with it.
     void* forget() {
-        void* p = mPtr;
+        void* p = moz_xrealloc(mPtr, mOff);
         mPtr = nullptr;
         return p;
     }
 
     bool WriteRaw(const void* data, size_t length) {
         if ((mOff + length > mLength) ||
             (mLength > std::numeric_limits<size_t>::max() - mOff)) {
             if (mLength == mLimit) {
@@ -246,24 +246,24 @@ gfxUserFontEntry::SanitizeOpenTypeData(c
     } else if (aFontType == GFX_USERFONT_WOFF2) {
         lengthHint *= 3;
     }
 
     // limit output/expansion to 256MB
     ExpandingMemoryStream output(lengthHint, 1024 * 1024 * 256);
 
     gfxOTSContext otsContext(this);
-
-    if (otsContext.Process(&output, aData, aLength)) {
-        aSaneLength = output.Tell();
-        return static_cast<uint8_t*>(output.forget());
-    } else {
+    if (!otsContext.Process(&output, aData, aLength)) {
+        // Failed to decode/sanitize the font, so discard it.
         aSaneLength = 0;
         return nullptr;
     }
+
+    aSaneLength = output.Tell();
+    return static_cast<const uint8_t*>(output.forget());
 }
 
 void
 gfxUserFontEntry::StoreUserFontData(gfxFontEntry* aFontEntry,
                                     bool aPrivate,
                                     const nsAString& aOriginalName,
                                     FallibleTArray<uint8_t>* aMetadata,
                                     uint32_t aMetaOrigLen,
@@ -292,16 +292,26 @@ gfxUserFontEntry::StoreUserFontData(gfxF
     userFontData->mRealName = aOriginalName;
     if (aMetadata) {
         userFontData->mMetadata.SwapElements(*aMetadata);
         userFontData->mMetaOrigLen = aMetaOrigLen;
         userFontData->mCompression = aCompression;
     }
 }
 
+size_t
+gfxUserFontData::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+    return aMallocSizeOf(this)
+           + mMetadata.ShallowSizeOfExcludingThis(aMallocSizeOf)
+           + mLocalName.SizeOfExcludingThisIfUnshared(aMallocSizeOf)
+           + mRealName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    // Not counting mURI and mPrincipal, as those will be shared.
+}
+
 void
 gfxUserFontEntry::GetFamilyNameAndURIForLogging(nsACString& aFamilyName,
                                                 nsACString& aURI)
 {
   aFamilyName.Assign(NS_ConvertUTF16toUTF8(mFamilyName));
 
   aURI.Truncate();
   if (mSrcIndex == mSrcList.Length()) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -96,16 +96,18 @@ class gfxUserFontData {
 public:
     gfxUserFontData()
         : mSrcIndex(0), mFormat(0), mMetaOrigLen(0),
           mCRC32(0), mLength(0), mCompression(kUnknownCompression),
           mPrivate(false), mIsBuffer(false)
     { }
     virtual ~gfxUserFontData() { }
 
+    size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
     nsTArray<uint8_t> mMetadata;  // woff metadata block (compressed), if any
     nsCOMPtr<nsIURI>  mURI;       // URI of the source, if it was url()
     nsCOMPtr<nsIPrincipal> mPrincipal; // principal for the download, if url()
     nsString          mLocalName; // font name used for the source, if local()
     nsString          mRealName;  // original fullname from the font resource
     uint32_t          mSrcIndex;  // index in the rule's source list
     uint32_t          mFormat;    // format hint for the source used, if any
     uint32_t          mMetaOrigLen; // length needed to decompress metadata
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -1952,16 +1952,21 @@ gfxWindowsPlatform::InitializeConfig()
 
 void
 gfxWindowsPlatform::InitializeD3D11Config()
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
   FeatureState& d3d11 = gfxConfig::GetFeature(Feature::D3D11_COMPOSITING);
 
+  if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
+    d3d11.DisableByDefault(FeatureStatus::Unavailable, "Hardware compositing is disabled");
+    return;
+  }
+
   d3d11.EnableByDefault();
 
   // If the user prefers D3D9, act as though they disabled D3D11.
   if (gfxPrefs::LayersPreferD3D9()) {
     d3d11.UserDisable("Disabled due to user preference for Direct3D 9");
     return;
   }
 
@@ -2292,20 +2297,17 @@ gfxWindowsPlatform::AttemptD3D11ImageBri
 
 void
 gfxWindowsPlatform::InitializeDevices()
 {
   // Ask the parent process for an updated list of which devices to create.
   UpdateDeviceInitData();
 
   // If acceleration is disabled, we refuse to initialize anything.
-  FeatureStatus compositing = gfxConfig::GetValue(Feature::HW_COMPOSITING);
-  if (IsFeatureStatusFailure(compositing)) {
-    gfxConfig::DisableByDefault(Feature::D3D11_COMPOSITING, compositing, "Hardware compositing is disabled");
-    gfxConfig::DisableByDefault(Feature::DIRECT2D, compositing, "Hardware compositing is disabled");
+  if (!gfxConfig::IsEnabled(Feature::HW_COMPOSITING)) {
     return;
   }
 
   MOZ_ASSERT(!InSafeMode());
 
   // If we previously crashed initializing devices, bail out now.
   D3D11LayersCrashGuard detectCrashes;
   if (detectCrashes.Crashed()) {
--- a/image/test/browser/browser_docshell_type_editor.js
+++ b/image/test/browser/browser_docshell_type_editor.js
@@ -16,31 +16,36 @@ add_task(function* () {
     gBrowser,
     url: SIMPLE_HTML
   }, function* (browser) {
     yield ContentTask.spawn(browser, null, function* () {
       let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
                                  .rootTreeItem
                                  .QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDocShell);
+      let defaultAppType = rootDocShell.appType;
 
       rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_EDITOR;
 
       is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_EDITOR,
         "sanity check: appType after update should be type editor");
 
       return new Promise(resolve => {
         let doc = content.document;
         let image = doc.createElement("img");
         image.onload = function() {
           ok(true, "APP_TYPE_EDITOR is allowed to load privileged image");
+          // restore appType of rootDocShell before moving on to the next test
+          rootDocShell.appType = defaultAppType;
           resolve();
         }
         image.onerror = function() {
           ok(false, "APP_TYPE_EDITOR is allowed to load privileged image");
+          // restore appType of rootDocShell before moving on to the next test
+          rootDocShell.appType = defaultAppType;
           resolve();
         }
         doc.body.appendChild(image);
         image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
       });
     });
   });
 });
@@ -52,31 +57,36 @@ add_task(function* () {
     gBrowser,
     url: SIMPLE_HTML
   }, function* (browser) {
     yield ContentTask.spawn(browser, null, function* () {
       let rootDocShell = docShell.QueryInterface(Ci.nsIDocShellTreeItem)
                                  .rootTreeItem
                                  .QueryInterface(Ci.nsIInterfaceRequestor)
                                  .getInterface(Ci.nsIDocShell);
+      let defaultAppType = rootDocShell.appType;
 
       rootDocShell.appType = Ci.nsIDocShell.APP_TYPE_UNKNOWN;
 
       is(rootDocShell.appType, Ci.nsIDocShell.APP_TYPE_UNKNOWN,
         "sanity check: appType of docshell should be unknown");
 
       return new Promise(resolve => {
         let doc = content.document;
         let image = doc.createElement("img");
         image.onload = function() {
           ok(false, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image");
+          // restore appType of rootDocShell before moving on to the next test
+          rootDocShell.appType = defaultAppType;
           resolve();
         }
         image.onerror = function() {
           ok(true, "APP_TYPE_UNKNOWN is *not* allowed to acces privileged image");
+          // restore appType of rootDocShell before moving on to the next test
+          rootDocShell.appType = defaultAppType;
           resolve();
         }
         doc.body.appendChild(image);
         image.src = "chrome://devtools/content/framework/dev-edition-promo/dev-edition-logo.png";
       });
     });
   });
 });
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -421,20 +421,24 @@ struct ParamTraits<nsLiteralString> : Pa
   typedef nsLiteralString paramType;
 };
 
 // Pickle::ReadBytes and ::WriteBytes take the length in ints, so we must
 // ensure there is no overflow. This returns |false| if it would overflow.
 // Otherwise, it returns |true| and places the byte length in |aByteLength|.
 bool ByteLengthIsValid(uint32_t aNumElements, size_t aElementSize, int* aByteLength);
 
+// Note: IPDL will sometimes codegen specialized implementations of
+// nsTArray serialization and deserialization code in
+// implementSpecialArrayPickling(). This is needed when ParamTraits<E>
+// is not defined.
 template <typename E>
-struct ParamTraits<FallibleTArray<E> >
+struct ParamTraits<nsTArray<E>>
 {
-  typedef FallibleTArray<E> paramType;
+  typedef nsTArray<E> paramType;
 
   // We write arrays of integer or floating-point data using a single pickling
   // call, rather than writing each element individually.  We deliberately do
   // not use mozilla::IsPod here because it is perfectly reasonable to have
   // a data structure T for which IsPod<T>::value is true, yet also have a
   // ParamTraits<T> specialization.
   static const bool sUseWriteBytes = (mozilla::IsIntegral<E>::value ||
                                       mozilla::IsFloatingPoint<E>::value);
@@ -450,16 +454,18 @@ struct ParamTraits<FallibleTArray<E> >
       aMsg->WriteBytes(aParam.Elements(), pickledLength);
     } else {
       for (uint32_t index = 0; index < length; index++) {
         WriteParam(aMsg, aParam[index]);
       }
     }
   }
 
+  // This method uses infallible allocation so that an OOM failure will
+  // show up as an OOM crash rather than an IPC FatalError.
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
     uint32_t length;
     if (!ReadParam(aMsg, aIter, &length)) {
       return false;
     }
 
     if (sUseWriteBytes) {
@@ -468,30 +474,24 @@ struct ParamTraits<FallibleTArray<E> >
         return false;
       }
 
       const char* outdata;
       if (!aMsg->ReadBytes(aIter, &outdata, pickledLength)) {
         return false;
       }
 
-      E* elements = aResult->AppendElements(length, mozilla::fallible);
-      if (!elements) {
-        return false;
-      }
+      E* elements = aResult->AppendElements(length);
 
       memcpy(elements, outdata, pickledLength);
     } else {
-      if (!aResult->SetCapacity(length, mozilla::fallible)) {
-        return false;
-      }
+      aResult->SetCapacity(length);
 
       for (uint32_t index = 0; index < length; index++) {
-        E* element = aResult->AppendElement(mozilla::fallible);
-        MOZ_ASSERT(element);
+        E* element = aResult->AppendElement();
         if (!ReadParam(aMsg, aIter, element)) {
           return false;
         }
       }
     }
 
     return true;
   }
@@ -503,39 +503,39 @@ struct ParamTraits<FallibleTArray<E> >
         aLog->append(L" ");
       }
       LogParam(aParam[index], aLog);
     }
   }
 };
 
 template<typename E>
-struct ParamTraits<InfallibleTArray<E> >
+struct ParamTraits<FallibleTArray<E>>
 {
-  typedef InfallibleTArray<E> paramType;
+  typedef FallibleTArray<E> paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
-    WriteParam(aMsg, static_cast<const FallibleTArray<E>&>(aParam));
+    WriteParam(aMsg, static_cast<const nsTArray<E>&>(aParam));
   }
 
-  // deserialize the array fallibly, but return an InfallibleTArray
+  // Deserialize the array infallibly, but return a FallibleTArray.
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult)
   {
-    FallibleTArray<E> temp;
+    nsTArray<E> temp;
     if (!ReadParam(aMsg, aIter, &temp))
       return false;
 
     aResult->SwapElements(temp);
     return true;
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
-    LogParam(static_cast<const FallibleTArray<E>&>(aParam), aLog);
+    LogParam(static_cast<const nsTArray<E>&>(aParam), aLog);
   }
 };
 
 template<typename E, size_t N>
 struct ParamTraits<AutoTArray<E, N>> : ParamTraits<nsTArray<E>>
 {
   typedef AutoTArray<E, N> paramType;
 };
@@ -773,17 +773,17 @@ struct ParamTraits<mozilla::SerializedSt
 template <>
 struct ParamTraits<nsIWidget::TouchPointerState>
   : public BitFlagsEnumSerializer<nsIWidget::TouchPointerState,
                                   nsIWidget::TouchPointerState::ALL_BITS>
 {
 };
 
 template<class T>
-struct ParamTraits< mozilla::Maybe<T> >
+struct ParamTraits<mozilla::Maybe<T>>
 {
   typedef mozilla::Maybe<T> paramType;
 
   static void Write(Message* msg, const paramType& param)
   {
     if (param.isSome()) {
       WriteParam(msg, true);
       WriteParam(msg, param.value());
--- a/ipc/ipdl/ipdl/lower.py
+++ b/ipc/ipdl/ipdl/lower.py
@@ -322,28 +322,22 @@ def _refptrTake(expr):
 
 def _cxxArrayType(basetype, const=0, ref=0):
     return Type('nsTArray', T=basetype, const=const, ref=ref, hasimplicitcopyctor=False)
 
 def _cxxManagedContainerType(basetype, const=0, ref=0):
     return Type('ManagedContainer', T=basetype,
                 const=const, ref=ref, hasimplicitcopyctor=False)
 
-def _cxxFallibleArrayType(basetype, const=0, ref=0):
-    return Type('FallibleTArray', T=basetype, const=const, ref=ref)
-
 def _callCxxArrayLength(arr):
     return ExprCall(ExprSelect(arr, '.', 'Length'))
 
-def _callCxxCheckedArraySetLength(arr, lenexpr, sel='.'):
-    ifbad = StmtIf(ExprNot(ExprCall(ExprSelect(arr, sel, 'SetLength'),
-                                    args=[ lenexpr, ExprVar('mozilla::fallible') ])))
-    ifbad.addifstmt(_fatalError('Error setting the array length'))
-    ifbad.addifstmt(StmtReturn.FALSE)
-    return ifbad
+def _callCxxArraySetLength(arr, lenexpr, sel='.'):
+    return ExprCall(ExprSelect(arr, sel, 'SetLength'),
+                    args=[ lenexpr ])
 
 def _callCxxSwapArrayElements(arr1, arr2, sel='.'):
     return ExprCall(ExprSelect(arr1, sel, 'SwapElements'),
                     args=[ arr2 ])
 
 def _callInsertManagedActor(managees, actor):
     return ExprCall(ExprSelect(managees, '.', 'PutEntry'),
                     args=[ actor ])
@@ -4444,24 +4438,24 @@ class _GenerateProtocolActorCode(ipdl.as
                                         ExprLiteral.ZERO),
                           cond=ExprBinary(ivar, '<', lenvar),
                           update=ExprPrefixUnop(ivar, '++'))
         forread.addstmt(
             self.checkedRead(eltipdltype, ExprAddrOf(ExprIndex(favar, ivar)),
                              msgvar, itervar, errfnRead,
                              eltipdltype.name() + '[i]'))
         read.addstmts([
-            StmtDecl(Decl(_cxxFallibleArrayType(_cxxBareType(arraytype.basetype, self.side)), favar.name)),
+            StmtDecl(Decl(_cxxArrayType(_cxxBareType(arraytype.basetype, self.side)), favar.name)),
             StmtDecl(Decl(Type.UINT32, lenvar.name)),
             self.checkedRead(None, ExprAddrOf(lenvar),
                              msgvar, itervar, errfnRead,
                              'length\' (' + Type.UINT32.name + ') of \'' +
                              arraytype.name()),
             Whitespace.NL,
-            _callCxxCheckedArraySetLength(favar, lenvar),
+            StmtExpr(_callCxxArraySetLength(favar, lenvar)),
             forread,
             StmtExpr(_callCxxSwapArrayElements(var, favar, '->')),
             StmtReturn.TRUE
         ])
 
         self.cls.addstmts([ write, Whitespace.NL, read, Whitespace.NL ])
 
 
--- a/js/public/HeapAPI.h
+++ b/js/public/HeapAPI.h
@@ -109,18 +109,16 @@ namespace shadow {
 
 struct Zone
 {
   protected:
     JSRuntime* const runtime_;
     JSTracer* const barrierTracer_;     // A pointer to the JSRuntime's |gcMarker|.
 
   public:
-    js::RootLists roots;
-
     bool needsIncrementalBarrier_;
 
     Zone(JSRuntime* runtime, JSTracer* barrierTracerArg)
       : runtime_(runtime),
         barrierTracer_(barrierTracerArg),
         needsIncrementalBarrier_(false)
     {}
 
--- a/js/public/Proxy.h
+++ b/js/public/Proxy.h
@@ -96,20 +96,18 @@ class JS_FRIEND_API(Wrapper);
  * object. Not every proxy has the notion of a target, however.
  *
  * To minimize code duplication, a set of abstract proxy handler classes is
  * provided, from which other handlers may inherit. These abstract classes are
  * organized in the following hierarchy:
  *
  *     BaseProxyHandler
  *     |
- *     DirectProxyHandler        // has a target
- *     |
- *     Wrapper                   // can be unwrapped, revealing target
- *     |                         // (see js::CheckedUnwrap)
+ *     Wrapper                   // has a target, can be unwrapped to reveal
+ *     |                         // target (see js::CheckedUnwrap)
  *     |
  *     CrossCompartmentWrapper   // target is in another compartment;
  *                               // implements membrane between compartments
  *
  * Example: Some DOM objects (including all the arraylike DOM objects) are
  * implemented as proxies. Since these objects don't need to forward operations
  * to any underlying JS object, DOMJSProxyHandler directly subclasses
  * BaseProxyHandler.
@@ -267,17 +265,17 @@ class JS_FRIEND_API(BaseProxyHandler)
      * getPrototype() crashes if called. setPrototype() throws a TypeError.
      */
     virtual bool getPrototype(JSContext* cx, HandleObject proxy, MutableHandleObject protop) const;
     virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
                               ObjectOpResult& result) const;
 
     /* Non-standard but conceptual kin to {g,s}etPrototype, so these live here. */
     virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
-                                        MutableHandleObject protop) const;
+                                        MutableHandleObject protop) const = 0;
     virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy, bool* succeeded) const;
 
     virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
                                    ObjectOpResult& result) const = 0;
     virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const = 0;
 
     /*
      * These standard internal methods are implemented, as a convenience, so
@@ -344,91 +342,16 @@ class JS_FRIEND_API(BaseProxyHandler)
     virtual bool getElements(JSContext* cx, HandleObject proxy, uint32_t begin, uint32_t end,
                              ElementAdder* adder) const;
 
     /* See comment for weakmapKeyDelegateOp in js/Class.h. */
     virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const;
     virtual bool isScripted() const { return false; }
 };
 
-/*
- * DirectProxyHandler includes a notion of a target object. All methods are
- * reimplemented such that they forward their behavior to the target. This
- * allows consumers of this class to forward to another object as transparently
- * and efficiently as possible.
- *
- * Important: If you add a method implementation here, you probably also need
- * to add an override in CrossCompartmentWrapper. If you don't, you risk
- * compartment mismatches. See bug 945826 comment 0.
- */
-class JS_FRIEND_API(DirectProxyHandler) : public BaseProxyHandler
-{
-  public:
-    explicit MOZ_CONSTEXPR DirectProxyHandler(const void* aFamily, bool aHasPrototype = false,
-                                              bool aHasSecurityPolicy = false)
-      : BaseProxyHandler(aFamily, aHasPrototype, aHasSecurityPolicy)
-    { }
-
-    /* Standard internal methods. */
-    virtual bool getOwnPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
-                                          MutableHandle<PropertyDescriptor> desc) const override;
-    virtual bool defineProperty(JSContext* cx, HandleObject proxy, HandleId id,
-                                Handle<PropertyDescriptor> desc,
-                                ObjectOpResult& result) const override;
-    virtual bool ownPropertyKeys(JSContext* cx, HandleObject proxy,
-                                 AutoIdVector& props) const override;
-    virtual bool delete_(JSContext* cx, HandleObject proxy, HandleId id,
-                         ObjectOpResult& result) const override;
-    virtual bool enumerate(JSContext* cx, HandleObject proxy,
-                           MutableHandleObject objp) const override;
-    virtual bool getPrototype(JSContext* cx, HandleObject proxy,
-                              MutableHandleObject protop) const override;
-    virtual bool setPrototype(JSContext* cx, HandleObject proxy, HandleObject proto,
-                              ObjectOpResult& result) const override;
-    virtual bool getPrototypeIfOrdinary(JSContext* cx, HandleObject proxy, bool* isOrdinary,
-                                        MutableHandleObject protop) const override;
-    virtual bool setImmutablePrototype(JSContext* cx, HandleObject proxy,
-                                       bool* succeeded) const override;
-    virtual bool preventExtensions(JSContext* cx, HandleObject proxy,
-                                   ObjectOpResult& result) const override;
-    virtual bool isExtensible(JSContext* cx, HandleObject proxy, bool* extensible) const override;
-    virtual bool has(JSContext* cx, HandleObject proxy, HandleId id,
-                     bool* bp) const override;
-    virtual bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
-                     HandleId id, MutableHandleValue vp) const override;
-    virtual bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
-                     HandleValue receiver, ObjectOpResult& result) const override;
-    virtual bool call(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
-    virtual bool construct(JSContext* cx, HandleObject proxy, const CallArgs& args) const override;
-
-    /* SpiderMonkey extensions. */
-    virtual bool getPropertyDescriptor(JSContext* cx, HandleObject proxy, HandleId id,
-                                       MutableHandle<PropertyDescriptor> desc) const override;
-    virtual bool hasOwn(JSContext* cx, HandleObject proxy, HandleId id,
-                        bool* bp) const override;
-    virtual bool getOwnEnumerablePropertyKeys(JSContext* cx, HandleObject proxy,
-                                              AutoIdVector& props) const override;
-    virtual bool nativeCall(JSContext* cx, IsAcceptableThis test, NativeImpl impl,
-                            const CallArgs& args) const override;
-    virtual bool hasInstance(JSContext* cx, HandleObject proxy, MutableHandleValue v,
-                             bool* bp) const override;
-    virtual bool getBuiltinClass(JSContext* cx, HandleObject proxy,
-                                 ESClassValue* classValue) const override;
-    virtual bool isArray(JSContext* cx, HandleObject proxy,
-                         JS::IsArrayAnswer* answer) const override;
-    virtual const char* className(JSContext* cx, HandleObject proxy) const override;
-    virtual JSString* fun_toString(JSContext* cx, HandleObject proxy,
-                                   unsigned indent) const override;
-    virtual bool regexp_toShared(JSContext* cx, HandleObject proxy,
-                                 RegExpGuard* g) const override;
-    virtual bool boxedValue_unbox(JSContext* cx, HandleObject proxy, MutableHandleValue vp) const override;
-    virtual bool isCallable(JSObject* obj) const override;
-    virtual JSObject* weakmapKeyDelegate(JSObject* proxy) const override;
-};
-
 extern JS_FRIEND_DATA(const js::Class* const) ProxyClassPtr;
 
 inline bool IsProxy(const JSObject* obj)
 {
     return GetObjectClass(obj)->isProxy();
 }
 
 namespace detail {
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -636,24 +636,18 @@ class MOZ_RAII Rooted : public js::Roote
 {
     /* Note: CX is a subclass of either ContextFriendFields or PerThreadDataFriendFields. */
     void registerWithRootLists(js::RootLists& roots) {
         this->stack = &roots.stackRoots_[JS::MapTypeToRootKind<T>::kind];
         this->prev = *stack;
         *stack = reinterpret_cast<Rooted<void*>*>(this);
     }
 
-    js::RootLists& rootLists(js::ContextFriendFields* cx) {
-        return rootLists(reinterpret_cast<JSContext*>(cx));
-    }
-    js::RootLists& rootLists(JSContext* cx) {
-        if (JS::Zone* zone = js::GetContextZone(cx))
-            return JS::shadow::Zone::asShadowZone(zone)->roots;
-        return rootLists(js::GetRuntime(cx));
-    }
+    js::RootLists& rootLists(js::ContextFriendFields* cx) { return cx->roots; }
+    js::RootLists& rootLists(JSContext* cx) { return js::ContextFriendFields::get(cx)->roots; }
     js::RootLists& rootLists(js::PerThreadDataFriendFields* pt) { return pt->roots; }
     js::RootLists& rootLists(JSRuntime* rt) {
         return js::PerThreadDataFriendFields::getMainThread(rt)->roots;
     }
 
   public:
     template <typename RootingContext>
     explicit Rooted(const RootingContext& cx)
--- a/js/src/asmjs/Wasm.cpp
+++ b/js/src/asmjs/Wasm.cpp
@@ -144,95 +144,96 @@ CheckValType(JSContext* cx, Decoder& d, 
 }
 
 static bool
 DecodeCallArgs(FunctionDecoder& f, uint32_t arity, const Sig& sig)
 {
     if (arity != sig.args().length())
         return f.iter().fail("call arity out of range");
 
-    Nothing arg;
     const ValTypeVector& args = sig.args();
     uint32_t numArgs = args.length();
     for (size_t i = 0; i < numArgs; ++i) {
         ValType argType = args[i];
-        if (!f.iter().readCallArg(argType, numArgs, i, &arg))
+        if (!f.iter().readCallArg(argType, numArgs, i, nullptr))
             return false;
     }
 
     return f.iter().readCallArgsEnd(numArgs);
 }
 
 static bool
 DecodeCallReturn(FunctionDecoder& f, const Sig& sig)
 {
     return f.iter().readCallReturn(sig.ret());
 }
 
 static bool
 DecodeCall(FunctionDecoder& f)
 {
-    if (!f.iter().readCall())
+    uint32_t calleeIndex;
+    uint32_t arity;
+    if (!f.iter().readCall(&calleeIndex, &arity))
         return false;
 
-    const CallRecord& call = f.iter().call();
-    if (call.callee >= f.mg().numFuncSigs())
+    if (calleeIndex >= f.mg().numFuncSigs())
         return f.iter().fail("callee index out of range");
 
-    const Sig& sig = f.mg().funcSig(call.callee);
-    return DecodeCallArgs(f, call.arity, sig) &&
+    const Sig& sig = f.mg().funcSig(calleeIndex);
+    return DecodeCallArgs(f, arity, sig) &&
            DecodeCallReturn(f, sig);
 }
 
 static bool
 DecodeCallIndirect(FunctionDecoder& f)
 {
-    if (!f.iter().readCallIndirect())
+    uint32_t sigIndex;
+    uint32_t arity;
+    if (!f.iter().readCallIndirect(&sigIndex, &arity))
         return false;
 
-    const CallIndirectRecord<Nothing>& callIndirect = f.iter().callIndirect();
-    if (callIndirect.sigIndex >= f.mg().numSigs())
+    if (sigIndex >= f.mg().numSigs())
         return f.iter().fail("signature index out of range");
 
-    const Sig& sig = f.mg().sig(callIndirect.sigIndex);
-    if (!DecodeCallArgs(f, callIndirect.arity, sig))
+    const Sig& sig = f.mg().sig(sigIndex);
+    if (!DecodeCallArgs(f, arity, sig))
         return false;
 
-    Nothing callee;
-    if (!f.iter().readCallIndirectCallee(&callee))
+    if (!f.iter().readCallIndirectCallee(nullptr))
         return false;
 
     return DecodeCallReturn(f, sig);
 }
 
 static bool
 DecodeCallImport(FunctionDecoder& f)
 {
-    if (!f.iter().readCallImport())
+    uint32_t importIndex;
+    uint32_t arity;
+    if (!f.iter().readCallImport(&importIndex, &arity))
         return false;
 
-    const CallImportRecord& callImport = f.iter().callImport();
-    if (callImport.callee >= f.mg().numImports())
+    if (importIndex >= f.mg().numImports())
         return f.iter().fail("import index out of range");
 
-    const Sig& sig = *f.mg().import(callImport.callee).sig;
-    return DecodeCallArgs(f, callImport.arity, sig) &&
+    const Sig& sig = *f.mg().import(importIndex).sig;
+    return DecodeCallArgs(f, arity, sig) &&
            DecodeCallReturn(f, sig);
 }
 
 static bool
 DecodeBrTable(FunctionDecoder& f)
 {
-    if (!f.iter().readBrTable())
+    uint32_t tableLength;
+    ExprType type;
+    if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr))
         return false;
 
-    ExprType type = f.iter().brTable().type;
-
     uint32_t depth;
-    for (size_t i = 0, e = f.iter().brTable().tableLength; i < e; ++i) {
+    for (size_t i = 0, e = tableLength; i < e; ++i) {
         if (!f.iter().readBrTableEntry(type, &depth))
             return false;
     }
 
     // Read the default label.
     return f.iter().readBrTableEntry(type, &depth);
 }
 
@@ -240,75 +241,73 @@ static bool
 DecodeExpr(FunctionDecoder& f)
 {
     Expr expr;
     if (!f.iter().readExpr(&expr))
         return false;
 
     switch (expr) {
       case Expr::Nop:
-        return f.iter().readTrivial();
+        return f.iter().readNullary();
       case Expr::Call:
         return DecodeCall(f);
       case Expr::CallIndirect:
         return DecodeCallIndirect(f);
       case Expr::CallImport:
         return DecodeCallImport(f);
       case Expr::I32Const:
-        return f.iter().readI32Const();
+        return f.iter().readI32Const(nullptr);
       case Expr::I64Const:
         return f.checkI64Support() &&
-               f.iter().readI64Const();
+               f.iter().readI64Const(nullptr);
       case Expr::F32Const:
-        return f.iter().readF32Const();
+        return f.iter().readF32Const(nullptr);
       case Expr::F64Const:
-        return f.iter().readF64Const();
+        return f.iter().readF64Const(nullptr);
       case Expr::GetLocal:
-        return f.iter().readGetLocal(f.locals());
+        return f.iter().readGetLocal(f.locals(), nullptr);
       case Expr::SetLocal:
-        return f.iter().readSetLocal(f.locals());
+        return f.iter().readSetLocal(f.locals(), nullptr, nullptr);
       case Expr::Select:
-        return f.iter().readSelect();
+        return f.iter().readSelect(nullptr, nullptr, nullptr, nullptr);
       case Expr::Block:
         return f.iter().readBlock();
       case Expr::Loop:
         return f.iter().readLoop();
       case Expr::If:
-        return f.iter().readIf();
+        return f.iter().readIf(nullptr);
       case Expr::Else:
-        return f.iter().readElse();
+        return f.iter().readElse(nullptr, nullptr);
       case Expr::End:
-        return f.iter().readEnd();
+        return f.iter().readEnd(nullptr, nullptr, nullptr);
       case Expr::I32Clz:
       case Expr::I32Ctz:
       case Expr::I32Popcnt:
-      case Expr::I32Eqz:
-        return f.iter().readUnary(ValType::I32);
+        return f.iter().readUnary(ValType::I32, nullptr);
       case Expr::I64Clz:
       case Expr::I64Ctz:
       case Expr::I64Popcnt:
-      case Expr::I64Eqz:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readUnary(ValType::I64);
+               f.iter().readUnary(ValType::I64, nullptr);
       case Expr::F32Abs:
       case Expr::F32Neg:
       case Expr::F32Ceil:
       case Expr::F32Floor:
       case Expr::F32Sqrt:
-        return f.iter().readUnary(ValType::F32);
+        return f.iter().readUnary(ValType::F32, nullptr);
       case Expr::F32Trunc:
         return f.iter().notYetImplemented("trunc");
       case Expr::F32Nearest:
         return f.iter().notYetImplemented("nearest");
       case Expr::F64Abs:
       case Expr::F64Neg:
       case Expr::F64Ceil:
       case Expr::F64Floor:
       case Expr::F64Sqrt:
-        return f.iter().readUnary(ValType::F64);
+        return f.iter().readUnary(ValType::F64, nullptr);
       case Expr::F64Trunc:
         return f.iter().notYetImplemented("trunc");
       case Expr::F64Nearest:
         return f.iter().notYetImplemented("nearest");
       case Expr::I32Add:
       case Expr::I32Sub:
       case Expr::I32Mul:
       case Expr::I32DivS:
@@ -318,187 +317,192 @@ DecodeExpr(FunctionDecoder& f)
       case Expr::I32And:
       case Expr::I32Or:
       case Expr::I32Xor:
       case Expr::I32Shl:
       case Expr::I32ShrS:
       case Expr::I32ShrU:
       case Expr::I32Rotl:
       case Expr::I32Rotr:
-        return f.iter().readBinary(ValType::I32);
+        return f.iter().readBinary(ValType::I32, nullptr, nullptr);
       case Expr::I64Add:
       case Expr::I64Sub:
       case Expr::I64Mul:
       case Expr::I64DivS:
       case Expr::I64DivU:
       case Expr::I64RemS:
       case Expr::I64RemU:
       case Expr::I64And:
       case Expr::I64Or:
       case Expr::I64Xor:
       case Expr::I64Shl:
       case Expr::I64ShrS:
       case Expr::I64ShrU:
       case Expr::I64Rotl:
       case Expr::I64Rotr:
         return f.checkI64Support() &&
-               f.iter().readBinary(ValType::I64);
+               f.iter().readBinary(ValType::I64, nullptr, nullptr);
       case Expr::F32Add:
       case Expr::F32Sub:
       case Expr::F32Mul:
       case Expr::F32Div:
       case Expr::F32Min:
       case Expr::F32Max:
-        return f.iter().readBinary(ValType::F32);
+        return f.iter().readBinary(ValType::F32, nullptr, nullptr);
       case Expr::F32CopySign:
         return f.iter().notYetImplemented("copysign");
       case Expr::F64Add:
       case Expr::F64Sub:
       case Expr::F64Mul:
       case Expr::F64Div:
       case Expr::F64Min:
       case Expr::F64Max:
-        return f.iter().readBinary(ValType::F64);
+        return f.iter().readBinary(ValType::F64, nullptr, nullptr);
       case Expr::F64CopySign:
         return f.iter().notYetImplemented("copysign");
       case Expr::I32Eq:
       case Expr::I32Ne:
       case Expr::I32LtS:
       case Expr::I32LtU:
       case Expr::I32LeS:
       case Expr::I32LeU:
       case Expr::I32GtS:
       case Expr::I32GtU:
       case Expr::I32GeS:
       case Expr::I32GeU:
-        return f.iter().readComparison(ValType::I32);
+        return f.iter().readComparison(ValType::I32, nullptr, nullptr);
       case Expr::I64Eq:
       case Expr::I64Ne:
       case Expr::I64LtS:
       case Expr::I64LtU:
       case Expr::I64LeS:
       case Expr::I64LeU:
       case Expr::I64GtS:
       case Expr::I64GtU:
       case Expr::I64GeS:
       case Expr::I64GeU:
         return f.checkI64Support() &&
-               f.iter().readComparison(ValType::I64);
+               f.iter().readComparison(ValType::I64, nullptr, nullptr);
       case Expr::F32Eq:
       case Expr::F32Ne:
       case Expr::F32Lt:
       case Expr::F32Le:
       case Expr::F32Gt:
       case Expr::F32Ge:
-        return f.iter().readComparison(ValType::F32);
+        return f.iter().readComparison(ValType::F32, nullptr, nullptr);
       case Expr::F64Eq:
       case Expr::F64Ne:
       case Expr::F64Lt:
       case Expr::F64Le:
       case Expr::F64Gt:
       case Expr::F64Ge:
-        return f.iter().readComparison(ValType::F64);
+        return f.iter().readComparison(ValType::F64, nullptr, nullptr);
+      case Expr::I32Eqz:
+        return f.iter().readConversion(ValType::I32, ValType::I32, nullptr);
+      case Expr::I64Eqz:
+        return f.checkI64Support() &&
+               f.iter().readConversion(ValType::I64, ValType::I32, nullptr);
       case Expr::I32WrapI64:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::I64, ValType::I32);
+               f.iter().readConversion(ValType::I64, ValType::I32, nullptr);
       case Expr::I32TruncSF32:
       case Expr::I32TruncUF32:
       case Expr::I32ReinterpretF32:
-        return f.iter().readConversion(ValType::F32, ValType::I32);
+        return f.iter().readConversion(ValType::F32, ValType::I32, nullptr);
       case Expr::I32TruncSF64:
       case Expr::I32TruncUF64:
-        return f.iter().readConversion(ValType::F64, ValType::I32);
+        return f.iter().readConversion(ValType::F64, ValType::I32, nullptr);
       case Expr::I64ExtendSI32:
       case Expr::I64ExtendUI32:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::I32, ValType::I64);
+               f.iter().readConversion(ValType::I32, ValType::I64, nullptr);
       case Expr::I64TruncSF32:
       case Expr::I64TruncUF32:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::F32, ValType::I64);
+               f.iter().readConversion(ValType::F32, ValType::I64, nullptr);
       case Expr::I64TruncSF64:
       case Expr::I64TruncUF64:
       case Expr::I64ReinterpretF64:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::F64, ValType::I64);
+               f.iter().readConversion(ValType::F64, ValType::I64, nullptr);
       case Expr::F32ConvertSI32:
       case Expr::F32ConvertUI32:
       case Expr::F32ReinterpretI32:
-        return f.iter().readConversion(ValType::I32, ValType::F32);
+        return f.iter().readConversion(ValType::I32, ValType::F32, nullptr);
       case Expr::F32ConvertSI64:
       case Expr::F32ConvertUI64:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::I64, ValType::F32);
+               f.iter().readConversion(ValType::I64, ValType::F32, nullptr);
       case Expr::F32DemoteF64:
-        return f.iter().readConversion(ValType::F64, ValType::F32);
+        return f.iter().readConversion(ValType::F64, ValType::F32, nullptr);
       case Expr::F64ConvertSI32:
       case Expr::F64ConvertUI32:
-        return f.iter().readConversion(ValType::I32, ValType::F64);
+        return f.iter().readConversion(ValType::I32, ValType::F64, nullptr);
       case Expr::F64ConvertSI64:
       case Expr::F64ConvertUI64:
       case Expr::F64ReinterpretI64:
         return f.checkI64Support() &&
-               f.iter().readConversion(ValType::I64, ValType::F64);
+               f.iter().readConversion(ValType::I64, ValType::F64, nullptr);
       case Expr::F64PromoteF32:
-        return f.iter().readConversion(ValType::F32, ValType::F64);
+        return f.iter().readConversion(ValType::F32, ValType::F64, nullptr);
       case Expr::I32Load8S:
       case Expr::I32Load8U:
-        return f.iter().readLoad(ValType::I32, 1);
+        return f.iter().readLoad(ValType::I32, 1, nullptr);
       case Expr::I32Load16S:
       case Expr::I32Load16U:
-        return f.iter().readLoad(ValType::I32, 2);
+        return f.iter().readLoad(ValType::I32, 2, nullptr);
       case Expr::I32Load:
-        return f.iter().readLoad(ValType::I32, 4);
+        return f.iter().readLoad(ValType::I32, 4, nullptr);
       case Expr::I64Load8S:
       case Expr::I64Load8U:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readLoad(ValType::I64, 1);
+               f.iter().readLoad(ValType::I64, 1, nullptr);
       case Expr::I64Load16S:
       case Expr::I64Load16U:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readLoad(ValType::I64, 2);
+               f.iter().readLoad(ValType::I64, 2, nullptr);
       case Expr::I64Load32S:
       case Expr::I64Load32U:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readLoad(ValType::I64, 4);
+               f.iter().readLoad(ValType::I64, 4, nullptr);
       case Expr::I64Load:
         return f.iter().notYetImplemented("i64");
       case Expr::F32Load:
-        return f.iter().readLoad(ValType::F32, 4);
+        return f.iter().readLoad(ValType::F32, 4, nullptr);
       case Expr::F64Load:
-        return f.iter().readLoad(ValType::F64, 8);
+        return f.iter().readLoad(ValType::F64, 8, nullptr);
       case Expr::I32Store8:
-        return f.iter().readStore(ValType::I32, 1);
+        return f.iter().readStore(ValType::I32, 1, nullptr, nullptr);
       case Expr::I32Store16:
-        return f.iter().readStore(ValType::I32, 2);
+        return f.iter().readStore(ValType::I32, 2, nullptr, nullptr);
       case Expr::I32Store:
-        return f.iter().readStore(ValType::I32, 4);
+        return f.iter().readStore(ValType::I32, 4, nullptr, nullptr);
       case Expr::I64Store8:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readStore(ValType::I64, 1);
+               f.iter().readStore(ValType::I64, 1, nullptr, nullptr);
       case Expr::I64Store16:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readStore(ValType::I64, 2);
+               f.iter().readStore(ValType::I64, 2, nullptr, nullptr);
       case Expr::I64Store32:
         return f.iter().notYetImplemented("i64") &&
-               f.iter().readStore(ValType::I64, 4);
+               f.iter().readStore(ValType::I64, 4, nullptr, nullptr);
       case Expr::I64Store:
         return f.iter().notYetImplemented("i64");
       case Expr::F32Store:
-        return f.iter().readStore(ValType::F32, 4);
+        return f.iter().readStore(ValType::F32, 4, nullptr, nullptr);
       case Expr::F64Store:
-        return f.iter().readStore(ValType::F64, 8);
+        return f.iter().readStore(ValType::F64, 8, nullptr, nullptr);
       case Expr::Br:
-        return f.iter().readBr();
+        return f.iter().readBr(nullptr, nullptr, nullptr);
       case Expr::BrIf:
-        return f.iter().readBrIf();
+        return f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr);
       case Expr::BrTable:
         return DecodeBrTable(f);
       case Expr::Return:
-        return f.iter().readReturn();
+        return f.iter().readReturn(nullptr);
       case Expr::Unreachable:
         return f.iter().readUnreachable();
       default:
         // Note: it's important not to remove this default since readExpr()
         // can return Expr values for which there is no enumerator.
         break;
     }
 
@@ -946,17 +950,17 @@ DecodeFunctionBody(JSContext* cx, Decode
     if (!f.iter().readFunctionStart())
         return false;
 
     while (d.currentPosition() < bodyEnd) {
         if (!DecodeExpr(f))
             return false;
     }
 
-    if (!f.iter().readFunctionEnd(f.sig().ret()))
+    if (!f.iter().readFunctionEnd(f.sig().ret(), nullptr))
         return false;
 
     if (d.currentPosition() != bodyEnd)
         return Fail(cx, d, "function body length mismatch");
 
     if (!fg.bytes().resize(bodySize))
         return false;
 
new file mode 100644
--- /dev/null
+++ b/js/src/asmjs/WasmBinaryIterator.cpp
@@ -0,0 +1,368 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ *
+ * Copyright 2015 Mozilla Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "asmjs/WasmBinaryIterator.h"
+
+using namespace js;
+using namespace js::jit;
+using namespace js::wasm;
+
+#ifdef DEBUG
+ExprKind
+wasm::Classify(Expr expr)
+{
+    switch (expr) {
+      case Expr::Block:
+        return ExprKind::Block;
+      case Expr::Loop:
+        return ExprKind::Loop;
+      case Expr::Unreachable:
+        return ExprKind::Unreachable;
+      case Expr::I32Const:
+        return ExprKind::I32;
+      case Expr::I64Const:
+        return ExprKind::I64;
+      case Expr::F32Const:
+        return ExprKind::F32;
+      case Expr::F64Const:
+        return ExprKind::F64;
+      case Expr::I32x4Const:
+        return ExprKind::I32x4;
+      case Expr::B32x4Const:
+        return ExprKind::B32x4;
+      case Expr::F32x4Const:
+        return ExprKind::F32x4;
+      case Expr::Br:
+        return ExprKind::Br;
+      case Expr::BrIf:
+        return ExprKind::BrIf;
+      case Expr::BrTable:
+        return ExprKind::BrTable;
+      case Expr::Nop:
+        return ExprKind::Nullary;
+      case Expr::I32Clz:
+      case Expr::I32Ctz:
+      case Expr::I32Popcnt:
+      case Expr::I64Clz:
+      case Expr::I64Ctz:
+      case Expr::I64Popcnt:
+      case Expr::I64Eqz:
+      case Expr::F32Abs:
+      case Expr::F32Neg:
+      case Expr::F32Ceil:
+      case Expr::F32Floor:
+      case Expr::F32Sqrt:
+      case Expr::F64Abs:
+      case Expr::F64Neg:
+      case Expr::F64Ceil:
+      case Expr::F64Floor:
+      case Expr::F64Sqrt:
+      case Expr::I32BitNot:
+      case Expr::I32Abs:
+      case Expr::F64Sin:
+      case Expr::F64Cos:
+      case Expr::F64Tan:
+      case Expr::F64Asin:
+      case Expr::F64Acos:
+      case Expr::F64Atan:
+      case Expr::F64Exp:
+      case Expr::F64Log:
+      case Expr::I32Neg:
+      case Expr::I32x4neg:
+      case Expr::I32x4not:
+      case Expr::F32x4neg:
+      case Expr::F32x4sqrt:
+      case Expr::F32x4abs:
+      case Expr::F32x4reciprocalApproximation:
+      case Expr::F32x4reciprocalSqrtApproximation:
+      case Expr::B32x4not:
+        return ExprKind::Unary;
+      case Expr::I32Add:
+      case Expr::I32Sub:
+      case Expr::I32Mul:
+      case Expr::I32DivS:
+      case Expr::I32DivU:
+      case Expr::I32RemS:
+      case Expr::I32RemU:
+      case Expr::I32And:
+      case Expr::I32Or:
+      case Expr::I32Xor:
+      case Expr::I32Shl:
+      case Expr::I32ShrS:
+      case Expr::I32ShrU:
+      case Expr::I32Rotl:
+      case Expr::I32Rotr:
+      case Expr::I64Add:
+      case Expr::I64Sub:
+      case Expr::I64Mul:
+      case Expr::I64DivS:
+      case Expr::I64DivU:
+      case Expr::I64RemS:
+      case Expr::I64RemU:
+      case Expr::I64And:
+      case Expr::I64Or:
+      case Expr::I64Xor:
+      case Expr::I64Shl:
+      case Expr::I64ShrS:
+      case Expr::I64ShrU:
+      case Expr::I64Rotl:
+      case Expr::I64Rotr:
+      case Expr::F32Add:
+      case Expr::F32Sub:
+      case Expr::F32Mul:
+      case Expr::F32Div:
+      case Expr::F32Min:
+      case Expr::F32Max:
+      case Expr::F32CopySign:
+      case Expr::F64Add:
+      case Expr::F64Sub:
+      case Expr::F64Mul:
+      case Expr::F64Div:
+      case Expr::F64Min:
+      case Expr::F64Max:
+      case Expr::F64CopySign:
+      case Expr::I32Min:
+      case Expr::I32Max:
+      case Expr::F64Mod:
+      case Expr::F64Pow:
+      case Expr::F64Atan2:
+      case Expr::I32x4add:
+      case Expr::I32x4sub:
+      case Expr::I32x4mul:
+      case Expr::I32x4and:
+      case Expr::I32x4or:
+      case Expr::I32x4xor:
+      case Expr::F32x4add:
+      case Expr::F32x4sub:
+      case Expr::F32x4mul:
+      case Expr::F32x4div:
+      case Expr::F32x4min:
+      case Expr::F32x4max:
+      case Expr::F32x4minNum:
+      case Expr::F32x4maxNum:
+      case Expr::B32x4and:
+      case Expr::B32x4or:
+      case Expr::B32x4xor:
+        return ExprKind::Binary;
+      case Expr::I32Eq:
+      case Expr::I32Ne:
+      case Expr::I32LtS:
+      case Expr::I32LtU:
+      case Expr::I32LeS:
+      case Expr::I32LeU:
+      case Expr::I32GtS:
+      case Expr::I32GtU:
+      case Expr::I32GeS:
+      case Expr::I32GeU:
+      case Expr::I64Eq:
+      case Expr::I64Ne:
+      case Expr::I64LtS:
+      case Expr::I64LtU:
+      case Expr::I64LeS:
+      case Expr::I64LeU:
+      case Expr::I64GtS:
+      case Expr::I64GtU:
+      case Expr::I64GeS:
+      case Expr::I64GeU:
+      case Expr::F32Eq:
+      case Expr::F32Ne:
+      case Expr::F32Lt:
+      case Expr::F32Le:
+      case Expr::F32Gt:
+      case Expr::F32Ge:
+      case Expr::F64Eq:
+      case Expr::F64Ne:
+      case Expr::F64Lt:
+      case Expr::F64Le:
+      case Expr::F64Gt:
+      case Expr::F64Ge:
+        return ExprKind::Comparison;
+      case Expr::I32Eqz:
+      case Expr::I32WrapI64:
+      case Expr::I32TruncSF32:
+      case Expr::I32TruncUF32:
+      case Expr::I32ReinterpretF32:
+      case Expr::I32TruncSF64:
+      case Expr::I32TruncUF64:
+      case Expr::I64ExtendSI32:
+      case Expr::I64ExtendUI32:
+      case Expr::I64TruncSF32:
+      case Expr::I64TruncUF32:
+      case Expr::I64TruncSF64:
+      case Expr::I64TruncUF64:
+      case Expr::I64ReinterpretF64:
+      case Expr::F32ConvertSI32:
+      case Expr::F32ConvertUI32:
+      case Expr::F32ReinterpretI32:
+      case Expr::F32ConvertSI64:
+      case Expr::F32ConvertUI64:
+      case Expr::F32DemoteF64:
+      case Expr::F64ConvertSI32:
+      case Expr::F64ConvertUI32:
+      case Expr::F64ConvertSI64:
+      case Expr::F64ConvertUI64:
+      case Expr::F64ReinterpretI64:
+      case Expr::F64PromoteF32:
+      case Expr::I32x4fromFloat32x4:
+      case Expr::I32x4fromFloat32x4U:
+      case Expr::F32x4fromInt32x4:
+      case Expr::F32x4fromUint32x4:
+      case Expr::I32x4fromFloat32x4Bits:
+      case Expr::F32x4fromInt32x4Bits:
+      case Expr::F32x4fromUint32x4Bits:
+        return ExprKind::Conversion;
+      case Expr::I32Load8S:
+      case Expr::I32Load8U:
+      case Expr::I32Load16S:
+      case Expr::I32Load16U:
+      case Expr::I64Load8S:
+      case Expr::I64Load8U:
+      case Expr::I64Load16S:
+      case Expr::I64Load16U:
+      case Expr::I64Load32S:
+      case Expr::I64Load32U:
+      case Expr::I32Load:
+      case Expr::I64Load:
+      case Expr::F32Load:
+      case Expr::F64Load:
+      case Expr::I32x4load:
+      case Expr::I32x4load1:
+      case Expr::I32x4load2:
+      case Expr::I32x4load3:
+      case Expr::F32x4load:
+      case Expr::F32x4load1:
+      case Expr::F32x4load2:
+      case Expr::F32x4load3:
+        return ExprKind::Load;
+      case Expr::I32Store8:
+      case Expr::I32Store16:
+      case Expr::I64Store8:
+      case Expr::I64Store16:
+      case Expr::I64Store32:
+      case Expr::I32Store:
+      case Expr::I64Store:
+      case Expr::F32Store:
+      case Expr::F64Store:
+      case Expr::F32StoreF64:
+      case Expr::F64StoreF32:
+      case Expr::I32x4store:
+      case Expr::I32x4store1:
+      case Expr::I32x4store2:
+      case Expr::I32x4store3:
+      case Expr::F32x4store:
+      case Expr::F32x4store1:
+      case Expr::F32x4store2:
+      case Expr::F32x4store3:
+        return ExprKind::Store;
+      case Expr::Select:
+        return ExprKind::Select;
+      case Expr::GetLocal:
+      case Expr::LoadGlobal:
+        return ExprKind::GetVar;
+      case Expr::SetLocal:
+      case Expr::StoreGlobal:
+        return ExprKind::SetVar;
+      case Expr::Call:
+        return ExprKind::Call;
+      case Expr::CallIndirect:
+        return ExprKind::CallIndirect;
+      case Expr::CallImport:
+        return ExprKind::CallImport;
+      case Expr::Return:
+      case Expr::Limit:
+        // Accept Limit, for use in decoding the end of a function after the body.
+        return ExprKind::Return;
+      case Expr::If:
+        return ExprKind::If;
+      case Expr::Else:
+        return ExprKind::Else;
+      case Expr::End:
+        return ExprKind::End;
+      case Expr::I32AtomicsLoad:
+        return ExprKind::AtomicLoad;
+      case Expr::I32AtomicsStore:
+        return ExprKind::AtomicStore;
+      case Expr::I32AtomicsBinOp:
+        return ExprKind::AtomicBinOp;
+      case Expr::I32AtomicsCompareExchange:
+        return ExprKind::AtomicCompareExchange;
+      case Expr::I32AtomicsExchange:
+        return ExprKind::AtomicExchange;
+      case Expr::I32x4extractLane:
+      case Expr::F32x4extractLane:
+      case Expr::B32x4extractLane:
+        return ExprKind::ExtractLane;
+      case Expr::I32x4replaceLane:
+      case Expr::F32x4replaceLane:
+      case Expr::B32x4replaceLane:
+        return ExprKind::ReplaceLane;
+      case Expr::I32x4swizzle:
+      case Expr::F32x4swizzle:
+        return ExprKind::Swizzle;
+      case Expr::I32x4shuffle:
+      case Expr::F32x4shuffle:
+        return ExprKind::Shuffle;
+      case Expr::I32x4splat:
+      case Expr::F32x4splat:
+      case Expr::B32x4splat:
+      case Expr::I32x4check:
+      case Expr::F32x4check:
+      case Expr::B32x4check:
+        return ExprKind::Splat;
+      case Expr::I32x4select:
+      case Expr::F32x4select:
+        return ExprKind::SimdSelect;
+      case Expr::I32x4Constructor:
+      case Expr::F32x4Constructor:
+      case Expr::B32x4Constructor:
+        return ExprKind::SimdCtor;
+      case Expr::B32x4anyTrue:
+      case Expr::B32x4allTrue:
+        return ExprKind::SimdBooleanReduction;
+      case Expr::I32x4shiftLeftByScalar:
+      case Expr::I32x4shiftRightByScalar:
+      case Expr::I32x4shiftRightByScalarU:
+        return ExprKind::SimdShiftByScalar;
+      case Expr::I32x4equal:
+      case Expr::I32x4notEqual:
+      case Expr::I32x4greaterThan:
+      case Expr::I32x4greaterThanOrEqual:
+      case Expr::I32x4lessThan:
+      case Expr::I32x4lessThanOrEqual:
+      case Expr::I32x4greaterThanU:
+      case Expr::I32x4greaterThanOrEqualU:
+      case Expr::I32x4lessThanU:
+      case Expr::I32x4lessThanOrEqualU:
+      case Expr::F32x4equal:
+      case Expr::F32x4notEqual:
+      case Expr::F32x4greaterThan:
+      case Expr::F32x4greaterThanOrEqual:
+      case Expr::F32x4lessThan:
+      case Expr::F32x4lessThanOrEqual:
+        return ExprKind::SimdComparison;
+      case Expr::CurrentMemory:
+      case Expr::GrowMemory:
+      case Expr::F32Trunc:
+      case Expr::F32Nearest:
+      case Expr::F64Trunc:
+      case Expr::F64Nearest:
+        break;
+    }
+    MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unimplemented opcode");
+}
+#endif
--- a/js/src/asmjs/WasmBinaryIterator.h
+++ b/js/src/asmjs/WasmBinaryIterator.h
@@ -11,334 +11,103 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#ifndef wasm_iterator_h
-#define wasm_iterator_h
+#ifndef wasm_binary_iterator_h
+#define wasm_binary_iterator_h
 
 #include "mozilla/Poison.h"
 
 #include "jsprf.h"
 
 #include "asmjs/WasmTypes.h"
 #include "jit/AtomicOp.h"
 
 namespace js {
 namespace wasm {
 
 // The kind of a control-flow stack item.
 enum class LabelKind : uint8_t { Block, Loop, Then, Else };
 
-// A description of a unary operator's parameters.
-template <typename Value>
-struct UnaryRecord
-{
-    Value op;
-
-    explicit UnaryRecord(Value op) : op(op) {}
+#ifdef DEBUG
+// Families of opcodes that share a signature and validation logic.
+enum class ExprKind {
+    Block,
+    Loop,
+    Unreachable,
+    I32,
+    I64,
+    F32,
+    F64,
+    I32x4,
+    B32x4,
+    F32x4,
+    Br,
+    BrIf,
+    BrTable,
+    Nullary,
+    Unary,
+    Binary,
+    Comparison,
+    Conversion,
+    Load,
+    Store,
+    Select,
+    GetVar,
+    SetVar,
+    Call,
+    CallIndirect,
+    CallImport,
+    Return,
+    If,
+    Else,
+    End,
+    AtomicLoad,
+    AtomicStore,
+    AtomicBinOp,
+    AtomicCompareExchange,
+    AtomicExchange,
+    ExtractLane,
+    ReplaceLane,
+    Swizzle,
+    Shuffle,
+    Splat,
+    SimdSelect,
+    SimdCtor,
+    SimdBooleanReduction,
+    SimdShiftByScalar,
+    SimdComparison,
 };
 
-// A description of a binary operator's parameters.
-template <typename Value>
-struct BinaryRecord
-{
-    Value lhs;
-    Value rhs;
-
-    BinaryRecord(Value lhs, Value rhs) : lhs(lhs), rhs(rhs) {}
-};
-
-// A description of a select operator's parameters.
-template <typename Value>
-struct SelectRecord
-{
-    ExprType type;
-    Value trueValue;
-    Value falseValue;
-    Value condition;
-
-    SelectRecord(ExprType type, Value trueValue, Value falseValue, Value condition)
-      : type(type), trueValue(trueValue), falseValue(falseValue), condition(condition)
-    {}
-};
+// Return the ExprKind for a given Expr. This is used for sanity-checking that
+// API users use the correct read function for a given Expr.
+ExprKind
+Classify(Expr expr);
+#endif
 
 // Common fields for linear memory access.
 template <typename Value>
 struct LinearMemoryAddress
 {
     Value base;
     uint32_t offset;
     uint32_t align;
 
+    LinearMemoryAddress()
+    {}
     LinearMemoryAddress(Value base, uint32_t offset, uint32_t align)
       : base(base), offset(offset), align(align)
     {}
 };
 
-// A description of a load operator's parameters.
-template <typename Value>
-struct LoadRecord
-{
-    LinearMemoryAddress<Value> addr;
-
-    LoadRecord(Value base, uint32_t offset, uint32_t align)
-      : addr(base, offset, align)
-    {}
-};
-
-// A description of a store operator's parameters.
-template <typename Value>
-struct StoreRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Value value;
-
-    StoreRecord(Value base, uint32_t offset, uint32_t align, Value value)
-      : addr(base, offset, align), value(value)
-    {}
-};
-
-// A description of an if operator's parameters.
-template <typename Value>
-struct IfRecord
-{
-    Value condition;
-
-    explicit IfRecord(Value condition) : condition(condition) {}
-};
-
-// A description of an else operator's parameters.
-template <typename Value>
-struct ElseRecord
-{
-    ExprType type;
-    Value thenValue;
-
-    ElseRecord(ExprType type, Value thenValue) : type(type), thenValue(thenValue) {}
-};
-
-// A description of a block, loop, if, or else operator's parameters.
-template <typename Value>
-struct EndRecord
-{
-    LabelKind kind;
-    ExprType type;
-    Value value;
-
-    explicit EndRecord(LabelKind kind, ExprType type, Value value)
-      : kind(kind), type(type), value(value)
-    {}
-};
-
-// A description of a br operator's parameters.
-template <typename Value>
-struct BrRecord
-{
-    uint32_t relativeDepth;
-    ExprType type;
-    Value value;
-
-    BrRecord(uint32_t relativeDepth, ExprType type, Value value)
-      : relativeDepth(relativeDepth), type(type), value(value) {}
-};
-
-// A description of a br_if operator's parameters.
-template <typename Value>
-struct BrIfRecord
-{
-    uint32_t relativeDepth;
-    ExprType type;
-    Value value;
-    Value condition;
-
-    BrIfRecord(uint32_t relativeDepth, ExprType type, Value value, Value condition)
-      : relativeDepth(relativeDepth), type(type), value(value), condition(condition)
-    {}
-};
-
-// A description of a br_table operator's parameters.
-template <typename Value>
-struct BrTableRecord
-{
-    uint32_t tableLength;
-    ExprType type;
-    Value value;
-    Value index;
-
-    BrTableRecord(uint32_t tableLength, ExprType type, Value value, Value index)
-      : tableLength(tableLength), type(type), value(value), index(index)
-    {}
-};
-
-// A description of a get_local or get_global operator's parameters.
-struct GetVarRecord
-{
-    uint32_t id;
-
-    explicit GetVarRecord(uint32_t id) : id(id) {}
-};
-
-// A description of a set_local or set_global operator's parameters.
-template <typename Value>
-struct SetVarRecord
-{
-    uint32_t id;
-    Value value;
-
-    SetVarRecord(uint32_t id, Value value) : id(id), value(value) {}
-};
-
-// A description of a call operator's parameters, not counting the call arguments.
-struct CallRecord
-{
-    uint32_t arity;
-    uint32_t callee;
-
-    CallRecord(uint32_t arity, uint32_t callee)
-      : arity(arity), callee(callee)
-    {}
-};
-
-// A description of a call_indirect operator's parameters, not counting the call arguments.
-template <typename Value>
-struct CallIndirectRecord
-{
-    uint32_t arity;
-    uint32_t sigIndex;
-
-    CallIndirectRecord(uint32_t arity, uint32_t sigIndex)
-      : arity(arity), sigIndex(sigIndex)
-    {}
-};
-
-// A description of a call_import operator's parameters, not counting the call arguments.
-struct CallImportRecord
-{
-    uint32_t arity;
-    uint32_t callee;
-
-    CallImportRecord(uint32_t arity, uint32_t callee)
-      : arity(arity), callee(callee)
-    {}
-};
-
-// A description of a return operator's parameters.
-template <typename Value>
-struct ReturnRecord
-{
-    Value value;
-
-    explicit ReturnRecord(Value value) : value(value) {}
-};
-
-template <typename Value>
-struct AtomicLoadRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Scalar::Type viewType;
-};
-
-template <typename Value>
-struct AtomicStoreRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Scalar::Type viewType;
-    Value value;
-};
-
-template <typename Value>
-struct AtomicBinOpRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Scalar::Type viewType;
-    jit::AtomicOp op;
-    Value value;
-};
-
-template <typename Value>
-struct AtomicCompareExchangeRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Scalar::Type viewType;
-    Value oldValue;
-    Value newValue;
-};
-
-template <typename Value>
-struct AtomicExchangeRecord
-{
-    LinearMemoryAddress<Value> addr;
-    Scalar::Type viewType;
-    Value value;
-};
-
-template <typename Value>
-struct ExtractLaneRecord
-{
-    jit::SimdLane lane;
-    Value vector;
-
-    ExtractLaneRecord(jit::SimdLane lane, Value vector) : lane(lane), vector(vector) {}
-};
-
-template <typename Value>
-struct ReplaceLaneRecord
-{
-    jit::SimdLane lane;
-    Value vector;
-    Value scalar;
-
-    ReplaceLaneRecord(jit::SimdLane lane, Value vector, Value scalar)
-      : lane(lane), vector(vector), scalar(scalar)
-    {}
-};
-
-template <typename Value>
-struct SwizzleRecord
-{
-    uint8_t lanes[4];
-    Value vector;
-
-    SwizzleRecord(uint8_t lanes[4], Value vector)
-      : vector(vector)
-    {
-        memcpy(this->lanes, lanes, sizeof(this->lanes));
-    }
-};
-
-template <typename Value>
-struct ShuffleRecord
-{
-    uint8_t lanes[4];
-    Value lhs;
-    Value rhs;
-
-    ShuffleRecord(uint8_t lanes[4], Value lhs, Value rhs)
-      : lhs(lhs), rhs(rhs)
-    {
-        memcpy(this->lanes, lanes, sizeof(this->lanes));
-    }
-};
-
-template <typename Value>
-struct SimdSelectRecord
-{
-    Value trueValue;
-    Value falseValue;
-    Value condition;
-
-    SimdSelectRecord(Value trueValue, Value falseValue, Value condition)
-      : trueValue(trueValue), falseValue(falseValue), condition(condition) {}
-};
-
 struct Nothing {};
 
 template <typename ControlItem>
 class ControlStackEntry
 {
     LabelKind kind_;
     ExprType type_;
     size_t valueStackStart_;
@@ -423,92 +192,52 @@ struct TypeAndValue<Nothing>
 // A policy class for configuring ExprIter. Clients can use this as a
 // base class, and override the behavior as needed.
 struct ExprIterPolicy
 {
     // Should the iterator perform validation, such as type checking and
     // validity checking?
     static const bool Validate = false;
 
+    // Should the iterator produce output values?
+    static const bool Output = false;
+
     // This function is called to report failures.
     static bool fail(const char*, Decoder&) {
         MOZ_CRASH("unexpected validation failure");
         return false;
     }
 
     // These members allow clients to add additional information to the value
     // and control stacks, respectively. Using Nothing means that no additional
     // field is added.
     typedef Nothing Value;
     typedef Nothing ControlItem;
 };
 
 // An iterator over the bytes of a function body. It performs validation
 // (if Policy::Validate is true) and unpacks the data into a usable form.
+//
+// The MOZ_STACK_CLASS attribute here is because of the use of DebugOnly.
+// There's otherwise nothing inherent in this class which would require
+// it to be used on the stack.
 template <typename Policy>
-class ExprIter : private Policy
+class MOZ_STACK_CLASS ExprIter : private Policy
 {
     static const bool Validate = Policy::Validate;
+    static const bool Output = Policy::Output;
     typedef typename Policy::Value Value;
     typedef typename Policy::ControlItem ControlItem;
 
-    // A union containing all the expression description types.
-    union ExprRecord {
-        ExprRecord() {
-#ifdef DEBUG
-            mozWritePoison(this, sizeof(*this));
-#endif
-        }
-
-        int32_t i32;
-        int64_t i64;
-        float f32;
-        double f64;
-        I32x4 i32x4;
-        F32x4 f32x4;
-        BrRecord<Value> br;
-        BrIfRecord<Value> brIf;
-        BrTableRecord<Value> brTable;
-        UnaryRecord<Value> unary;
-        BinaryRecord<Value> binary;
-        LoadRecord<Value> load;
-        StoreRecord<Value> store;
-        SelectRecord<Value> select;
-        GetVarRecord getVar;
-        SetVarRecord<Value> setVar;
-        CallRecord call;
-        CallIndirectRecord<Value> callIndirect;
-        CallImportRecord callImport;
-        ReturnRecord<Value> return_;
-        IfRecord<Value> if_;
-        ElseRecord<Value> else_;
-        EndRecord<Value> end;
-        AtomicLoadRecord<Value> atomicLoad;
-        AtomicStoreRecord<Value> atomicStore;
-        AtomicBinOpRecord<Value> atomicBinOp;
-        AtomicCompareExchangeRecord<Value> atomicCompareExchange;
-        AtomicExchangeRecord<Value> atomicExchange;
-        ExtractLaneRecord<Value> extractLane;
-        ReplaceLaneRecord<Value> replaceLane;
-        SwizzleRecord<Value> swizzle;
-        ShuffleRecord<Value> shuffle;
-        SimdSelectRecord<Value> simdSelect;
-    };
-
     Decoder& d_;
 
     Vector<TypeAndValue<Value>, 0, SystemAllocPolicy> valueStack_;
     Vector<ControlStackEntry<ControlItem>, 0, SystemAllocPolicy> controlStack_;
 
-    ExprRecord u_;
-
-#ifdef DEBUG
-    Expr expr_;
-    bool isInitialized_;
-#endif
+    DebugOnly<Expr> expr_;
 
     MOZ_MUST_USE bool readFixedU8(uint8_t* out) {
         if (Validate)
             return d_.readFixedU8(out);
         *out = d_.uncheckedReadFixedU8();
         return true;
     }
     MOZ_MUST_USE bool readFixedU32(uint32_t* out) {
@@ -591,251 +320,26 @@ class ExprIter : private Policy
               default:
                 return fail("unrecognized atomic binop");
             }
         }
         *op = jit::AtomicOp(x);
         return true;
     }
 
-#ifdef DEBUG
-    bool isI32()     const { return isInitialized_ && expr_ == Expr::I32Const; }
-    bool isI64()     const { return isInitialized_ && expr_ == Expr::I64Const; }
-    bool isF32()     const { return isInitialized_ && expr_ == Expr::F32Const; }
-    bool isF64()     const { return isInitialized_ && expr_ == Expr::F64Const; }
-    bool isI32x4()   const { return isInitialized_ && (expr_ == Expr::I32x4Const ||
-                                                       expr_ == Expr::B32x4Const); }
-    bool isF32x4()   const { return isInitialized_ && expr_ == Expr::F32x4Const; }
-    bool isBr()      const { return isInitialized_ && expr_ == Expr::Br; }
-    bool isBrIf()    const { return isInitialized_ && expr_ == Expr::BrIf; }
-    bool isBrTable() const { return isInitialized_ && expr_ == Expr::BrTable; }
-    bool isUnary() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32Clz || expr_ == Expr::I32Ctz ||
-                expr_ == Expr::I32Popcnt || expr_ == Expr::I32Eqz ||
-                expr_ == Expr::I64Clz || expr_ == Expr::I64Ctz ||
-                expr_ == Expr::I64Popcnt || expr_ == Expr::I64Eqz ||
-                expr_ == Expr::F32Abs || expr_ == Expr::F32Neg ||
-                expr_ == Expr::F32Ceil || expr_ == Expr::F32Floor ||
-                expr_ == Expr::F32Sqrt || expr_ == Expr::F64Abs ||
-                expr_ == Expr::F64Neg || expr_ == Expr::F64Ceil ||
-                expr_ == Expr::F64Floor || expr_ == Expr::F64Sqrt ||
-                expr_ == Expr::I32WrapI64 || expr_ == Expr::I32TruncSF32 ||
-                expr_ == Expr::I32TruncUF32 || expr_ == Expr::I32ReinterpretF32 ||
-                expr_ == Expr::I32TruncSF64 || expr_ == Expr::I32TruncUF64 ||
-                expr_ == Expr::I64ExtendSI32 || expr_ == Expr::I64ExtendUI32 ||
-                expr_ == Expr::I64TruncSF32 || expr_ == Expr::I64TruncUF32 ||
-                expr_ == Expr::I64TruncSF64 || expr_ == Expr::I64TruncUF64 ||
-                expr_ == Expr::I64ReinterpretF64 || expr_ == Expr::F32ConvertSI32 ||
-                expr_ == Expr::F32ConvertUI32 || expr_ == Expr::F32ReinterpretI32 ||
-                expr_ == Expr::F32ConvertSI64 || expr_ == Expr::F32ConvertUI64 ||
-                expr_ == Expr::F32DemoteF64 || expr_ == Expr::F64ConvertSI32 ||
-                expr_ == Expr::F64ConvertUI32 || expr_ == Expr::F64ConvertSI64 ||
-                expr_ == Expr::F64ConvertUI64 || expr_ == Expr::F64ReinterpretI64 ||
-                expr_ == Expr::F64PromoteF32 ||
-                expr_ == Expr::I32BitNot || expr_ == Expr::I32Abs ||
-                expr_ == Expr::F64Sin || expr_ == Expr::F64Cos ||
-                expr_ == Expr::F64Tan || expr_ == Expr::F64Asin ||
-                expr_ == Expr::F64Acos || expr_ == Expr::F64Atan ||
-                expr_ == Expr::F64Exp || expr_ == Expr::F64Log ||
-                expr_ == Expr::I32Neg ||
-                expr_ == Expr::I32x4splat || expr_ == Expr::F32x4splat ||
-                expr_ == Expr::B32x4splat ||
-                expr_ == Expr::I32x4check || expr_ == Expr::F32x4check ||
-                expr_ == Expr::B32x4check ||
-                expr_ == Expr::I32x4fromFloat32x4 ||
-                expr_ == Expr::I32x4fromFloat32x4U ||
-                expr_ == Expr::F32x4fromInt32x4 ||
-                expr_ == Expr::F32x4fromUint32x4 ||
-                expr_ == Expr::I32x4fromFloat32x4Bits ||
-                expr_ == Expr::F32x4fromInt32x4Bits ||
-                expr_ == Expr::F32x4fromUint32x4Bits ||
-                expr_ == Expr::I32x4neg || expr_ == Expr::I32x4not ||
-                expr_ == Expr::F32x4neg || expr_ == Expr::F32x4sqrt ||
-                expr_ == Expr::F32x4abs ||
-                expr_ == Expr::F32x4reciprocalApproximation ||
-                expr_ == Expr::F32x4reciprocalSqrtApproximation ||
-                expr_ == Expr::B32x4not ||
-                expr_ == Expr::B32x4anyTrue ||
-                expr_ == Expr::B32x4allTrue);
-    }
-    bool isBinary() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32Add || expr_ == Expr::I32Sub ||
-                expr_ == Expr::I32Mul || expr_ == Expr::I32DivS ||
-                expr_ == Expr::I32DivU || expr_ == Expr::I32RemS ||
-                expr_ == Expr::I32RemU || expr_ == Expr::I32And ||
-                expr_ == Expr::I32Or || expr_ == Expr::I32Xor ||
-                expr_ == Expr::I32Shl || expr_ == Expr::I32ShrS ||
-                expr_ == Expr::I32ShrU || expr_ == Expr::I32Rotl ||
-                expr_ == Expr::I32Rotr || expr_ == Expr::I64Add ||
-                expr_ == Expr::I64Sub || expr_ == Expr::I64Mul ||
-                expr_ == Expr::I64DivS || expr_ == Expr::I64DivU ||
-                expr_ == Expr::I64RemS || expr_ == Expr::I64RemU ||
-                expr_ == Expr::I64And || expr_ == Expr::I64Or ||
-                expr_ == Expr::I64Xor || expr_ == Expr::I64Shl ||
-                expr_ == Expr::I64ShrS || expr_ == Expr::I64ShrU ||
-                expr_ == Expr::I64Rotl || expr_ == Expr::I64Rotr ||
-                expr_ == Expr::F32Add || expr_ == Expr::F32Sub ||
-                expr_ == Expr::F32Mul || expr_ == Expr::F32Div ||
-                expr_ == Expr::F32Min || expr_ == Expr::F32Max ||
-                expr_ == Expr::F32CopySign || expr_ == Expr::F64Add ||
-                expr_ == Expr::F64Sub || expr_ == Expr::F64Mul ||
-                expr_ == Expr::F64Div || expr_ == Expr::F64Min ||
-                expr_ == Expr::F64Max || expr_ == Expr::F64CopySign ||
-                expr_ == Expr::I32Eq || expr_ == Expr::I32Ne ||
-                expr_ == Expr::I32LtS || expr_ == Expr::I32LtU ||
-                expr_ == Expr::I32LeS || expr_ == Expr::I32LeU ||
-                expr_ == Expr::I32GtS || expr_ == Expr::I32GtU ||
-                expr_ == Expr::I32GeS || expr_ == Expr::I32GeU ||
-                expr_ == Expr::I64Eq || expr_ == Expr::I64Ne ||
-                expr_ == Expr::I64LtS || expr_ == Expr::I64LtU ||
-                expr_ == Expr::I64LeS || expr_ == Expr::I64LeU ||
-                expr_ == Expr::I64GtS || expr_ == Expr::I64GtU ||
-                expr_ == Expr::I64GeS || expr_ == Expr::I64GeU ||
-                expr_ == Expr::F32Eq || expr_ == Expr::F32Ne ||
-                expr_ == Expr::F32Lt || expr_ == Expr::F32Le ||
-                expr_ == Expr::F32Gt || expr_ == Expr::F32Ge ||
-                expr_ == Expr::F64Eq || expr_ == Expr::F64Ne ||
-                expr_ == Expr::F64Lt || expr_ == Expr::F64Le ||
-                expr_ == Expr::F64Gt || expr_ == Expr::F64Ge ||
-                expr_ == Expr::I32Min || expr_ == Expr::I32Max ||
-                expr_ == Expr::F64Mod || expr_ == Expr::F64Pow ||
-                expr_ == Expr::F64Atan2 ||
-                expr_ == Expr::I32x4add || expr_ == Expr::I32x4sub ||
-                expr_ == Expr::I32x4mul ||
-                expr_ == Expr::I32x4and || expr_ == Expr::I32x4or ||
-                expr_ == Expr::I32x4xor ||
-                expr_ == Expr::I32x4shiftLeftByScalar ||
-                expr_ == Expr::I32x4shiftRightByScalar ||
-                expr_ == Expr::I32x4shiftRightByScalarU ||
-                expr_ == Expr::I32x4equal || expr_ == Expr::I32x4notEqual ||
-                expr_ == Expr::I32x4greaterThan ||
-                expr_ == Expr::I32x4greaterThanOrEqual ||
-                expr_ == Expr::I32x4lessThan ||
-                expr_ == Expr::I32x4lessThanOrEqual ||
-                expr_ == Expr::I32x4greaterThanU ||
-                expr_ == Expr::I32x4greaterThanOrEqualU ||
-                expr_ == Expr::I32x4lessThanU ||
-                expr_ == Expr::I32x4lessThanOrEqualU ||
-                expr_ == Expr::F32x4add || expr_ == Expr::F32x4sub ||
-                expr_ == Expr::F32x4mul || expr_ == Expr::F32x4div ||
-                expr_ == Expr::F32x4min || expr_ == Expr::F32x4max ||
-                expr_ == Expr::F32x4minNum || expr_ == Expr::F32x4maxNum ||
-                expr_ == Expr::F32x4equal ||
-                expr_ == Expr::F32x4notEqual ||
-                expr_ == Expr::F32x4greaterThan ||
-                expr_ == Expr::F32x4greaterThanOrEqual ||
-                expr_ == Expr::F32x4lessThan ||
-                expr_ == Expr::F32x4lessThanOrEqual ||
-                expr_ == Expr::B32x4and || expr_ == Expr::B32x4or ||
-                expr_ == Expr::B32x4xor);
-    }
-    bool isLoad() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32Load8S || expr_ == Expr::I32Load8U ||
-                expr_ == Expr::I32Load16S || expr_ == Expr::I32Load16U ||
-                expr_ == Expr::I64Load8S || expr_ == Expr::I64Load8U ||
-                expr_ == Expr::I64Load16S || expr_ == Expr::I64Load16U ||
-                expr_ == Expr::I64Load32S || expr_ == Expr::I64Load32U ||
-                expr_ == Expr::I32Load || expr_ == Expr::I64Load ||
-                expr_ == Expr::F32Load || expr_ == Expr::F64Load ||
-                expr_ == Expr::I32x4load || expr_ == Expr::I32x4load1 ||
-                expr_ == Expr::I32x4load2 || expr_ == Expr::I32x4load3 ||
-                expr_ == Expr::F32x4load || expr_ == Expr::F32x4load1 ||
-                expr_ == Expr::F32x4load2 || expr_ == Expr::F32x4load3);
-    }
-    bool isStore() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32Store8 || expr_ == Expr::I32Store16 ||
-                expr_ == Expr::I64Store8 || expr_ == Expr::I64Store16 ||
-                expr_ == Expr::I64Store32 || expr_ == Expr::I32Store ||
-                expr_ == Expr::I64Store || expr_ == Expr::F32Store ||
-                expr_ == Expr::F64Store ||
-                expr_ == Expr::F32StoreF64 || expr_ == Expr::F64StoreF32 ||
-                expr_ == Expr::I32x4store || expr_ == Expr::I32x4store1 ||
-                expr_ == Expr::I32x4store2 || expr_ == Expr::I32x4store3 ||
-                expr_ == Expr::F32x4store || expr_ == Expr::F32x4store1 ||
-                expr_ == Expr::F32x4store2 || expr_ == Expr::F32x4store3);
-    }
-    bool isSelect()  const { return isInitialized_ && expr_ == Expr::Select; }
-    bool isGetVar()  const { return isInitialized_ &&
-                                    (expr_ == Expr::GetLocal ||
-                                     expr_ == Expr::LoadGlobal); }
-    bool isSetVar()  const { return isInitialized_ &&
-                                    (expr_ == Expr::SetLocal ||
-                                     expr_ == Expr::StoreGlobal); }
-    bool isCall()    const { return isInitialized_ && expr_ == Expr::Call; }
-    bool isCallIndirect() const { return isInitialized_ && expr_ == Expr::CallIndirect; }
-    bool isCallImport() const { return isInitialized_ && expr_ == Expr::CallImport; }
-    bool isReturn()  const {
-        // Accept Limit, for use in decoding the end of a function after the body.
-        return isInitialized_ &&
-               (expr_ == Expr::Return ||
-                expr_ == Expr::Limit);
-    }
-    bool isIf()      const { return isInitialized_ && expr_ == Expr::If; }
-    bool isElse()    const { return isInitialized_ && expr_ == Expr::Else; }
-    bool isEnd()     const { return isInitialized_ && expr_ == Expr::End; }
-    bool isAtomicLoad() const { return isInitialized_ && expr_ == Expr::I32AtomicsLoad; }
-    bool isAtomicStore() const { return isInitialized_ && expr_ == Expr::I32AtomicsStore; }
-    bool isAtomicBinOp() const { return isInitialized_ && expr_ == Expr::I32AtomicsBinOp; }
-    bool isAtomicCompareExchange() const {
-        return isInitialized_ && expr_ == Expr::I32AtomicsCompareExchange;
-    }
-    bool isAtomicExchange() const {
-        return isInitialized_ && expr_ == Expr::I32AtomicsExchange;
-    }
-    bool isExtractLane() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32x4extractLane ||
-                expr_ == Expr::F32x4extractLane ||
-                expr_ == Expr::B32x4extractLane);
-    }
-    bool isReplaceLane() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32x4replaceLane ||
-                expr_ == Expr::F32x4replaceLane ||
-                expr_ == Expr::B32x4replaceLane);
-    }
-    bool isSwizzle() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32x4swizzle ||
-                expr_ == Expr::F32x4swizzle);
-    }
-    bool isShuffle() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32x4shuffle ||
-                expr_ == Expr::F32x4shuffle);
-    }
-    bool isSimdSelect() const {
-        return isInitialized_ &&
-               (expr_ == Expr::I32x4select ||
-                expr_ == Expr::F32x4select);
-    }
-#endif
-
-#ifdef DEBUG
-    bool setInitialized() {
-        isInitialized_ = true;
-        return true;
-    }
-#else
-    bool setInitialized() { return true; }
-#endif
-
     MOZ_MUST_USE bool typeMismatch(ExprType actual, ExprType expected) MOZ_COLD;
     MOZ_MUST_USE bool checkType(ExprType actual, ExprType expected);
     MOZ_MUST_USE bool readFunctionReturnValue(ExprType ret);
     MOZ_MUST_USE bool checkBranch(uint32_t relativeDepth, ExprType type);
     MOZ_MUST_USE bool pushControl(LabelKind kind);
     MOZ_MUST_USE bool popControl(LabelKind* kind, ExprType* type, Value* value);
     MOZ_MUST_USE bool popControlAfterCheck(LabelKind* kind, ExprType* type, Value* value);
     MOZ_MUST_USE bool push(ExprType t) { return valueStack_.emplaceBack(t); }
     MOZ_MUST_USE bool push(TypeAndValue<Value> tv) { return valueStack_.append(tv); }
+
     MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
 
     void infallibleCheckSuccessor(ControlStackEntry<ControlItem>& controlItem, ExprType type);
     void infalliblePush(ExprType t) { valueStack_.infallibleEmplaceBack(t); }
     void infalliblePush(TypeAndValue<Value> tv) { valueStack_.infallibleAppend(tv); }
 
     // Test whether reading the top of the value stack is currently valid.
     MOZ_MUST_USE bool checkTop() {
@@ -854,17 +358,18 @@ class ExprIter : private Policy
 
     // Pop the top of the value stack and check that it has the given type.
     MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value) {
         if (!checkTop())
             return false;
         TypeAndValue<Value> tv = valueStack_.popCopy();
         if (!checkType(tv.type(), expectedType))
             return false;
-        *value = tv.value();
+        if (Output)
+            *value = tv.value();
         return true;
     }
 
     // Read the top of the value stack (without popping it).
     MOZ_MUST_USE bool top(TypeAndValue<Value>* tv) {
         if (!checkTop())
             return false;
         *tv = valueStack_.back();
@@ -874,36 +379,34 @@ class ExprIter : private Policy
     // Read the top of the value stack (without popping it) and check that it
     // has the given type.
     MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value) {
         if (!checkTop())
             return false;
         TypeAndValue<Value>& tv = valueStack_.back();
         if (!checkType(tv.type(), expectedType))
             return false;
-        *value = tv.value();
+        if (Output)
+            *value = tv.value();
         return true;
     }
 
     // Read the value stack entry at depth |index|.
     bool peek(uint32_t index, TypeAndValue<Value>* tv) {
         if (Validate && valueStack_.length() - controlStack_.back().valueStackStart() <= index)
             return fail("peeking at value from outside block");
         *tv = valueStack_[valueStack_.length() - index];
         return true;
     }
 
   public:
     ExprIter(Policy policy, Decoder& decoder)
       : Policy(policy), d_(decoder)
     {
-#ifdef DEBUG
         expr_ = Expr::Limit;
-        isInitialized_ = false;
-#endif
     }
 
     // Return the decoding byte offset.
     uint32_t currentOffset() const { return d_.currentOffset(); }
 
     // Test whether the iterator has reached the end of the buffer.
     bool done() const { return d_.done(); }
 
@@ -916,124 +419,97 @@ class ExprIter : private Policy
     // Report an unrecognized opcode.
     MOZ_MUST_USE bool unrecognizedOpcode(Expr expr) MOZ_COLD;
 
     // ------------------------------------------------------------------------
     // Decoding and validation interface.
 
     MOZ_MUST_USE bool readExpr(Expr* expr);
     MOZ_MUST_USE bool readFunctionStart();
-    MOZ_MUST_USE bool readFunctionEnd(ExprType ret);
-    MOZ_MUST_USE bool readReturn();
+    MOZ_MUST_USE bool readFunctionEnd(ExprType ret, Value* value);
+    MOZ_MUST_USE bool readReturn(Value* value);
     MOZ_MUST_USE bool readBlock();
     MOZ_MUST_USE bool readLoop();
-    MOZ_MUST_USE bool readIf();
-    MOZ_MUST_USE bool readElse();
-    MOZ_MUST_USE bool readEnd();
-    MOZ_MUST_USE bool readBr();
-    MOZ_MUST_USE bool readBrIf();
-    MOZ_MUST_USE bool readBrTable();
+    MOZ_MUST_USE bool readIf(Value* condition);
+    MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue);
+    MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value);
+    MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type, Value* value);
+    MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type,
+                               Value* value, Value* condition);
+    MOZ_MUST_USE bool readBrTable(uint32_t* tableLength, ExprType* type,
+                                  Value* value, Value* index);
     MOZ_MUST_USE bool readBrTableEntry(ExprType type, uint32_t* depth);
     MOZ_MUST_USE bool readUnreachable();
-    MOZ_MUST_USE bool readUnary(ValType operandType);
-    MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType);
-    MOZ_MUST_USE bool readBinary(ValType operandType);
-    MOZ_MUST_USE bool readComparison(ValType operandType);
-    MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize);
-    MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize);
-    MOZ_MUST_USE bool readTrivial();
-    MOZ_MUST_USE bool readSelect();
-    MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals);
-    MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals);
-    MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals);
-    MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals);
-    MOZ_MUST_USE bool readI32Const();
-    MOZ_MUST_USE bool readI64Const();
-    MOZ_MUST_USE bool readF32Const();
-    MOZ_MUST_USE bool readF64Const();
-    MOZ_MUST_USE bool readI32x4Const();
-    MOZ_MUST_USE bool readF32x4Const();
-    MOZ_MUST_USE bool readB32x4Const();
-    MOZ_MUST_USE bool readCall();
-    MOZ_MUST_USE bool readCallIndirect();
-    MOZ_MUST_USE bool readCallImport();
+    MOZ_MUST_USE bool readUnary(ValType operandType, Value* input);
+    MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType, Value* input);
+    MOZ_MUST_USE bool readBinary(ValType operandType, Value* lhs, Value* rhs);
+    MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
+    MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
+                               LinearMemoryAddress<Value>* addr);
+    MOZ_MUST_USE bool readStore(ValType resultType, uint32_t byteSize,
+                                LinearMemoryAddress<Value>* addr, Value* value);
+    MOZ_MUST_USE bool readNullary();
+    MOZ_MUST_USE bool readSelect(ExprType* type,
+                                 Value* trueValue, Value* falseValue, Value* condition);
+    MOZ_MUST_USE bool readGetLocal(const ValTypeVector& locals, uint32_t* id);
+    MOZ_MUST_USE bool readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readGetGlobal(const GlobalDescVector& globals, uint32_t* id);
+    MOZ_MUST_USE bool readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value);
+    MOZ_MUST_USE bool readI32Const(int32_t* i32);
+    MOZ_MUST_USE bool readI64Const(int64_t* i64);
+    MOZ_MUST_USE bool readF32Const(float* f32);
+    MOZ_MUST_USE bool readF64Const(double* f64);
+    MOZ_MUST_USE bool readI32x4Const(I32x4* i32x4);
+    MOZ_MUST_USE bool readF32x4Const(F32x4* f32x4);
+    MOZ_MUST_USE bool readB32x4Const(I32x4* i32x4);
+    MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, uint32_t* arity);
+    MOZ_MUST_USE bool readCallIndirect(uint32_t* sigIndex, uint32_t* arity);
+    MOZ_MUST_USE bool readCallImport(uint32_t* importIndex, uint32_t* arity);
     MOZ_MUST_USE bool readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg);
     MOZ_MUST_USE bool readCallArgsEnd(uint32_t numArgs);
     MOZ_MUST_USE bool readCallIndirectCallee(Value* callee);
     MOZ_MUST_USE bool readCallReturn(ExprType ret);
-    MOZ_MUST_USE bool readAtomicLoad();
-    MOZ_MUST_USE bool readAtomicStore();
-    MOZ_MUST_USE bool readAtomicBinOp();
-    MOZ_MUST_USE bool readAtomicCompareExchange();
-    MOZ_MUST_USE bool readAtomicExchange();
-    MOZ_MUST_USE bool readSimdComparison(ValType simdType);
-    MOZ_MUST_USE bool readSimdShiftByScalar(ValType simdType);
-    MOZ_MUST_USE bool readSimdBooleanReduction(ValType simdType);
-    MOZ_MUST_USE bool readExtractLane(ValType simdType);
-    MOZ_MUST_USE bool readReplaceLane(ValType simdType);
-    MOZ_MUST_USE bool readSplat(ValType simdType);
-    MOZ_MUST_USE bool readSwizzle(ValType simdType);
-    MOZ_MUST_USE bool readShuffle(ValType simdType);
-    MOZ_MUST_USE bool readSimdSelect(ValType simdType);
+    MOZ_MUST_USE bool readAtomicLoad(LinearMemoryAddress<Value>* addr,
+                                     Scalar::Type* viewType);
+    MOZ_MUST_USE bool readAtomicStore(LinearMemoryAddress<Value>* addr,
+                                      Scalar::Type* viewType,
+                                      Value* value);
+    MOZ_MUST_USE bool readAtomicBinOp(LinearMemoryAddress<Value>* addr,
+                                      Scalar::Type* viewType,
+                                      jit::AtomicOp* op,
+                                      Value* value);
+    MOZ_MUST_USE bool readAtomicCompareExchange(LinearMemoryAddress<Value>* addr,
+                                                Scalar::Type* viewType,
+                                                Value* oldValue,
+                                                Value* newValue);
+    MOZ_MUST_USE bool readAtomicExchange(LinearMemoryAddress<Value>* addr,
+                                         Scalar::Type* viewType,
+                                         Value* newValue);
+    MOZ_MUST_USE bool readSimdComparison(ValType simdType, Value* lhs,
+                                         Value* rhs);
+    MOZ_MUST_USE bool readSimdShiftByScalar(ValType simdType, Value* lhs,
+                                            Value* rhs);
+    MOZ_MUST_USE bool readSimdBooleanReduction(ValType simdType, Value* input);
+    MOZ_MUST_USE bool readExtractLane(ValType simdType, jit::SimdLane* lane,
+                                      Value* vector);
+    MOZ_MUST_USE bool readReplaceLane(ValType simdType, jit::SimdLane* lane,
+                                      Value* vector, Value* scalar);
+    MOZ_MUST_USE bool readSplat(ValType simdType, Value* scalar);
+    MOZ_MUST_USE bool readSwizzle(ValType simdType, uint8_t (* lanes)[4], Value* vector);
+    MOZ_MUST_USE bool readShuffle(ValType simdType, uint8_t (* lanes)[4],
+                                  Value* lhs, Value* rhs);
+    MOZ_MUST_USE bool readSimdSelect(ValType simdType, Value* trueValue,
+                                     Value* falseValue,
+                                     Value* condition);
     MOZ_MUST_USE bool readSimdCtor();
     MOZ_MUST_USE bool readSimdCtorArg(ValType elementType, uint32_t numElements, uint32_t argIndex, Value* arg);
     MOZ_MUST_USE bool readSimdCtorArgsEnd(uint32_t numElements);
     MOZ_MUST_USE bool readSimdCtorReturn(ValType simdType);
 
     // ------------------------------------------------------------------------
-    // Translation interface. These methods provide the information obtained
-    // through decoding.
-
-    int32_t                     i32()     const { MOZ_ASSERT(isI32());     return u_.i32; }
-    int64_t                     i64()     const { MOZ_ASSERT(isI64());     return u_.i64; }
-    float                       f32()     const { MOZ_ASSERT(isF32());     return u_.f32; }
-    double                      f64()     const { MOZ_ASSERT(isF64());     return u_.f64; }
-    const I32x4&                i32x4()   const { MOZ_ASSERT(isI32x4());   return u_.i32x4; }
-    const F32x4&                f32x4()   const { MOZ_ASSERT(isF32x4());   return u_.f32x4; }
-    const BrRecord<Value>&      br()      const { MOZ_ASSERT(isBr());      return u_.br; }
-    const BrIfRecord<Value>&    brIf()    const { MOZ_ASSERT(isBrIf());    return u_.brIf; }
-    const BrTableRecord<Value>& brTable() const { MOZ_ASSERT(isBrTable()); return u_.brTable; }
-    const UnaryRecord<Value>&   unary()   const { MOZ_ASSERT(isUnary());   return u_.unary; }
-    const BinaryRecord<Value>&  binary()  const { MOZ_ASSERT(isBinary());  return u_.binary; }
-    const LoadRecord<Value>&    load()    const { MOZ_ASSERT(isLoad());    return u_.load; }
-    const StoreRecord<Value>&   store()   const { MOZ_ASSERT(isStore());   return u_.store; }
-    const SelectRecord<Value>&  select()  const { MOZ_ASSERT(isSelect());  return u_.select; }
-    const GetVarRecord&         getVar()  const { MOZ_ASSERT(isGetVar());  return u_.getVar; }
-    const SetVarRecord<Value>&  setVar()  const { MOZ_ASSERT(isSetVar());  return u_.setVar; }
-    const CallRecord&           call()    const { MOZ_ASSERT(isCall());    return u_.call; }
-    const CallIndirectRecord<Value>& callIndirect() const
-    { MOZ_ASSERT(isCallIndirect());          return u_.callIndirect; }
-    const CallImportRecord&     callImport() const
-    { MOZ_ASSERT(isCallImport());            return u_.callImport; }
-    const ReturnRecord<Value>&  return_() const { MOZ_ASSERT(isReturn());  return u_.return_; }
-    const IfRecord<Value>&      if_()     const { MOZ_ASSERT(isIf());      return u_.if_; }
-    const ElseRecord<Value>&    else_()   const { MOZ_ASSERT(isElse());    return u_.else_; }
-    const EndRecord<Value>&     end()     const { MOZ_ASSERT(isEnd());     return u_.end; }
-    const AtomicLoadRecord<Value>& atomicLoad() const
-    { MOZ_ASSERT(isAtomicLoad());            return u_.atomicLoad; }
-    const AtomicStoreRecord<Value>& atomicStore() const
-    { MOZ_ASSERT(isAtomicStore());           return u_.atomicStore; }
-    const AtomicBinOpRecord<Value>& atomicBinOp() const
-    { MOZ_ASSERT(isAtomicBinOp());           return u_.atomicBinOp; }
-    const AtomicCompareExchangeRecord<Value>& atomicCompareExchange() const
-    { MOZ_ASSERT(isAtomicCompareExchange()); return u_.atomicCompareExchange; }
-    const AtomicExchangeRecord<Value>& atomicExchange() const
-    { MOZ_ASSERT(isAtomicExchange());        return u_.atomicExchange; }
-    const ExtractLaneRecord<Value>& extractLane() const
-    { MOZ_ASSERT(isExtractLane()); return u_.extractLane; }
-    const ReplaceLaneRecord<Value>& replaceLane() const
-    { MOZ_ASSERT(isReplaceLane()); return u_.replaceLane; }
-    const SwizzleRecord<Value>& swizzle() const
-    { MOZ_ASSERT(isSwizzle()); return u_.swizzle; }
-    const ShuffleRecord<Value>& shuffle() const
-    { MOZ_ASSERT(isShuffle()); return u_.shuffle; }
-    const SimdSelectRecord<Value>& simdSelect() const
-    { MOZ_ASSERT(isSimdSelect()); return u_.simdSelect; }
-
-    // ------------------------------------------------------------------------
     // Stack management.
 
     // Set the result value of the current top-of-value-stack expression.
     void setResult(Value value) {
         valueStack_.back().setValue(value);
     }
 
     // Return the result value of the current top-of-value-stack expression.
@@ -1108,77 +584,69 @@ ExprIter<Policy>::readExpr(Expr* expr)
 {
     if (Validate) {
         if (MOZ_UNLIKELY(!d_.readExpr(expr)))
             return fail("unable to read opcode");
     } else {
         *expr = d_.uncheckedReadExpr();
     }
 
-#ifdef DEBUG
     expr_ = *expr;
-    mozWritePoison(&u_, sizeof(u_));
-    isInitialized_ = false;
-#endif
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readFunctionStart()
 {
     MOZ_ASSERT(valueStack_.empty());
     MOZ_ASSERT(controlStack_.empty());
-    MOZ_ASSERT(expr_ == Expr::Limit);
+    MOZ_ASSERT(Expr(expr_) == Expr::Limit);
 
     return pushControl(LabelKind::Block);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readFunctionEnd(ExprType ret)
+ExprIter<Policy>::readFunctionEnd(ExprType ret, Value* value)
 {
-#ifdef DEBUG
     expr_ = Expr::Limit;
-    mozWritePoison(&u_, sizeof(u_));
-#endif
 
     if (Validate) {
         MOZ_ASSERT(controlStack_.length() > 0);
         if (controlStack_.length() != 1)
             return fail("unbalanced function body control flow");
     } else {
         MOZ_ASSERT(controlStack_.length() == 1);
     }
 
     ExprType type;
-    Value value;
     LabelKind kind;
-    if (!popControlAfterCheck(&kind, &type, &value))
+    if (!popControlAfterCheck(&kind, &type, value))
         return false;
 
     MOZ_ASSERT(kind == LabelKind::Block);
     MOZ_ASSERT(valueStack_.length() == 1);
 
     if (!IsVoid(ret)) {
         if (!checkType(type, ret))
             return false;
     }
 
-    u_.return_ = ReturnRecord<Value>(value);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readReturn()
+ExprIter<Policy>::readReturn(Value* value)
 {
     ControlStackEntry<ControlItem>& controlItem = controlStack_[0];
     MOZ_ASSERT(controlItem.kind() == LabelKind::Block);
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Return);
 
     uint32_t arity;
     if (!readVarU32(&arity))
         return fail("failed to read return arity");
     if (arity > 1)
         return fail("return arity too big");
 
     TypeAndValue<Value> tv;
@@ -1189,18 +657,20 @@ ExprIter<Policy>::readReturn()
         tv = TypeAndValue<Value>(ExprType::Void);
     }
 
     infallibleCheckSuccessor(controlItem, tv.type());
 
     if (!push(AnyType))
         return false;
 
-    u_.return_ = ReturnRecord<Value>(tv.value());
-    return setInitialized();
+    if (Output)
+        *value = tv.value();
+
+    return true;
 }
 
 template <typename Policy>
 inline void
 ExprIter<Policy>::infallibleCheckSuccessor(ControlStackEntry<ControlItem>& controlItem,
                                      ExprType type)
 {
     controlItem.type() = Unify(controlItem.type(), type);
@@ -1250,17 +720,19 @@ ExprIter<Policy>::popControl(LabelKind* 
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::popControlAfterCheck(LabelKind* kind, ExprType* type, Value* value)
 {
     TypeAndValue<Value> tv;
     if (!pop(&tv))
         return false;
-    *value = tv.value();
+
+    if (Output)
+        *value = tv.value();
 
     ControlStackEntry<ControlItem> controlItem = controlStack_.popCopy();
     *kind = controlItem.kind();
 
     infallibleCheckSuccessor(controlItem, tv.type());
 
     *type = controlItem.type();
 
@@ -1270,181 +742,208 @@ ExprIter<Policy>::popControlAfterCheck(L
     infalliblePush(controlItem.type());
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBlock()
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Block);
+
     return pushControl(LabelKind::Block);
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readLoop()
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Loop);
+
     return pushControl(LabelKind::Block) &&
            pushControl(LabelKind::Loop);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readIf()
+ExprIter<Policy>::readIf(Value* condition)
 {
-    Value condition;
-    if (!popWithType(ExprType::I32, &condition))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::If);
+
+    if (!popWithType(ExprType::I32, condition))
         return false;
 
-    u_.if_ = IfRecord<Value>(condition);
-
-    return setInitialized() &&
-           pushControl(LabelKind::Then);
+    return pushControl(LabelKind::Then);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readElse()
+ExprIter<Policy>::readElse(ExprType* thenType, Value* thenValue)
 {
-    ExprType thenType;
-    Value thenValue;
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Else);
+
+    ExprType type;
     LabelKind kind;
-    if (!popControl(&kind, &thenType, &thenValue))
+    if (!popControl(&kind, &type, thenValue))
         return false;
 
     if (Validate && kind != LabelKind::Then)
         return fail("else can only be used within an if");
 
     // Pop and discard the old then value for now.
     TypeAndValue<Value> tv;
     if (!pop(&tv))
         return false;
 
     if (!pushControl(LabelKind::Else))
         return false;
 
     // Initialize the else block's type with the then block's type, so that
     // the two get unified.
     ControlStackEntry<ControlItem>& controlItem = controlStack_.back();
-    controlItem.type() = thenType;
+    controlItem.type() = type;
 
-    u_.else_ = ElseRecord<Value>(thenType, thenValue);
-    return setInitialized();
+    if (Output)
+        *thenType = type;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readEnd()
+ExprIter<Policy>::readEnd(LabelKind* kind, ExprType* type, Value* value)
 {
-    ExprType type;
-    Value value;
-    LabelKind kind;
-    if (!popControl(&kind, &type, &value))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::End);
+
+    LabelKind validateKind;
+    ExprType validateType;
+    if (!popControl(&validateKind, &validateType, value))
         return false;
 
-    switch (kind) {
+    switch (validateKind) {
       case LabelKind::Block:
         break;
       case LabelKind::Loop: {
         // Note: Propose a spec change: loops don't implicitly have an end label.
 
-        setResult(value);
+        if (Output)
+            setResult(*value);
 
         LabelKind blockKind;
-        if (!popControl(&blockKind, &type, &value))
+        if (!popControl(&blockKind, &validateType, value))
             return false;
 
         MOZ_ASSERT(blockKind == LabelKind::Block);
         break;
       }
       case LabelKind::Then:
         valueStack_.back() = TypeAndValue<Value>(ExprType::Void);
-        type = ExprType::Void;
+        if (Output)
+            *type = ExprType::Void;
         break;
       case LabelKind::Else:
         break;
     }
 
-    u_.end = EndRecord<Value>(kind, type, value);
-    return setInitialized();
+    if (Output) {
+        *kind = validateKind;
+        *type = validateType;
+    }
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readBr()
+ExprIter<Policy>::readBr(uint32_t* relativeDepth, ExprType* type, Value* value)
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Br);
+
     uint32_t arity;
     if (!readVarU32(&arity))
         return fail("unable to read br arity");
     if (arity > 1)
         return fail("br arity too big");
 
-    uint32_t relativeDepth;
-    if (!readVarU32(&relativeDepth))
+    uint32_t validateRelativeDepth;
+    if (!readVarU32(&validateRelativeDepth))
         return fail("unable to read br depth");
 
     TypeAndValue<Value> tv;
     if (arity) {
         if (!pop(&tv))
             return false;
     } else {
         tv = TypeAndValue<Value>(ExprType::Void);
     }
 
-    if (!checkBranch(relativeDepth, tv.type()))
+    if (!checkBranch(validateRelativeDepth, tv.type()))
         return false;
 
     if (!push(AnyType))
         return false;
 
-    u_.br = BrRecord<Value>(relativeDepth, tv.type(), tv.value());
-    return setInitialized();
+    if (Output) {
+        *relativeDepth = validateRelativeDepth;
+        *type = tv.type();
+        *value = tv.value();
+    }
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readBrIf()
+ExprIter<Policy>::readBrIf(uint32_t* relativeDepth, ExprType* type, Value* value, Value* condition)
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::BrIf);
+
     uint32_t arity;
     if (!readVarU32(&arity))
         return fail("unable to read br_if arity");
     if (arity > 1)
         return fail("br_if arity too big");
 
-    uint32_t relativeDepth;
-    if (!readVarU32(&relativeDepth))
+    uint32_t validateRelativeDepth;
+    if (!readVarU32(&validateRelativeDepth))
         return fail("unable to read br_if depth");
 
-    Value condition;
-    if (!popWithType(ExprType::I32, &condition))
+    if (!popWithType(ExprType::I32, condition))
         return false;
 
     TypeAndValue<Value> tv;
     if (arity) {
         if (!top(&tv))
             return false;
     } else {
         tv = TypeAndValue<Value>(ExprType::Void);
         if (!push(tv))
             return false;
     }
 
-    if (!checkBranch(relativeDepth, tv.type()))
+    if (!checkBranch(validateRelativeDepth, tv.type()))
         return false;
 
-    u_.brIf = BrIfRecord<Value>(relativeDepth, tv.type(), tv.value(), condition);
-    return setInitialized();
+    if (Output) {
+        *relativeDepth = validateRelativeDepth;
+        *type = tv.type();
+        *value = tv.value();
+    }
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readBrTable()
+ExprIter<Policy>::readBrTable(uint32_t* tableLength, ExprType* type,
+                              Value* value, Value* index)
 {
-    Value index;
-    if (!popWithType(ExprType::I32, &index))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::BrTable);
+
+    if (!popWithType(ExprType::I32, index))
         return false;
 
     uint32_t arity;
     if (!readVarU32(&arity))
         return fail("unable to read br_table arity");
     if (arity > 1)
         return fail("br_table arity too big");
 
@@ -1452,400 +951,449 @@ ExprIter<Policy>::readBrTable()
     if (arity) {
         if (!top(&tv))
             return false;
     } else {
         tv = TypeAndValue<Value>(ExprType::Void);
         if (!push(tv))
             return false;
     }
+    *type = tv.type();
+    if (Output)
+        *value = tv.value();
 
-    uint32_t tableLength;
-    if (!readVarU32(&tableLength))
+    if (!readVarU32(tableLength))
         return fail("unable to read br_table table length");
 
-    u_.brTable = BrTableRecord<Value>(tableLength, tv.type(), tv.value(), index);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readBrTableEntry(ExprType type, uint32_t* depth)
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::BrTable);
+
     return readFixedU32(depth) &&
            checkBranch(*depth, type);
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readUnreachable()
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Unreachable);
+
     return push(AnyType);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readUnary(ValType operandType)
+ExprIter<Policy>::readUnary(ValType operandType, Value* input)
 {
-    Value op;
-    if (!popWithType(ToExprType(operandType), &op))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Unary);
+
+    if (!popWithType(ToExprType(operandType), input))
         return false;
 
     infalliblePush(ToExprType(operandType));
 
-    u_.unary = UnaryRecord<Value>(op);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readConversion(ValType operandType, ValType resultType)
+ExprIter<Policy>::readConversion(ValType operandType, ValType resultType, Value* input)
 {
-    Value op;
-    if (!popWithType(ToExprType(operandType), &op))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Conversion);
+
+    if (!popWithType(ToExprType(operandType), input))
         return false;
 
     infalliblePush(ToExprType(resultType));
 
-    u_.unary = UnaryRecord<Value>(op);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readBinary(ValType operandType)
+ExprIter<Policy>::readBinary(ValType operandType, Value* lhs, Value* rhs)
 {
-    Value rhs;
-    if (!popWithType(ToExprType(operandType), &rhs))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Binary);
+
+    if (!popWithType(ToExprType(operandType), rhs))
         return false;
 
-    Value lhs;
-    if (!popWithType(ToExprType(operandType), &lhs))
+    if (!popWithType(ToExprType(operandType), lhs))
         return false;
 
     infalliblePush(ToExprType(operandType));
 
-    u_.binary = BinaryRecord<Value>(lhs, rhs);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readComparison(ValType operandType)
+ExprIter<Policy>::readComparison(ValType operandType, Value* lhs, Value* rhs)
 {
-    Value rhs;
-    if (!popWithType(ToExprType(operandType), &rhs))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Comparison);
+
+    if (!popWithType(ToExprType(operandType), rhs))
         return false;
 
-    Value lhs;
-    if (!popWithType(ToExprType(operandType), &lhs))
+    if (!popWithType(ToExprType(operandType), lhs))
         return false;
 
     infalliblePush(ExprType::I32);
 
-    u_.binary = BinaryRecord<Value>(lhs, rhs);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr)
 {
-    Value base;
-    if (!popWithType(ExprType::I32, &base))
+    Value unused;
+    if (!popWithType(ExprType::I32, Output ? &addr->base : &unused))
         return false;
 
     uint8_t alignLog2;
     if (!readFixedU8(&alignLog2))
         return fail("unable to read load alignment");
     if (Validate && (alignLog2 >= 32 || (uint32_t(1) << alignLog2) > byteSize))
         return fail("greater than natural alignment");
-    uint32_t align = uint32_t(1) << alignLog2;
+    if (Output)
+        addr->align = uint32_t(1) << alignLog2;
 
-    uint32_t offset;
-    if (!readVarU32(&offset))
+    uint32_t unusedOffset;
+    if (!readVarU32(Output ? &addr->offset : &unusedOffset))
         return fail("unable to read load offset");
 
-    *addr = LinearMemoryAddress<Value>(base, offset, align);
     return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readLoad(ValType resultType, uint32_t byteSize)
+ExprIter<Policy>::readLoad(ValType resultType, uint32_t byteSize,
+                           LinearMemoryAddress<Value>* addr)
 {
-    if (!readLinearMemoryAddress(byteSize, &u_.load.addr))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Load);
+
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
     infalliblePush(ToExprType(resultType));
-    return setInitialized();
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readStore(ValType resultType, uint32_t byteSize)
+ExprIter<Policy>::readStore(ValType resultType, uint32_t byteSize,
+                            LinearMemoryAddress<Value>* addr, Value* value)
 {
-    Value value;
-    if (!popWithType(ToExprType(resultType), &value))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Store);
+
+    if (!popWithType(ToExprType(resultType), value))
         return false;
 
-    if (!readLinearMemoryAddress(byteSize, &u_.store.addr))
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    infalliblePush(TypeAndValue<Value>(ToExprType(resultType), value));
+    infalliblePush(TypeAndValue<Value>(ToExprType(resultType), Output ? *value : Value()));
 
-    u_.store.value = value;
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readTrivial()
+ExprIter<Policy>::readNullary()
 {
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Nullary);
+
     return push(ExprType::Void);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readSelect()
+ExprIter<Policy>::readSelect(ExprType* type, Value* trueValue, Value* falseValue, Value* condition)
 {
-    Value condition;
-    if (!popWithType(ExprType::I32, &condition))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Select);
+
+    if (!popWithType(ExprType::I32, condition))
         return false;
 
-    TypeAndValue<Value> falseValue;
-    if (!pop(&falseValue))
+    TypeAndValue<Value> false_;
+    if (!pop(&false_))
         return false;
 
-    TypeAndValue<Value> trueValue;
-    if (!pop(&trueValue))
+    TypeAndValue<Value> true_;
+    if (!pop(&true_))
         return false;
 
-    ExprType resultType = Unify(trueValue.type(), falseValue.type());
+    ExprType resultType = Unify(true_.type(), false_.type());
     infalliblePush(resultType);
 
-    u_.select = SelectRecord<Value>(resultType, trueValue.value(), falseValue.value(),
-                                    condition);
-    return setInitialized();
+    if (Output) {
+        *type = resultType;
+        *trueValue = true_.value();
+        *falseValue = false_.value();
+    }
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readGetLocal(const ValTypeVector& locals)
+ExprIter<Policy>::readGetLocal(const ValTypeVector& locals, uint32_t* id)
 {
-    uint32_t id;
-    if (!readVarU32(&id))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::GetVar);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
         return false;
 
-    if (Validate && id >= locals.length())
+    if (Validate && validateId >= locals.length())
         return fail("get_local index out of range");
 
-    if (!push(ToExprType(locals[id])))
+    if (!push(ToExprType(locals[validateId])))
         return false;
 
-    u_.getVar = GetVarRecord(id);
-    return setInitialized();
+    if (Output)
+        *id = validateId;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readSetLocal(const ValTypeVector& locals)
+ExprIter<Policy>::readSetLocal(const ValTypeVector& locals, uint32_t* id, Value* value)
 {
-    uint32_t id;
-    if (!readVarU32(&id))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::SetVar);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
         return false;
 
-    if (Validate && id >= locals.length())
+    if (Validate && validateId >= locals.length())
         return fail("set_local index out of range");
 
-    Value value;
-    if (!topWithType(ToExprType(locals[id]), &value))
+    if (!topWithType(ToExprType(locals[validateId]), value))
         return false;
 
-    u_.setVar = SetVarRecord<Value>(id, value);
-    return setInitialized();
+    if (Output)
+        *id = validateId;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readGetGlobal(const GlobalDescVector& globals)
+ExprIter<Policy>::readGetGlobal(const GlobalDescVector& globals, uint32_t* id)
 {
-    uint32_t id;
-    if (!readVarU32(&id))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::GetVar);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
         return false;
 
-    if (!push(ToExprType(globals[id].type)))
+    if (Validate && validateId >= globals.length())
+        return fail("get_global index out of range");
+
+    if (!push(ToExprType(globals[validateId].type)))
         return false;
 
-    u_.getVar = GetVarRecord(id);
-    return setInitialized();
+    if (Output)
+        *id = validateId;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readSetGlobal(const GlobalDescVector& globals)
+ExprIter<Policy>::readSetGlobal(const GlobalDescVector& globals, uint32_t* id, Value* value)
 {
-    uint32_t id;
-    if (!readVarU32(&id))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::SetVar);
+
+    uint32_t validateId;
+    if (!readVarU32(&validateId))
         return false;
 
-    Value value;
-    if (!topWithType(ToExprType(globals[id].type), &value))
+    if (Validate && validateId >= globals.length())
+        return fail("set_global index out of range");
+
+    if (!topWithType(ToExprType(globals[validateId].type), value))
         return false;
 
-    u_.setVar = SetVarRecord<Value>(id, value);
-    return setInitialized();
+    if (Output)
+        *id = validateId;
+
+    return true;
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readI32Const()
+ExprIter<Policy>::readI32Const(int32_t* i32)
 {
-    return readVarS32(&u_.i32) &&
-           setInitialized() &&
+    MOZ_ASSERT(Classify(expr_) == ExprKind::I32);
+
+    int32_t unused;
+    return readVarS32(Output ? i32 : &unused) &&
            push(ExprType::I32);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readI64Const()
+ExprIter<Policy>::readI64Const(int64_t* i64)
 {
-    return readVarS64(&u_.i64) &&
-           setInitialized() &&
+    MOZ_ASSERT(Classify(expr_) == ExprKind::I64);
+
+    int64_t unused;
+    return readVarS64(Output ? i64 : &unused) &&
            push(ExprType::I64);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readF32Const()
+ExprIter<Policy>::readF32Const(float* f32)
 {
-    if (!readFixedF32(&u_.f32))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::F32);
+
+    float validateF32;
+    if (!readFixedF32(Output ? f32 : &validateF32))
         return false;
 
-    if (Validate && mozilla::IsNaN(u_.f32)) {
+    if (Validate && mozilla::IsNaN(Output ? *f32 : validateF32)) {
         const float jsNaN = (float)JS::GenericNaN();
-        if (memcmp(&u_.f32, &jsNaN, sizeof(u_.f32)) != 0)
+        if (memcmp(Output ? f32 : &validateF32, &jsNaN, sizeof(*f32)) != 0)
             return notYetImplemented("NaN literals with custom payloads");
     }
 
-    return setInitialized() &&
-           push(ExprType::F32);
+    return push(ExprType::F32);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readF64Const()
+ExprIter<Policy>::readF64Const(double* f64)
 {
-    if (!readFixedF64(&u_.f64))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::F64);
+
+    double validateF64;
+    if (!readFixedF64(Output ? f64 : &validateF64))
        return false;
 
-    if (Validate && mozilla::IsNaN(u_.f64)) {
+    if (Validate && mozilla::IsNaN(Output ? *f64 : validateF64)) {
         const double jsNaN = JS::GenericNaN();
-        if (memcmp(&u_.f64, &jsNaN, sizeof(u_.f64)) != 0)
+        if (memcmp(Output ? f64 : &validateF64, &jsNaN, sizeof(*f64)) != 0)
             return notYetImplemented("NaN literals with custom payloads");
     }
 
-    return setInitialized() &&
-           push(ExprType::F64);
+    return push(ExprType::F64);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readI32x4Const()
+ExprIter<Policy>::readI32x4Const(I32x4* i32x4)
 {
-    return readFixedI32x4(&u_.i32x4) &&
-           setInitialized() &&
+    MOZ_ASSERT(Classify(expr_) == ExprKind::I32x4);
+
+    I32x4 unused;
+    return readFixedI32x4(Output ? i32x4 : &unused) &&
            push(ExprType::I32x4);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readF32x4Const()
+ExprIter<Policy>::readF32x4Const(F32x4* f32x4)
 {
-    return readFixedF32x4(&u_.f32x4) &&
-           setInitialized() &&
+    MOZ_ASSERT(Classify(expr_) == ExprKind::F32x4);
+
+    F32x4 unused;
+    return readFixedF32x4(Output ? f32x4 : &unused) &&
            push(ExprType::F32x4);
 }
 
 template <typename Policy>
 inline MOZ_MUST_USE bool
-ExprIter<Policy>::readB32x4Const()
+ExprIter<Policy>::readB32x4Const(I32x4* i32x4)
 {
-    return readFixedI32x4(&u_.i32x4) &&
-           setInitialized() &&
+    MOZ_ASSERT(Classify(expr_) == ExprKind::B32x4);
+
+    I32x4 unused;
+    return readFixedI32x4(Output ? i32x4 : &unused) &&
            push(ExprType::B32x4);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCall()
+ExprIter<Policy>::readCall(uint32_t* calleeIndex, uint32_t* arity)
 {
-    uint32_t arity;
-    if (!readVarU32(&arity))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::Call);
+
+    if (!readVarU32(arity))
         return fail("unable to read call arity");
 
-    uint32_t funcIndex;
-    if (!readVarU32(&funcIndex))
+    if (!readVarU32(calleeIndex))
         return fail("unable to read call function index");
 
-    u_.call = CallRecord(arity, funcIndex);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCallIndirect()
+ExprIter<Policy>::readCallIndirect(uint32_t* sigIndex, uint32_t* arity)
 {
-    uint32_t arity;
-    if (!readVarU32(&arity))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::CallIndirect);
+
+    if (!readVarU32(arity))
         return fail("unable to read call_indirect arity");
 
-    uint32_t sigIndex;
-    if (!readVarU32(&sigIndex))
+    if (!readVarU32(sigIndex))
         return fail("unable to read call_indirect signature index");
 
-    u_.callIndirect = CallIndirectRecord<Value>(arity, sigIndex);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readCallImport()
+ExprIter<Policy>::readCallImport(uint32_t* importIndex, uint32_t* arity)
 {
-    uint32_t arity;
-    if (!readVarU32(&arity))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::CallImport);
+
+    if (!readVarU32(arity))
         return fail("unable to read call_import arity");
 
-    uint32_t importIndex;
-    if (!readVarU32(&importIndex))
+    if (!readVarU32(importIndex))
         return fail("unable to read call_import import index");
 
-    u_.callImport = CallImportRecord(arity, importIndex);
-    return setInitialized();
+    return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallArg(ValType type, uint32_t numArgs, uint32_t argIndex, Value* arg)
 {
     TypeAndValue<Value> tv;
     if (!peek(numArgs - argIndex, &tv))
         return false;
     if (!checkType(tv.type(), ToExprType(type)))
         return false;
 
-    *arg = tv.value();
+    if (Output)
+        *arg = tv.value();
+
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallArgsEnd(uint32_t numArgs)
 {
     MOZ_ASSERT(numArgs <= valueStack_.length());
+
     valueStack_.shrinkBy(numArgs);
     return true;
 }
 
 template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallIndirectCallee(Value* callee)
 {
@@ -1856,350 +1404,372 @@ template <typename Policy>
 inline bool
 ExprIter<Policy>::readCallReturn(ExprType ret)
 {
     return push(ret);
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readAtomicLoad()
+ExprIter<Policy>::readAtomicLoad(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType)
 {
-    Scalar::Type viewType;
-    if (!readAtomicViewType(&viewType))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::AtomicLoad);
+
+    Scalar::Type validateViewType;
+    if (!readAtomicViewType(&validateViewType))
         return false;
 
-    uint32_t byteSize = Scalar::byteSize(viewType);
-    if (!readLinearMemoryAddress(byteSize, &u_.atomicLoad.addr))
+    uint32_t byteSize = Scalar::byteSize(validateViewType);
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
     infalliblePush(ExprType::I32);
 
-    u_.atomicLoad.viewType = viewType;
-    return setInitialized();
+    if (Output)
+        *viewType = validateViewType;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readAtomicStore()
+ExprIter<Policy>::readAtomicStore(LinearMemoryAddress<Value>* addr,
+                                  Scalar::Type* viewType, Value* value)
 {
-    Scalar::Type viewType;
-    if (!readAtomicViewType(&viewType))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::AtomicStore);
+
+    Scalar::Type validateViewType;
+    if (!readAtomicViewType(&validateViewType))
         return false;
 
-    uint32_t byteSize = Scalar::byteSize(viewType);
-    if (!readLinearMemoryAddress(byteSize, &u_.atomicStore.addr))
+    uint32_t byteSize = Scalar::byteSize(validateViewType);
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    Value value;
-    if (!popWithType(ExprType::I32, &value))
+    if (!popWithType(ExprType::I32, value))
         return false;
 
     infalliblePush(ExprType::I32);
 
-    u_.atomicStore.viewType = viewType;
-    u_.atomicStore.value = value;
-    return setInitialized();
+    if (Output)
+        *viewType = validateViewType;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readAtomicBinOp()
+ExprIter<Policy>::readAtomicBinOp(LinearMemoryAddress<Value>* addr, Scalar::Type* viewType,
+                                  jit::AtomicOp* op, Value* value)
 {
-    Scalar::Type viewType;
-    if (!readAtomicViewType(&viewType))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::AtomicBinOp);
+
+    Scalar::Type validateViewType;
+    if (!readAtomicViewType(&validateViewType))
         return false;
 
-    jit::AtomicOp op;
-    if (!readAtomicBinOpOp(&op))
+    if (!readAtomicBinOpOp(op))
         return false;
 
-    uint32_t byteSize = Scalar::byteSize(viewType);
-    if (!readLinearMemoryAddress(byteSize, &u_.atomicStore.addr))
+    uint32_t byteSize = Scalar::byteSize(validateViewType);
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    Value value;
-    if (!popWithType(ExprType::I32, &value))
+    if (!popWithType(ExprType::I32, value))
         return false;
 
     infalliblePush(ExprType::I32);
 
-    u_.atomicBinOp.viewType = viewType;
-    u_.atomicBinOp.op = op;
-    u_.atomicBinOp.value = value;
-    return setInitialized();
+    if (Output)
+        *viewType = validateViewType;
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
-ExprIter<Policy>::readAtomicCompareExchange()
+ExprIter<Policy>::readAtomicCompareExchange(LinearMemoryAddress<Value>* addr,
+                                            Scalar::Type* viewType,
+                                            Value* oldValue, Value* newValue)
 {
-    Scalar::Type viewType;
-    if (!readAtomicViewType(&viewType))
+    MOZ_ASSERT(Classify(expr_) == ExprKind::AtomicCompareExchange);
+
+    Scalar::Type validateViewType;
+    if (!readAtomicViewType(&validateViewType))
         return false;
 
-    uint32_t byteSize = Scalar::byteSize(viewType);
-    if (!readLinearMemoryAddress(byteSize, &u_.atomicStore.addr))
+    uint32_t byteSize = Scalar::byteSize(validateViewType);
+    if (!readLinearMemoryAddress(byteSize, addr))
         return false;
 
-    Value new_;
-    if (!popWithType(ExprType::I32, &new_))
+    if (!popWithType(ExprType::I32, newValue))
         return false;
 
-    Value old;
-    if (!popWithType(ExprType::I32, &old))
+    if (!popWithType(ExprType::I32, oldValue))
         return false;
 
     infalliblePush(ExprType::I32);
 
-    u_.atomicCompareExchange.viewType = viewType;
-    u_.atomicCompareExchange.oldValue = old;
-    u_.atomicCompareExchange.newValue = new_;
-    return setInitialized();
+    if (Output)
+        *viewType = validateViewType;
+
+    return true;
 }
 
 template <typename Policy>