Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 01 Dec 2014 12:42:09 +0100
changeset 234918 20091d55962c2966fd20536a09b2fe854ed7fa9e
parent 234917 27bf490c510f245dfec1fef1f4a82effd433b1ae (current diff)
parent 234895 08be3008650fbeb88777dea3e6034622196dc949 (diff)
child 234919 9986ec246b13dd6d3155ad4d2e851f7e944ead6a
push id7472
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 20:36:27 +0000
treeherdermozilla-aurora@300ca104f8fb [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone37.0a1
Merge mozilla-central to b2g-inbound
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -19,58 +19,63 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
     /**
      * Opens the panel for Loop and sizes it appropriately.
      *
      * @param {event}  event   The event opening the panel, used to anchor
      *                         the panel to the button which triggers it.
      * @param {String} [tabId] Identifier of the tab to select when the panel is
      *                         opened. Example: 'rooms', 'contacts', etc.
+     * @return {Promise}
      */
     openCallPanel: function(event, tabId = null) {
-      let callback = iframe => {
-        // Helper function to show a specific tab view in the panel.
-        function showTab() {
-          if (!tabId) {
+      return new Promise((resolve) => {
+        let callback = iframe => {
+          // Helper function to show a specific tab view in the panel.
+          function showTab() {
+            if (!tabId) {
+              resolve();
+              return;
+            }
+
+            let win = iframe.contentWindow;
+            let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
+              detail: {
+                action: "selectTab",
+                tab: tabId
+              }
+            }, win));
+            win.dispatchEvent(ev);
+            resolve();
+          }
+
+          // If the panel has been opened and initialized before, we can skip waiting
+          // for the content to load - because it's already there.
+          if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
+            showTab();
             return;
           }
 
-          let win = iframe.contentWindow;
-          let ev = new win.CustomEvent("UIAction", Cu.cloneInto({
-            detail: {
-              action: "selectTab",
-              tab: tabId
-            }
-          }, win));
-          win.dispatchEvent(ev);
-        }
-
-        // If the panel has been opened and initialized before, we can skip waiting
-        // for the content to load - because it's already there.
-        if (("contentWindow" in iframe) && iframe.contentWindow.document.readyState == "complete") {
-          showTab();
-          return;
-        }
+          iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
+            iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
+            injectLoopAPI(iframe.contentWindow);
+            iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
+              iframe.contentWindow.removeEventListener("loopPanelInitialized",
+                                                       loopPanelInitialized);
+              showTab();
+            });
+          }, true);
+        };
 
-        iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
-          iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
-          injectLoopAPI(iframe.contentWindow);
-          iframe.contentWindow.addEventListener("loopPanelInitialized", function loopPanelInitialized() {
-            iframe.contentWindow.removeEventListener("loopPanelInitialized",
-              loopPanelInitialized);
-            showTab();
-          });
-        }, true);
-      };
+        // Used to clear the temporary "login" state from the button.
+        Services.obs.notifyObservers(null, "loop-status-changed", null);
 
-      // Used to clear the temporary "login" state from the button.
-      Services.obs.notifyObservers(null, "loop-status-changed", null);
-
-      PanelFrame.showPopup(window, event ? event.target : this.toolbarButton.node,
-                           "loop", null, "about:looppanel", null, callback);
+        PanelFrame.showPopup(window, event ? event.target : this.toolbarButton.node,
+                             "loop", null, "about:looppanel", null, callback);
+      });
     },
 
     /**
      * Triggers the initialization of the loop service.  Called by
      * delayedStartup.
      */
     init: function() {
       // Add observer notifications before the service is initialized
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -59,17 +59,17 @@ function promiseGetMozLoopAPI() {
     // Remove the iframe after each test. This also avoids mochitest complaining
     // about leaks on shutdown as we intentionally hold the iframe open for the
     // life of the application.
     registerCleanupFunction(function() {
       loopPanel.hidePopup();
       let frameId = btn.getAttribute("notificationFrameId");
       let frame = document.getElementById(frameId);
       if (frame) {
-        loopPanel.removeChild(frame);
+        frame.remove();
       }
     });
   });
 }
 
 /**
  * Loads the loop panel by clicking the button and waits for its open to complete.
  * It also registers
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -106,25 +106,25 @@ this.UITour = {
       query: (aDocument) => {
         let customizeButton = aDocument.getElementById("PanelUI-customize");
         return aDocument.getAnonymousElementByAttribute(customizeButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
+    ["devtools",    {query: "#developer-button"}],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
-    ["loop",        {query: "#loop-button"}],
-    ["devtools",    {query: "#developer-button"}],
-    ["webide",      {query: "#webide-button"}],
     ["forget", {
       query: "#panic-button",
       widgetName: "panic-button",
-      allowAdd: true }],
+      allowAdd: true,
+    }],
+    ["loop",        {query: "#loop-button"}],
     ["privateWindow",  {query: "#privatebrowsing-button"}],
     ["quit",        {query: "#PanelUI-quit"}],
     ["search",      {
       query: "#searchbar",
       widgetName: "search-container",
     }],
     ["searchProvider", {
       query: (aDocument) => {
@@ -183,16 +183,17 @@ this.UITour = {
         }
         return element;
       },
     }],
     ["urlbar",      {
       query: "#urlbar",
       widgetName: "urlbar-container",
     }],
+    ["webide",      {query: "#webide-button"}],
   ]),
 
   init: function() {
     log.debug("Initializing UITour");
     // Lazy getter is initialized here so it can be replicated any time
     // in a test.
     delete this.seenPageIDs;
     Object.defineProperty(this, "seenPageIDs", {
@@ -709,36 +710,41 @@ this.UITour = {
     return {
       seenPageIDs: [...this.seenPageIDs.keys()],
     };
   },
 
   teardownTour: function(aWindow, aWindowClosing = false) {
     log.debug("teardownTour: aWindowClosing = " + aWindowClosing);
     aWindow.gBrowser.tabContainer.removeEventListener("TabSelect", this);
-    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
-    aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
     aWindow.removeEventListener("SSWindowClosing", this);
 
     let originTabs = this.originTabs.get(aWindow);
     if (originTabs) {
       for (let tab of originTabs) {
         tab.removeEventListener("TabClose", this);
         tab.removeEventListener("TabBecomingWindow", this);
       }
     }
     this.originTabs.delete(aWindow);
 
     if (!aWindowClosing) {
       this.hideHighlight(aWindow);
       this.hideInfo(aWindow);
       // Ensure the menu panel is hidden before calling recreatePopup so popup events occur.
       this.hideMenu(aWindow, "appMenu");
+      this.hideMenu(aWindow, "loop");
     }
 
+    // Clean up panel listeners after we may have called hideMenu above.
+    aWindow.PanelUI.panel.removeEventListener("popuphiding", this.hidePanelAnnotations);
+    aWindow.PanelUI.panel.removeEventListener("ViewShowing", this.hidePanelAnnotations);
+    let loopPanel = aWindow.document.getElementById("loop-notification-panel");
+    loopPanel.removeEventListener("popuphidden", this.onLoopPanelHidden);
+
     this.endUrlbarCapture(aWindow);
     this.removePinnedTab(aWindow);
     this.resetTheme();
   },
 
   getChromeWindow: function(aContentDocument) {
     return aContentDocument.defaultView
                            .window
@@ -862,17 +868,17 @@ this.UITour = {
   },
 
   /**
    * Called before opening or after closing a highlight or info panel to see if
    * we need to open or close the appMenu to see the annotation's anchor.
    */
   _setAppMenuStateForAnnotation: function(aWindow, aAnnotationType, aShouldOpenForHighlight, aCallback = null) {
     log.debug("_setAppMenuStateForAnnotation:", aAnnotationType);
-    log.debug("_setAppMenuStateForAnnotation: Menu is exptected to be:", aShouldOpenForHighlight ? "open" : "closed");
+    log.debug("_setAppMenuStateForAnnotation: Menu is expected to be:", aShouldOpenForHighlight ? "open" : "closed");
 
     // If the panel is in the desired state, we're done.
     let panelIsOpen = aWindow.PanelUI.panel.state != "closed";
     if (aShouldOpenForHighlight == panelIsOpen) {
       log.debug("_setAppMenuStateForAnnotation: Panel already in expected state");
       if (aCallback)
         aCallback();
       return;
@@ -1233,16 +1239,36 @@ this.UITour = {
       aWindow.PanelUI.panel.addEventListener("ViewShowing", this.hidePanelAnnotations);
       if (aOpenCallback) {
         aWindow.PanelUI.panel.addEventListener("popupshown", onPopupShown);
       }
       aWindow.PanelUI.show();
     } else if (aMenuName == "bookmarks") {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       openMenuButton(menuBtn);
+    } else if (aMenuName == "loop") {
+      let toolbarButton = aWindow.LoopUI.toolbarButton;
+      if (!toolbarButton || !toolbarButton.node) {
+        return;
+      }
+
+      let panel = aWindow.document.getElementById("loop-notification-panel");
+      panel.setAttribute("noautohide", true);
+      if (panel.state != "open") {
+        this.recreatePopup(panel);
+      }
+
+      // An event object is expected but we don't want to toggle the panel with a click if the panel
+      // is already open.
+      aWindow.LoopUI.openCallPanel({ target: toolbarButton.node, }).then(() => {
+        if (aOpenCallback) {
+          aOpenCallback();
+        }
+      });
+      panel.addEventListener("popuphidden", this.onLoopPanelHidden);
     } else if (aMenuName == "searchEngines") {
       this.getTarget(aWindow, "searchProvider").then(target => {
         openMenuButton(target.node);
       }).catch(log.error);
     }
   },
 
   hideMenu: function(aWindow, aMenuName) {
@@ -1253,16 +1279,19 @@ this.UITour = {
 
     if (aMenuName == "appMenu") {
       aWindow.PanelUI.panel.removeAttribute("noautohide");
       aWindow.PanelUI.hide();
       this.recreatePopup(aWindow.PanelUI.panel);
     } else if (aMenuName == "bookmarks") {
       let menuBtn = aWindow.document.getElementById("bookmarks-menu-button");
       closeMenuButton(menuBtn);
+    } else if (aMenuName == "loop") {
+      let panel = aWindow.document.getElementById("loop-notification-panel");
+      panel.hidePopup();
     } else if (aMenuName == "searchEngines") {
       let menuBtn = this.targets.get("searchProvider").query(aWindow.document);
       closeMenuButton(menuBtn);
     }
   },
 
   hidePanelAnnotations: function(aEvent) {
     let win = aEvent.target.ownerDocument.defaultView;
@@ -1284,16 +1313,21 @@ this.UITour = {
           }
           hideMethod(win);
         }).catch(log.error);
       }
     });
     UITour.appMenuOpenForAnnotation.clear();
   },
 
+  onLoopPanelHidden: function(aEvent) {
+    aEvent.target.removeAttribute("noautohide");
+    UITour.recreatePopup(aEvent.target);
+  },
+
   recreatePopup: function(aPanel) {
     // After changing popup attributes that relate to how the native widget is created
     // (e.g. @noautohide) we need to re-create the frame/widget for it to take effect.
     if (aPanel.hidden) {
       // If the panel is already hidden, we don't need to recreate it but flush
       // in case someone just hid it.
       aPanel.clientWidth; // flush
       return;
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -23,16 +23,18 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 [browser_UITour3.js]
 skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
+[browser_UITour_loop.js]
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_modalDialog.js]
 run-if = os == "mac" # modal dialog disabling only working on OS X
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_panel_close_annotation.js]
 skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
 [browser_UITour_registerPageID.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_sync.js]
--- a/browser/modules/test/browser_UITour2.js
+++ b/browser/modules/test/browser_UITour2.js
@@ -84,17 +84,17 @@ let tests = [
     tabInfo = UITour.pinnedTabs.get(window);
     is(tabInfo, null, "Should not have any data about the removed pinned tab after removePinnedTab()");
 
     yield addPinnedTabPromise();
     yield addPinnedTabPromise();
     yield addPinnedTabPromise();
     is(gBrowser.tabs[1].pinned, false, "After multiple calls of addPinnedTab, should still only have one pinned tab");
   }),
-  taskify(function* test_menu() {
+  taskify(function* test_bookmarks_menu() {
     let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
 
     ise(bookmarksMenuButton.open, false, "Menu should initially be closed");
     gContentAPI.showMenu("bookmarks");
 
     yield waitForConditionPromise(() => {
       return bookmarksMenuButton.open;
     }, "Menu should be visible after showMenu()");
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UITour_loop.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let loopButton;
+let loopPanel = document.getElementById("loop-notification-panel");
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+let tests = [
+  taskify(function* test_menu_show_hide() {
+    ise(loopButton.open, false, "Menu should initially be closed");
+    gContentAPI.showMenu("loop");
+
+    yield waitForConditionPromise(() => {
+      return loopButton.open;
+    }, "Menu should be visible after showMenu()");
+
+    ok(loopPanel.hasAttribute("noautohide"), "@noautohide should be on the loop panel");
+    ok(loopPanel.hasAttribute("panelopen"), "The panel should have @panelopen");
+    is(loopPanel.state, "open", "The panel should be open");
+    ok(loopButton.hasAttribute("open"), "Loop button should know that the menu is open");
+
+    gContentAPI.hideMenu("loop");
+    yield waitForConditionPromise(() => {
+        return !loopButton.open;
+    }, "Menu should be hidden after hideMenu()");
+
+    checkLoopPanelIsHidden();
+  }),
+  // Test the menu was cleaned up in teardown.
+  taskify(function* setup_menu_cleanup() {
+    gContentAPI.showMenu("loop");
+
+    yield waitForConditionPromise(() => {
+      return loopButton.open;
+    }, "Menu should be visible after showMenu()");
+
+    // Leave it open so it gets torn down and we can test below that teardown was succesful.
+  }),
+  taskify(function* test_menu_cleanup() {
+    // Test that the open menu from above was torn down fully.
+    checkLoopPanelIsHidden();
+  }),
+];
+
+function checkLoopPanelIsHidden() {
+  ok(!loopPanel.hasAttribute("noautohide"), "@noautohide on the loop panel should have been cleaned up");
+  ok(!loopPanel.hasAttribute("panelopen"), "The panel shouldn't have @panelopen");
+  isnot(loopPanel.state, "open", "The panel shouldn't be open");
+  is(loopButton.hasAttribute("open"), false, "Loop button should know that the panel is closed");
+}
+
+if (Services.prefs.getBoolPref("loop.enabled")) {
+  loopButton = window.LoopUI.toolbarButton.node;
+  registerCleanupFunction(() => {
+    // Copied from browser/components/loop/test/mochitest/head.js
+    // Remove the iframe after each test. This also avoids mochitest complaining
+    // about leaks on shutdown as we intentionally hold the iframe open for the
+    // life of the application.
+    let frameId = loopButton.getAttribute("notificationFrameId");
+    let frame = document.getElementById(frameId);
+    if (frame) {
+      frame.remove();
+    }
+  });
+} else {
+  ok(true, "Loop is disabled so skip the UITour Loop tests");
+  tests = [];
+}
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -29,17 +29,17 @@ treecol {
   list-style-image: url("chrome://browser/skin/preferences/in-content/icons.png");
 }
 
 #category-general > .category-icon {
   -moz-image-region: rect(0, 24px, 24px, 0);
 }
 
 #category-search > .category-icon {
-  -moz-image-region: rect(0, 194px, 24px, 168px);
+  -moz-image-region: rect(0, 192px, 24px, 168px);
 }
 
 #category-content > .category-icon {
   -moz-image-region: rect(0, 48px, 24px, 24px);
 }
 
 #category-application > .category-icon {
   -moz-image-region: rect(0, 72px, 24px, 48px);
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -742,16 +742,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/breadcrumbs-divider@2x.png      (../shared/devtools/images/breadcrumbs-divider@2x.png)
         skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton.png    (../shared/devtools/images/breadcrumbs-scrollbutton.png)
         skin/classic/aero/browser/devtools/breadcrumbs-scrollbutton@2x.png (../shared/devtools/images/breadcrumbs-scrollbutton@2x.png)
 *       skin/classic/aero/browser/devtools/canvasdebugger.css        (devtools/canvasdebugger.css)
 *       skin/classic/aero/browser/devtools/debugger.css              (devtools/debugger.css)
         skin/classic/aero/browser/devtools/eyedropper.css            (../shared/devtools/eyedropper.css)
 *       skin/classic/aero/browser/devtools/netmonitor.css            (devtools/netmonitor.css)
 *       skin/classic/aero/browser/devtools/profiler.css              (devtools/profiler.css)
+*       skin/classic/aero/browser/devtools/performance.css           (devtools/performance.css)
 *       skin/classic/aero/browser/devtools/timeline.css              (devtools/timeline.css)
 *       skin/classic/aero/browser/devtools/scratchpad.css            (devtools/scratchpad.css)
 *       skin/classic/aero/browser/devtools/shadereditor.css          (devtools/shadereditor.css)
 *       skin/classic/aero/browser/devtools/splitview.css             (../shared/devtools/splitview.css)
         skin/classic/aero/browser/devtools/styleeditor.css           (../shared/devtools/styleeditor.css)
         skin/classic/aero/browser/devtools/storage.css               (../shared/devtools/storage.css)
 *       skin/classic/aero/browser/devtools/webaudioeditor.css        (devtools/webaudioeditor.css)
         skin/classic/aero/browser/devtools/magnifying-glass.png      (../shared/devtools/images/magnifying-glass.png)
--- a/config/external/moz.build
+++ b/config/external/moz.build
@@ -20,34 +20,32 @@ if CONFIG['MOZ_UPDATER']:
 external_dirs += ['modules/brotli']
 
 if CONFIG['MOZ_VORBIS']:
     external_dirs += ['media/libvorbis']
 
 if CONFIG['MOZ_TREMOR']:
     external_dirs += ['media/libtremor']
 
-if CONFIG['MOZ_OPUS']:
-    external_dirs += ['media/libopus']
-
 if CONFIG['MOZ_WEBM']:
     external_dirs += ['media/libnestegg']
 
 if CONFIG['MOZ_WEBM_ENCODER']:
     external_dirs += ['media/libmkv']
 
 if CONFIG['MOZ_VPX'] and not CONFIG['MOZ_NATIVE_LIBVPX']:
     external_dirs += ['media/libvpx']
 
 if not CONFIG['MOZ_NATIVE_PNG']:
     external_dirs += ['media/libpng']
 
 external_dirs += [
     'media/kiss_fft',
     'media/libcubeb',
     'media/libogg',
+    'media/libopus',
     'media/libtheora',
     'media/libspeex_resampler',
     'media/libstagefright',
     'media/libsoundtouch',
 ]
 
 DIRS += ['../../' + i for i in external_dirs]
--- a/configure.in
+++ b/configure.in
@@ -3847,17 +3847,16 @@ MOZ_FEEDS=1
 MOZ_WEBAPP_RUNTIME=
 MOZ_AUTH_EXTENSION=1
 MOZ_RAW=
 MOZ_VORBIS=
 MOZ_TREMOR=
 MOZ_WAVE=1
 MOZ_SAMPLE_TYPE_FLOAT32=
 MOZ_SAMPLE_TYPE_S16=
-MOZ_OPUS=1
 MOZ_WEBM=1
 MOZ_GSTREAMER=
 MOZ_DIRECTSHOW=
 MOZ_WMF=
 if test -n "$MOZ_FMP4"; then
   MOZ_FMP4=1
 else
   MOZ_FMP4=
@@ -5220,24 +5219,16 @@ AC_CACHE_CHECK([__attribute__ ((aligned 
      done
        CFLAGS="${CFLAGS_save}"])
 if test "${ac_cv_c_attribute_aligned}" != "0"; then
   AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX],
                      [${ac_cv_c_attribute_aligned}],[Maximum supported data alignment])
 fi
 
 dnl ========================================================
-dnl = Disable Opus audio codec support
-dnl ========================================================
-MOZ_ARG_DISABLE_BOOL(opus,
-[  --disable-opus          Disable support for Opus audio],
-    MOZ_OPUS=,
-    MOZ_OPUS=1)
-
-dnl ========================================================
 dnl = Disable VP8 decoder support
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(webm,
 [  --disable-webm          Disable support for WebM media (VP8 video and Vorbis audio)],
     MOZ_WEBM=,
     MOZ_WEBM=1)
 
 if test -n "$MOZ_WEBM"; then
@@ -5562,34 +5553,26 @@ fi
 dnl ========================================================
 dnl = Handle dependent MEDIA defines
 dnl ========================================================
 
 if test -n "$MOZ_VORBIS" -a -n "$MOZ_TREMOR"; then
     AC_MSG_ERROR([MOZ_VORBIS and MOZ_TREMOR are mutually exclusive!  The build system should not allow them both to be set, but they are.  Please file a bug at https://bugzilla.mozilla.org/])
 fi
 
-if test -n "$MOZ_WEBRTC" -a -z "$MOZ_OPUS"; then
-    AC_MSG_ERROR([MOZ_WEBRTC requires MOZ_OPUS which is disabled.])
-fi
-
 if test -n "$MOZ_VORBIS"; then
     AC_DEFINE(MOZ_VORBIS)
 fi
 
 if test -n "$MOZ_TREMOR"; then
     AC_DEFINE(MOZ_TREMOR)
     # Tremor doesn't have an encoder.
     MOZ_WEBM_ENCODER=
 fi
 
-if test -n "$MOZ_OPUS"; then
-    AC_DEFINE(MOZ_OPUS)
-fi
-
 if test -n "$MOZ_WEBM_ENCODER"; then
     AC_DEFINE(MOZ_WEBM_ENCODER)
 fi
 AC_SUBST(MOZ_WEBM_ENCODER)
 
 dnl ==================================
 dnl = Check alsa availability on Linux
 dnl ==================================
@@ -8872,17 +8855,16 @@ AC_SUBST(CXX_VERSION)
 AC_SUBST(MSMANIFEST_TOOL)
 AC_SUBST(NS_ENABLE_TSF)
 AC_SUBST(WIN32_CONSOLE_EXE_LDFLAGS)
 AC_SUBST(WIN32_GUI_EXE_LDFLAGS)
 
 AC_SUBST(MOZ_WAVE)
 AC_SUBST(MOZ_VORBIS)
 AC_SUBST(MOZ_TREMOR)
-AC_SUBST(MOZ_OPUS)
 AC_SUBST(MOZ_WEBM)
 AC_SUBST(MOZ_WMF)
 AC_SUBST(MOZ_FFMPEG)
 AC_SUBST(MOZ_FMP4)
 AC_SUBST(MOZ_EME)
 AC_SUBST(MOZ_DIRECTSHOW)
 AC_SUBST(MOZ_ANDROID_OMX)
 AC_SUBST(MOZ_APPLEMEDIA)
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -368,17 +368,17 @@ skip-if = (buildapp == 'b2g' && toolkit 
 [test_bug331959.html]
 [test_bug333198.html]
 [test_bug333673.html]
 [test_bug337631.html]
 [test_bug338541.xhtml]
 [test_bug338583.html]
 # b2g(https not working, bug 907770) b2g-debug(https not working, bug 907770) b2g-desktop(43 total - bug 901343, specialpowers.wrap issue createsystemxhr)
 # e10s - bug 970589, bug 1091934
-skip-if = buildapp == 'b2g' || toolkit == 'android' || (os == 'linux' && debug && e10s) 
+skip-if = buildapp == 'b2g' || toolkit == 'android'
 [test_bug338679.html]
 [test_bug339494.html]
 [test_bug339494.xhtml]
 [test_bug340571.html]
 [test_bug343596.html]
 [test_bug345339.html]
 [test_bug346485.html]
 [test_bug352728.html]
--- a/dom/base/test/test_bug338583.html
+++ b/dom/base/test/test_bug338583.html
@@ -639,17 +639,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     var f = function() {
       for (var j=0; j<f.i; ++j)
         eval("Math.log(Math.atan(Math.sqrt(Math.pow(3.1415, 13.1415))/0.0007))");
       if (f.i < 10) {
         f.i++;
         setTimeout(f, 10 + 10*f.i);
       } else {
         stress_factor = ((new Date()).getTime()-begin_time)*1/589;
-        stress_factor *= 1.10; // also, a margin of 10%
+        stress_factor *= 1.50; // also, a margin of 50%
 
         runAllTests();
       }
     }
     f.i = 0;
 
     setTimeout(f, 10);
   }
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -23,13 +23,16 @@ public:
     virtual ~WebGL1Context();
 
     virtual bool IsWebGL2() const MOZ_OVERRIDE {
         return false;
     }
 
     // nsWrapperCache
     virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
+
+private:
+    virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) MOZ_OVERRIDE;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_1_CONTEXT_H_
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGL1ContextUniforms.cpp
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 "WebGL1Context.h"
+
+using namespace mozilla;
+
+bool
+WebGL1Context::ValidateAttribPointerType(bool /*integerMode*/, GLenum type, GLsizei* out_alignment, const char* info)
+{
+    MOZ_ASSERT(out_alignment);
+    if (!out_alignment)
+        return false;
+
+    switch (type) {
+    case LOCAL_GL_BYTE:
+    case LOCAL_GL_UNSIGNED_BYTE:
+        *out_alignment = 1;
+        return true;
+
+    case LOCAL_GL_SHORT:
+    case LOCAL_GL_UNSIGNED_SHORT:
+        *out_alignment = 2;
+        return true;
+        // XXX case LOCAL_GL_FIXED:
+    case LOCAL_GL_FLOAT:
+        *out_alignment = 4;
+        return true;
+    }
+
+    ErrorInvalidEnumInfo(info, type);
+    return false;
+}
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -30,17 +30,16 @@ public:
         return true;
     }
 
     // -------------------------------------------------------------------------
     // IMPLEMENT nsWrapperCache
 
     virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
-
     // -------------------------------------------------------------------------
     // Buffer objects - WebGL2ContextBuffers.cpp
 
     void CopyBufferSubData(GLenum readTarget, GLenum writeTarget,
                            GLintptr readOffset, GLintptr writeOffset, GLsizeiptr size);
     void GetBufferSubData(GLenum target, GLintptr offset, const dom::ArrayBuffer& returnedData);
     void GetBufferSubData(GLenum target, GLintptr offset, const dom::ArrayBufferView& returnedData);
 
@@ -121,16 +120,22 @@ public:
     void UniformMatrix2x4fv(WebGLUniformLocation* location, bool transpose, const dom::Sequence<GLfloat>& value);
     void UniformMatrix4x2fv(WebGLUniformLocation* location, bool transpose, const dom::Float32Array& value);
     void UniformMatrix4x2fv(WebGLUniformLocation* location, bool transpose, const dom::Sequence<GLfloat>& value);
     void UniformMatrix3x4fv(WebGLUniformLocation* location, bool transpose, const dom::Float32Array& value);
     void UniformMatrix3x4fv(WebGLUniformLocation* location, bool transpose, const dom::Sequence<GLfloat>& value);
     void UniformMatrix4x3fv(WebGLUniformLocation* location, bool transpose, const dom::Float32Array& value);
     void UniformMatrix4x3fv(WebGLUniformLocation* location, bool transpose, const dom::Sequence<GLfloat>& value);
 
+private:
+    void VertexAttribI4iv(GLuint index, size_t length, const GLint* v);
+    void VertexAttribI4uiv(GLuint index, size_t length, const GLuint* v);
+
+public:
+    // GL 3.0 & ES 3.0
     void VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w);
     void VertexAttribI4iv(GLuint index, const dom::Sequence<GLint>& v);
     void VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w);
     void VertexAttribI4uiv(GLuint index, const dom::Sequence<GLuint>& v);
 
 
     // -------------------------------------------------------------------------
     // Writing to the drawing buffer
@@ -245,18 +250,21 @@ public:
     void DeleteVertexArray(WebGLVertexArrayObject* vertexArray);
     bool IsVertexArray(WebGLVertexArrayObject* vertexArray);
     void BindVertexArray(WebGLVertexArrayObject* vertexArray);
 */
 
 private:
     WebGL2Context();
 
+    JS::Value GetTexParameterInternal(const TexTarget& target, GLenum pname) MOZ_OVERRIDE;
+
     bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info);
     bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
                                 GLsizei width, GLsizei height, GLsizei depth,
                                 const char* info);
-    JS::Value GetTexParameterInternal(const TexTarget& target, GLenum pname) MOZ_OVERRIDE;
+
+    virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) MOZ_OVERRIDE;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextQueries.cpp
+++ b/dom/canvas/WebGL2ContextQueries.cpp
@@ -51,17 +51,17 @@ SimulateOcclusionQueryTarget(const gl::G
         return target;
     } else if (gl->IsSupported(gl::GLFeature::occlusion_query2)) {
         return LOCAL_GL_ANY_SAMPLES_PASSED;
     }
 
     return LOCAL_GL_SAMPLES_PASSED;
 }
 
-WebGLQueryRefPtr*
+WebGLRefPtr<WebGLQuery>*
 WebGLContext::GetQueryTargetSlot(GLenum target)
 {
     switch (target) {
         case LOCAL_GL_ANY_SAMPLES_PASSED:
         case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
             return &mActiveOcclusionQuery;
 
         case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -1,26 +1,112 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* 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 "WebGL2Context.h"
 #include "GLContext.h"
+#include "WebGLVertexArray.h"
+#include "WebGLVertexAttribData.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+typedef union { GLint i; GLfloat f; GLuint u; } fi_t;
+
+static inline
+GLfloat PuntToFloat(GLint i)
+{
+   fi_t tmp;
+   tmp.i = i;
+   return tmp.f;
+}
+
+static inline
+GLfloat PuntToFloat(GLuint u)
+{
+   fi_t tmp;
+   tmp.u = u;
+   return tmp.f;
+}
+
+bool
+WebGL2Context::ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info)
+{
+    MOZ_ASSERT(alignment);
+
+    switch (type) {
+    case LOCAL_GL_BYTE:
+    case LOCAL_GL_UNSIGNED_BYTE:
+        *alignment = 1;
+        return true;
+
+    case LOCAL_GL_SHORT:
+    case LOCAL_GL_UNSIGNED_SHORT:
+        *alignment = 2;
+        return true;
+
+    case LOCAL_GL_INT:
+    case LOCAL_GL_UNSIGNED_INT:
+        *alignment = 4;
+        return true;
+    }
+
+    if (!integerMode) {
+        switch (type) {
+        case LOCAL_GL_HALF_FLOAT:
+            *alignment = 2;
+            return true;
+
+        case LOCAL_GL_FLOAT:
+        case LOCAL_GL_FIXED:
+        case LOCAL_GL_INT_2_10_10_10_REV:
+        case LOCAL_GL_UNSIGNED_INT_2_10_10_10_REV:
+            *alignment = 4;
+            return true;
+        }
+    }
+
+    ErrorInvalidEnum("%s: invalid enum value 0x%x", info, type);
+    return false;
+}
+
 // -------------------------------------------------------------------------
 // Uniforms and attributes
 
 void
 WebGL2Context::VertexAttribIPointer(GLuint index, GLint size, GLenum type, GLsizei stride, GLintptr offset)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!ValidateAttribIndex(index, "vertexAttribIPointer"))
+        return;
+
+    if (!ValidateAttribPointer(true, index, size, type, LOCAL_GL_FALSE, stride, offset, "vertexAttribIPointer"))
+        return;
+
+    MOZ_ASSERT(mBoundVertexArray);
+    mBoundVertexArray->EnsureAttrib(index);
+
+    InvalidateBufferFetching();
+
+    WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
+
+    vd.buf = mBoundArrayBuffer;
+    vd.stride = stride;
+    vd.size = size;
+    vd.byteOffset = offset;
+    vd.type = type;
+    vd.normalized = false;
+    vd.integer = true;
+
+    MakeContextCurrent();
+    gl->fVertexAttribIPointer(index, size, type, stride, reinterpret_cast<void*>(offset));
 }
 
 void
 WebGL2Context::Uniform1ui(WebGLUniformLocation* location, GLuint v0)
 {
     MOZ_CRASH("Not Implemented.");
 }
 
@@ -136,35 +222,91 @@ void
 WebGL2Context::UniformMatrix4x3fv(WebGLUniformLocation* location, bool transpose, const dom::Sequence<GLfloat>& value)
 {
     MOZ_CRASH("Not Implemented.");
 }
 
 void
 WebGL2Context::VertexAttribI4i(GLuint index, GLint x, GLint y, GLint z, GLint w)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (index || gl->IsGLES()) {
+        MakeContextCurrent();
+        gl->fVertexAttribI4i(index, x, y, z, w);
+    } else {
+        mVertexAttrib0Vector[0] = PuntToFloat(x);
+        mVertexAttrib0Vector[1] = PuntToFloat(y);
+        mVertexAttrib0Vector[2] = PuntToFloat(z);
+        mVertexAttrib0Vector[3] = PuntToFloat(w);
+    }
+}
+
+void
+WebGL2Context::VertexAttribI4iv(GLuint index, size_t length, const GLint* v)
+{
+    if (!ValidateAttribArraySetter("vertexAttribI4iv", 4, length))
+        return;
+
+    if (index || gl->IsGLES()) {
+        MakeContextCurrent();
+        gl->fVertexAttribI4iv(index, v);
+    } else {
+        mVertexAttrib0Vector[0] = PuntToFloat(v[0]);
+        mVertexAttrib0Vector[1] = PuntToFloat(v[1]);
+        mVertexAttrib0Vector[2] = PuntToFloat(v[2]);
+        mVertexAttrib0Vector[3] = PuntToFloat(v[3]);
+    }
 }
 
 void
 WebGL2Context::VertexAttribI4iv(GLuint index, const dom::Sequence<GLint>& v)
 {
-    MOZ_CRASH("Not Implemented.");
+    VertexAttribI4iv(index, v.Length(), v.Elements());
 }
 
 void
 WebGL2Context::VertexAttribI4ui(GLuint index, GLuint x, GLuint y, GLuint z, GLuint w)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (index || gl->IsGLES()) {
+        MakeContextCurrent();
+        gl->fVertexAttribI4ui(index, x, y, z, w);
+    } else {
+        mVertexAttrib0Vector[0] = PuntToFloat(x);
+        mVertexAttrib0Vector[1] = PuntToFloat(y);
+        mVertexAttrib0Vector[2] = PuntToFloat(z);
+        mVertexAttrib0Vector[3] = PuntToFloat(w);
+    }
+}
+
+void
+WebGL2Context::VertexAttribI4uiv(GLuint index, size_t length, const GLuint* v)
+{
+    if (IsContextLost())
+        return;
+
+    if (index || gl->IsGLES()) {
+        MakeContextCurrent();
+        gl->fVertexAttribI4uiv(index, v);
+    } else {
+        mVertexAttrib0Vector[0] = PuntToFloat(v[0]);
+        mVertexAttrib0Vector[1] = PuntToFloat(v[1]);
+        mVertexAttrib0Vector[2] = PuntToFloat(v[2]);
+        mVertexAttrib0Vector[3] = PuntToFloat(v[3]);
+    }
 }
 
 void
 WebGL2Context::VertexAttribI4uiv(GLuint index, const dom::Sequence<GLuint>& v)
 {
-    MOZ_CRASH("Not Implemented.");
+    VertexAttribI4uiv(index, v.Length(), v.Elements());
 }
 
 // -------------------------------------------------------------------------
 // Uniform Buffer Objects and Transform Feedback Buffers
 // TODO(djg): Implemented in WebGLContext
 /*
     void BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer);
     void BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer, GLintptr offset, GLsizeiptr size);
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -47,17 +47,17 @@
 class nsIDocShell;
 
 /*
  * Minimum value constants defined in 6.2 State Tables of OpenGL ES - 2.0.25
  *   https://bugzilla.mozilla.org/show_bug.cgi?id=686732
  *
  * Exceptions: some of the following values are set to higher values than in the spec because
  * the values in the spec are ridiculously low. They are explicitly marked below
-*/
+ */
 #define MINVALUE_GL_MAX_TEXTURE_SIZE                  1024  // Different from the spec, which sets it to 64 on page 162
 #define MINVALUE_GL_MAX_CUBE_MAP_TEXTURE_SIZE         512   // Different from the spec, which sets it to 16 on page 162
 #define MINVALUE_GL_MAX_VERTEX_ATTRIBS                8     // Page 164
 #define MINVALUE_GL_MAX_FRAGMENT_UNIFORM_VECTORS      16    // Page 164
 #define MINVALUE_GL_MAX_VERTEX_UNIFORM_VECTORS        128   // Page 164
 #define MINVALUE_GL_MAX_VARYING_VECTORS               8     // Page 164
 #define MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS           8     // Page 164
 #define MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS    0     // Page 164
@@ -90,18 +90,16 @@ class ImageData;
 struct WebGLContextAttributes;
 template<typename> struct Nullable;
 }
 
 namespace gfx {
 class SourceSurface;
 }
 
-typedef WebGLRefPtr<WebGLQuery> WebGLQueryRefPtr;
-
 WebGLTexelFormat GetWebGLTexelFormat(TexInternalFormat format);
 
 void AssertUintParamCorrect(gl::GLContext* gl, GLenum pname, GLuint shadow);
 
 struct WebGLContextOptions
 {
     // these are defaults
     WebGLContextOptions();
@@ -129,21 +127,23 @@ struct WebGLContextOptions
 };
 
 // From WebGLContextUtils
 TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget);
 
 class WebGLIntOrFloat {
     enum {
         Int,
-        Float
+        Float,
+        Uint
     } mType;
     union {
         GLint i;
         GLfloat f;
+        GLuint u;
     } mValue;
 
 public:
     explicit WebGLIntOrFloat(GLint i) : mType(Int) { mValue.i = i; }
     explicit WebGLIntOrFloat(GLfloat f) : mType(Float) { mValue.f = f; }
 
     GLint AsInt() const { return (mType == Int) ? mValue.i : NS_lroundf(mValue.f); }
     GLfloat AsFloat() const { return (mType == Float) ? mValue.f : GLfloat(mValue.i); }
@@ -153,16 +153,17 @@ class WebGLContext
     : public nsIDOMWebGLRenderingContext
     , public nsICanvasRenderingContextInternal
     , public nsSupportsWeakReference
     , public WebGLContextUnchecked
     , public WebGLRectangleObject
     , public nsWrapperCache
     , public SupportsWeakPtr<WebGLContext>
 {
+    friend class WebGL2Context;
     friend class WebGLContextUserData;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTextureETC1;
     friend class WebGLExtensionCompressedTexturePVRTC;
     friend class WebGLExtensionCompressedTextureS3TC;
     friend class WebGLExtensionDepthTexture;
     friend class WebGLExtensionDrawBuffers;
     friend class WebGLExtensionLoseContext;
@@ -893,20 +894,20 @@ private:
     WebGLRefPtr<WebGLBuffer>* GetBufferSlotByTargetIndexed(GLenum target,
                                                            GLuint index,
                                                            const char* info);
     bool ValidateBufferUsageEnum(GLenum target, const char* info);
 
 // -----------------------------------------------------------------------------
 // Queries (WebGL2ContextQueries.cpp)
 protected:
-    WebGLQueryRefPtr* GetQueryTargetSlot(GLenum target);
+    WebGLRefPtr<WebGLQuery>* GetQueryTargetSlot(GLenum target);
 
-    WebGLQueryRefPtr mActiveOcclusionQuery;
-    WebGLQueryRefPtr mActiveTransformFeedbackQuery;
+    WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
+    WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
 
 // -----------------------------------------------------------------------------
 // State and State Requests (WebGLContextState.cpp)
 public:
     void Disable(GLenum cap);
     void Enable(GLenum cap);
     bool GetStencilBits(GLint* out_stencilBits);
     JS::Value GetParameter(JSContext* cx, GLenum pname, ErrorResult& rv);
@@ -1001,16 +1002,17 @@ public:
 private:
     // Cache the max number of vertices and instances that can be read from
     // bound VBOs (result of ValidateBuffers).
     bool mBufferFetchingIsVerified;
     bool mBufferFetchingHasPerVertex;
     uint32_t mMaxFetchedVertices;
     uint32_t mMaxFetchedInstances;
 
+protected:
     inline void InvalidateBufferFetching() {
         mBufferFetchingIsVerified = false;
         mBufferFetchingHasPerVertex = false;
         mMaxFetchedVertices = 0;
         mMaxFetchedInstances = 0;
     }
 
     bool DrawArrays_check(GLint first, GLsizei count, GLsizei primcount,
@@ -1187,16 +1189,20 @@ protected:
                                              const char* info);
     bool ValidateTextureTargetEnum(GLenum target, const char* info);
     bool ValidateComparisonEnum(GLenum target, const char* info);
     bool ValidateStencilOpEnum(GLenum action, const char* info);
     bool ValidateFaceEnum(GLenum face, const char* info);
     bool ValidateTexInputData(GLenum type, js::Scalar::Type jsArrayType,
                               WebGLTexImageFunc func, WebGLTexDimensions dims);
     bool ValidateDrawModeEnum(GLenum mode, const char* info);
+    bool ValidateAttribIndex(GLuint index, const char* info);
+    bool ValidateAttribPointer(bool integerMode, GLuint index, GLint size, GLenum type,
+                               WebGLboolean normalized, GLsizei stride,
+                               WebGLintptr byteOffset, const char* info);
     bool ValidateStencilParamsForDrawCall();
 
     bool ValidateGLSLVariableName(const nsAString& name, const char* info);
     bool ValidateGLSLCharacter(char16_t c);
     bool ValidateGLSLString(const nsAString& string, const char* info);
 
     bool ValidateCopyTexImage(GLenum internalFormat, WebGLTexImageFunc func,
                               WebGLTexDimensions dims);
@@ -1324,16 +1330,21 @@ protected:
     bool ValidateObjectAllowDeleted(const char* info, ObjectType* object);
 
 private:
     // Like ValidateObject, but only for cases when `object` is known to not be
     // null already.
     template<class ObjectType>
     bool ValidateObjectAssumeNonNull(const char* info, ObjectType* object);
 
+private:
+    // -------------------------------------------------------------------------
+    // Context customization points
+    virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) = 0;
+
 protected:
     int32_t MaxTextureSizeForTarget(TexTarget target) const {
         return (target == LOCAL_GL_TEXTURE_2D) ? mGLMaxTextureSize
                                                : mGLMaxCubeMapTextureSize;
     }
 
     int32_t
     MaxTextureLevelForTexImageTarget(TexImageTarget texImageTarget) const {
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -616,22 +616,30 @@ WebGLContext::UndoFakeVertexAttrib0()
     WebGLVertexAttrib0Status whatDoesAttrib0Need = WhatDoesVertexAttrib0Need();
 
     if (MOZ_LIKELY(whatDoesAttrib0Need == WebGLVertexAttrib0Status::Default))
         return;
 
     if (mBoundVertexArray->HasAttrib(0) && mBoundVertexArray->mAttribs[0].buf) {
         const WebGLVertexAttribData& attrib0 = mBoundVertexArray->mAttribs[0];
         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, attrib0.buf->GLName());
-        gl->fVertexAttribPointer(0,
-                                 attrib0.size,
-                                 attrib0.type,
-                                 attrib0.normalized,
-                                 attrib0.stride,
-                                 reinterpret_cast<const GLvoid *>(attrib0.byteOffset));
+        if (attrib0.integer) {
+            gl->fVertexAttribIPointer(0,
+                                      attrib0.size,
+                                      attrib0.type,
+                                      attrib0.stride,
+                                      reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
+        } else {
+            gl->fVertexAttribPointer(0,
+                                     attrib0.size,
+                                     attrib0.type,
+                                     attrib0.normalized,
+                                     attrib0.stride,
+                                     reinterpret_cast<const GLvoid*>(attrib0.byteOffset));
+        }
     } else {
         gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0);
     }
 
     gl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, mBoundArrayBuffer ? mBoundArrayBuffer->GLName() : 0);
 }
 
 WebGLContextFakeBlackStatus
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -1714,16 +1714,88 @@ WebGLContext::ValidateUniformMatrixArray
 
     *out_rawLoc = loc->Location();
     *out_numElementsToUpload = std::min((size_t)loc->Info().arraySize,
                                         setterArraySize / setterElemSize);
     return true;
 }
 
 bool
+WebGLContext::ValidateAttribIndex(GLuint index, const char* info)
+{
+    bool valid = (index < MaxVertexAttribs());
+
+    if (!valid) {
+        if (index == GLuint(-1)) {
+            ErrorInvalidValue("%s: -1 is not a valid `index`. This value"
+                              " probably comes from a getAttribLocation()"
+                              " call, where this return value -1 means"
+                              " that the passed name didn't correspond to"
+                              " an active attribute in the specified"
+                              " program.", info);
+        } else {
+            ErrorInvalidValue("%s: `index` must be less than"
+                              " MAX_VERTEX_ATTRIBS.", info);
+        }
+    }
+
+    return valid;
+}
+
+bool
+WebGLContext::ValidateAttribPointer(bool integerMode, GLuint index, GLint size, GLenum type,
+                                    WebGLboolean normalized, GLsizei stride,
+                                    WebGLintptr byteOffset, const char* info)
+{
+    WebGLBuffer* buffer = mBoundArrayBuffer;
+    if (!buffer) {
+        ErrorInvalidOperation("%s: must have valid GL_ARRAY_BUFFER binding", info);
+        return false;
+    }
+
+    GLsizei requiredAlignment = 0;
+    if (!ValidateAttribPointerType(integerMode, type, &requiredAlignment, info))
+        return false;
+
+    // requiredAlignment should always be a power of two
+    MOZ_ASSERT(IsPOTAssumingNonnegative(requiredAlignment));
+    GLsizei requiredAlignmentMask = requiredAlignment - 1;
+
+    if (size < 1 || size > 4) {
+        ErrorInvalidValue("%s: invalid element size", info);
+        return false;
+    }
+
+    // see WebGL spec section 6.6 "Vertex Attribute Data Stride"
+    if (stride < 0 || stride > 255) {
+        ErrorInvalidValue("%s: negative or too large stride", info);
+        return false;
+    }
+
+    if (byteOffset < 0) {
+        ErrorInvalidValue("%s: negative offset", info);
+        return false;
+    }
+
+    if (stride & requiredAlignmentMask) {
+        ErrorInvalidOperation("%s: stride doesn't satisfy the alignment "
+                              "requirement of given type", info);
+        return false;
+    }
+
+    if (byteOffset & requiredAlignmentMask) {
+        ErrorInvalidOperation("%s: byteOffset doesn't satisfy the alignment "
+                              "requirement of given type", info);
+        return false;
+    }
+
+    return true;
+}
+
+bool
 WebGLContext::ValidateStencilParamsForDrawCall()
 {
     const char msg[] = "%s set different front and back stencil %s. Drawing in"
                        " this configuration is not allowed.";
 
     if (mStencilRefFront != mStencilRefBack) {
         ErrorInvalidOperation(msg, "stencilFuncSeparate", "reference values");
         return false;
--- a/dom/canvas/WebGLContextVertices.cpp
+++ b/dom/canvas/WebGLContextVertices.cpp
@@ -15,44 +15,23 @@
 #include "WebGLTexture.h"
 #include "WebGLUniformInfo.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 using namespace mozilla;
 using namespace dom;
 
-static bool
-CheckAttribIndex(WebGLContext& webgl, GLuint index, const char* info)
-{
-    if (index >= webgl.MaxVertexAttribs()) {
-        if (index == GLuint(-1)) {
-            webgl.ErrorInvalidValue("%s: -1 is not a valid `index`. This value"
-                                    " probably comes from a getAttribLocation()"
-                                    " call, where this return value -1 means"
-                                    " that the passed name didn't correspond to"
-                                    " an active attribute in the specified"
-                                    " program.", info);
-        } else {
-            webgl.ErrorInvalidValue("%s: `index` must be less than"
-                                    " MAX_VERTEX_ATTRIBS.", info);
-        }
-        return false;
-    }
-
-    return true;
-}
-
 void
 WebGLContext::VertexAttrib1f(GLuint index, GLfloat x0)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib1f"))
+    if (!ValidateAttribIndex(index, "vertexAttrib1f"))
         return;
 
     MakeContextCurrent();
 
     if (index) {
         gl->fVertexAttrib1f(index, x0);
     } else {
         mVertexAttrib0Vector[0] = x0;
@@ -65,17 +44,17 @@ WebGLContext::VertexAttrib1f(GLuint inde
 }
 
 void
 WebGLContext::VertexAttrib2f(GLuint index, GLfloat x0, GLfloat x1)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib2f"))
+    if (!ValidateAttribIndex(index, "vertexAttrib2f"))
         return;
 
     MakeContextCurrent();
 
     if (index) {
         gl->fVertexAttrib2f(index, x0, x1);
     } else {
         mVertexAttrib0Vector[0] = x0;
@@ -88,17 +67,17 @@ WebGLContext::VertexAttrib2f(GLuint inde
 }
 
 void
 WebGLContext::VertexAttrib3f(GLuint index, GLfloat x0, GLfloat x1, GLfloat x2)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib3f"))
+    if (!ValidateAttribIndex(index, "vertexAttrib3f"))
         return;
 
     MakeContextCurrent();
 
     if (index) {
         gl->fVertexAttrib3f(index, x0, x1, x2);
     } else {
         mVertexAttrib0Vector[0] = x0;
@@ -112,17 +91,17 @@ WebGLContext::VertexAttrib3f(GLuint inde
 
 void
 WebGLContext::VertexAttrib4f(GLuint index, GLfloat x0, GLfloat x1,
                              GLfloat x2, GLfloat x3)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib4f"))
+    if (!ValidateAttribIndex(index, "vertexAttrib4f"))
         return;
 
     MakeContextCurrent();
 
     if (index) {
         gl->fVertexAttrib4f(index, x0, x1, x2, x3);
     } else {
         mVertexAttrib0Vector[0] = x0;
@@ -137,17 +116,17 @@ WebGLContext::VertexAttrib4f(GLuint inde
 
 void
 WebGLContext::VertexAttrib1fv_base(GLuint index, uint32_t arrayLength,
                                    const GLfloat* ptr)
 {
     if (!ValidateAttribArraySetter("VertexAttrib1fv", 1, arrayLength))
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib1fv"))
+    if (!ValidateAttribIndex(index, "vertexAttrib1fv"))
         return;
 
     MakeContextCurrent();
     if (index) {
         gl->fVertexAttrib1fv(index, ptr);
     } else {
         mVertexAttrib0Vector[0] = ptr[0];
         mVertexAttrib0Vector[1] = GLfloat(0);
@@ -160,17 +139,17 @@ WebGLContext::VertexAttrib1fv_base(GLuin
 
 void
 WebGLContext::VertexAttrib2fv_base(GLuint index, uint32_t arrayLength,
                                    const GLfloat* ptr)
 {
     if (!ValidateAttribArraySetter("VertexAttrib2fv", 2, arrayLength))
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib2fv"))
+    if (!ValidateAttribIndex(index, "vertexAttrib2fv"))
         return;
 
     MakeContextCurrent();
     if (index) {
         gl->fVertexAttrib2fv(index, ptr);
     } else {
         mVertexAttrib0Vector[0] = ptr[0];
         mVertexAttrib0Vector[1] = ptr[1];
@@ -183,17 +162,17 @@ WebGLContext::VertexAttrib2fv_base(GLuin
 
 void
 WebGLContext::VertexAttrib3fv_base(GLuint index, uint32_t arrayLength,
                                    const GLfloat* ptr)
 {
     if (!ValidateAttribArraySetter("VertexAttrib3fv", 3, arrayLength))
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib3fv"))
+    if (!ValidateAttribIndex(index, "vertexAttrib3fv"))
         return;
 
     MakeContextCurrent();
     if (index) {
         gl->fVertexAttrib3fv(index, ptr);
     } else {
         mVertexAttrib0Vector[0] = ptr[0];
         mVertexAttrib0Vector[1] = ptr[1];
@@ -206,17 +185,17 @@ WebGLContext::VertexAttrib3fv_base(GLuin
 
 void
 WebGLContext::VertexAttrib4fv_base(GLuint index, uint32_t arrayLength,
                                    const GLfloat* ptr)
 {
     if (!ValidateAttribArraySetter("VertexAttrib4fv", 4, arrayLength))
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttrib4fv"))
+    if (!ValidateAttribIndex(index, "vertexAttrib4fv"))
         return;
 
     MakeContextCurrent();
     if (index) {
         gl->fVertexAttrib4fv(index, ptr);
     } else {
         mVertexAttrib0Vector[0] = ptr[0];
         mVertexAttrib0Vector[1] = ptr[1];
@@ -228,17 +207,17 @@ WebGLContext::VertexAttrib4fv_base(GLuin
 }
 
 void
 WebGLContext::EnableVertexAttribArray(GLuint index)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "enableVertexAttribArray"))
+    if (!ValidateAttribIndex(index, "enableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
     InvalidateBufferFetching();
 
     gl->fEnableVertexAttribArray(index);
 
     MOZ_ASSERT(mBoundVertexArray);
@@ -247,17 +226,17 @@ WebGLContext::EnableVertexAttribArray(GL
 }
 
 void
 WebGLContext::DisableVertexAttribArray(GLuint index)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "disableVertexAttribArray"))
+    if (!ValidateAttribIndex(index, "disableVertexAttribArray"))
         return;
 
     MakeContextCurrent();
     InvalidateBufferFetching();
 
     if (index || gl->IsGLES())
         gl->fDisableVertexAttribArray(index);
 
@@ -268,17 +247,17 @@ WebGLContext::DisableVertexAttribArray(G
 
 JS::Value
 WebGLContext::GetVertexAttrib(JSContext* cx, GLuint index, GLenum pname,
                               ErrorResult& rv)
 {
     if (IsContextLost())
         return JS::NullValue();
 
-    if (!CheckAttribIndex(*this, index, "getVertexAttrib"))
+    if (!ValidateAttribIndex(index, "getVertexAttrib"))
         return JS::NullValue();
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
 
     MakeContextCurrent();
 
     switch (pname) {
@@ -354,17 +333,17 @@ WebGLContext::GetVertexAttrib(JSContext*
 }
 
 WebGLsizeiptr
 WebGLContext::GetVertexAttribOffset(GLuint index, GLenum pname)
 {
     if (IsContextLost())
         return 0;
 
-    if (!CheckAttribIndex(*this, index, "getVertexAttribOffset"))
+    if (!ValidateAttribIndex(index, "getVertexAttribOffset"))
         return 0;
 
     if (pname != LOCAL_GL_VERTEX_ATTRIB_ARRAY_POINTER) {
         ErrorInvalidEnum("getVertexAttribOffset: bad parameter");
         return 0;
     }
 
     MOZ_ASSERT(mBoundVertexArray);
@@ -375,96 +354,54 @@ WebGLContext::GetVertexAttribOffset(GLui
 void
 WebGLContext::VertexAttribPointer(GLuint index, GLint size, GLenum type,
                                   WebGLboolean normalized, GLsizei stride,
                                   WebGLintptr byteOffset)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttribPointer"))
+    if (!ValidateAttribIndex(index, "vertexAttribPointer"))
         return;
 
-    if (mBoundArrayBuffer == nullptr)
-        return ErrorInvalidOperation("vertexAttribPointer: must have valid GL_ARRAY_BUFFER binding");
-
-    GLsizei requiredAlignment = 1;
-    switch (type) {
-        case LOCAL_GL_BYTE:
-        case LOCAL_GL_UNSIGNED_BYTE:
-            requiredAlignment = 1;
-            break;
-        case LOCAL_GL_SHORT:
-        case LOCAL_GL_UNSIGNED_SHORT:
-            requiredAlignment = 2;
-            break;
-            // XXX case LOCAL_GL_FIXED:
-        case LOCAL_GL_FLOAT:
-            requiredAlignment = 4;
-            break;
-        default:
-            return ErrorInvalidEnumInfo("vertexAttribPointer: type", type);
-    }
-
-    // requiredAlignment should always be a power of two.
-    GLsizei requiredAlignmentMask = requiredAlignment - 1;
+    if (!ValidateAttribPointer(false, index, size, type, normalized, stride, byteOffset, "vertexAttribPointer"))
+        return;
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
 
-    if (size < 1 || size > 4)
-        return ErrorInvalidValue("vertexAttribPointer: invalid element size");
-
-    if (stride < 0 || stride > 255) // see WebGL spec section 6.6 "Vertex Attribute Data Stride"
-        return ErrorInvalidValue("vertexAttribPointer: negative or too large stride");
-
-    if (byteOffset < 0)
-        return ErrorInvalidValue("vertexAttribPointer: negative offset");
-
-    if (stride & requiredAlignmentMask) {
-        return ErrorInvalidOperation("vertexAttribPointer: stride doesn't satisfy the alignment "
-                                     "requirement of given type");
-    }
-
-    if (byteOffset & requiredAlignmentMask) {
-        return ErrorInvalidOperation("vertexAttribPointer: byteOffset doesn't satisfy the alignment "
-                                     "requirement of given type");
-
-    }
-
     InvalidateBufferFetching();
 
     /* XXX make work with bufferSubData & heterogeneous types
      if (type != mBoundArrayBuffer->GLType())
      return ErrorInvalidOperation("vertexAttribPointer: type must match bound VBO type: %d != %d", type, mBoundArrayBuffer->GLType());
      */
 
     WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
 
     vd.buf = mBoundArrayBuffer;
     vd.stride = stride;
     vd.size = size;
     vd.byteOffset = byteOffset;
     vd.type = type;
     vd.normalized = normalized;
+    vd.integer = false;
 
     MakeContextCurrent();
-
-    gl->fVertexAttribPointer(index, size, type, normalized,
-                             stride,
+    gl->fVertexAttribPointer(index, size, type, normalized, stride,
                              reinterpret_cast<void*>(byteOffset));
 }
 
 void
 WebGLContext::VertexAttribDivisor(GLuint index, GLuint divisor)
 {
     if (IsContextLost())
         return;
 
-    if (!CheckAttribIndex(*this, index, "vertexAttribDivisor"))
+    if (!ValidateAttribIndex(index, "vertexAttribDivisor"))
         return;
 
     MOZ_ASSERT(mBoundVertexArray);
     mBoundVertexArray->EnsureAttrib(index);
 
     WebGLVertexAttribData& vd = mBoundVertexArray->mAttribs[index];
     vd.divisor = divisor;
 
--- a/dom/canvas/WebGLVertexArray.h
+++ b/dom/canvas/WebGLVertexArray.h
@@ -69,13 +69,14 @@ protected:
     }
 
     GLuint mGLName;
     nsTArray<WebGLVertexAttribData> mAttribs;
     WebGLRefPtr<WebGLBuffer> mElementArrayBuffer;
 
     friend class WebGLContext;
     friend class WebGLVertexArrayFake;
+    friend class WebGL2Context;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_VERTEX_ARRAY_H_
--- a/dom/canvas/WebGLVertexArrayFake.cpp
+++ b/dom/canvas/WebGLVertexArrayFake.cpp
@@ -24,18 +24,23 @@ WebGLVertexArrayFake::BindVertexArrayImp
     WebGLRefPtr<WebGLBuffer> prevBuffer = mContext->mBoundArrayBuffer;
     mContext->BindBuffer(LOCAL_GL_ELEMENT_ARRAY_BUFFER, mElementArrayBuffer);
 
     for (size_t i = 0; i < mAttribs.Length(); ++i) {
         const WebGLVertexAttribData& vd = mAttribs[i];
 
         mContext->BindBuffer(LOCAL_GL_ARRAY_BUFFER, vd.buf);
 
-        gl->fVertexAttribPointer(i, vd.size, vd.type, vd.normalized, vd.stride,
-                                 reinterpret_cast<void*>(vd.byteOffset));
+        if (vd.integer) {
+            gl->fVertexAttribIPointer(i, vd.size, vd.type, vd.stride,
+                                      reinterpret_cast<const GLvoid*>(vd.byteOffset));
+        } else {
+            gl->fVertexAttribPointer(i, vd.size, vd.type, vd.normalized, vd.stride,
+                                     reinterpret_cast<const GLvoid*>(vd.byteOffset));
+        }
 
         if (vd.enabled)
             gl->fEnableVertexAttribArray(i);
         else
             gl->fDisableVertexAttribArray(i);
     }
 
     size_t len = prevVertexArray->mAttribs.Length();
--- a/dom/canvas/WebGLVertexAttribData.h
+++ b/dom/canvas/WebGLVertexAttribData.h
@@ -20,26 +20,28 @@ struct WebGLVertexAttribData
         : buf(0)
         , stride(0)
         , size(4)
         , divisor(0) // OpenGL ES 3.0 specs paragraphe 6.2 p240
         , byteOffset(0)
         , type(LOCAL_GL_FLOAT)
         , enabled(false)
         , normalized(false)
+        , integer(false)
     {}
 
     WebGLRefPtr<WebGLBuffer> buf;
     GLuint stride;
     GLuint size;
     GLuint divisor;
     GLuint byteOffset;
     GLenum type;
     bool enabled;
     bool normalized;
+    bool integer;
 
     GLuint componentSize() const {
         switch(type) {
         case LOCAL_GL_BYTE:
             return sizeof(GLbyte);
 
         case LOCAL_GL_UNSIGNED_BYTE:
             return sizeof(GLubyte);
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -37,16 +37,17 @@ UNIFIED_SOURCES += [
     'DocumentRendererParent.cpp',
     'ImageData.cpp',
 ]
 
 # WebGL Sources
 UNIFIED_SOURCES += [
     'MurmurHash3.cpp',
     'WebGL1Context.cpp',
+    'WebGL1ContextUniforms.cpp',
     'WebGL2Context.cpp',
     'WebGL2ContextBuffers.cpp',
     'WebGL2ContextDraw.cpp',
     'WebGL2ContextFramebuffers.cpp',
     'WebGL2ContextMRTs.cpp',
     'WebGL2ContextPrograms.cpp',
     'WebGL2ContextQueries.cpp',
     'WebGL2ContextSamplers.cpp',
--- a/dom/events/test/test_error_events.html
+++ b/dom/events/test/test_error_events.html
@@ -1,15 +1,16 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <title>Test for error events being ErrorEvent</title>
 <script src="/resources/testharness.js"></script>
 <script src="/resources/testharnessreport.js"></script>
 <div id="log"></div>
 <script>
+  setup({allow_uncaught_exception:true});
   var errorEvent;
   var file;
   var line;
   var msg;
   var column;
   var error;
   window.addEventListener("error", function errorListener(e) {
     window.removeEventListener("error", errorListener);
@@ -26,18 +27,18 @@
   }
   var thrown = new Error("hello");
   throw thrown;
 </script>
 <script>
   generate_tests(assert_equals, [
     [ "Event filename", errorEvent.filename, location.href ],
     [ "Callback filename", file, location.href ],
-    [ "Event line number", errorEvent.lineno, 27 ],
-    [ "Callback line number", line, 27 ],
+    [ "Event line number", errorEvent.lineno, 28 ],
+    [ "Callback line number", line, 28 ],
     [ "Event message", errorEvent.message, "Error: hello" ],
     [ "Callback message", msg, "Error: hello" ],
     [ "Event error-object", errorEvent.error, thrown],
     [ "Callback error-object", error, thrown ],
     [ "Event column", errorEvent.colno, 15 ],
     [ "Callback column", column, 15 ]
   ]);
 </script>
--- a/dom/imptests/WebIDLParser.js
+++ b/dom/imptests/WebIDLParser.js
@@ -26,17 +26,17 @@
                 if (matched) break;
             }
             if (matched) continue;
             throw new Error("Token stream not progressing");
         }
         return tokens;
     };
     
-    var parse = function (tokens) {
+    var parse = function (tokens, opt) {
         var line = 1;
         tokens = tokens.slice();
         
         var FLOAT = "float"
         ,   INT = "integer"
         ,   ID = "identifier"
         ,   STR = "string"
         ,   OTHER = "other"
@@ -77,24 +77,51 @@
             if (!tokens.length) return;
             if (tokens[0].type === "whitespace") {
                 var t = tokens.shift();
                 t.value.replace(/\n/g, function (m) { line++; return m; });
                 return t;
             }
         };
         
-        var all_ws = function () {
+        var all_ws = function (store, pea) { // pea == post extended attribute, tpea = same for types
             var t = { type: "whitespace", value: "" };
             while (true) {
                 var w = ws();
                 if (!w) break;
                 t.value += w.value;
             }
-            if (t.value.length > 0) return t;
+            if (t.value.length > 0) {
+                if (store) {
+                    var w = t.value
+                    ,   re = {
+                            "ws":                   /^([\t\n\r ]+)/
+                        ,   "line-comment":         /^\/\/(.*)\n?/m
+                        ,   "multiline-comment":    /^\/\*((?:.|\n|\r)*?)\*\//
+                        }
+                    ,   wsTypes = []
+                    ;
+                    for (var k in re) wsTypes.push(k);
+                    while (w.length) {
+                        var matched = false;
+                        for (var i = 0, n = wsTypes.length; i < n; i++) {
+                            var type = wsTypes[i];
+                            w = w.replace(re[type], function (tok, m1) {
+                                store.push({ type: type + (pea ? ("-" + pea) : ""), value: m1 });
+                                matched = true;
+                                return "";
+                            });
+                            if (matched) break;
+                        }
+                        if (matched) continue;
+                        throw new Error("Surprising white space construct."); // this shouldn't happen
+                    }
+                }
+                return t;
+            }
         };
         
         var integer_type = function () {
             var ret = "";
             all_ws();
             if (consume(ID, "unsigned")) ret = "unsigned ";
             all_ws();
             if (consume(ID, "short")) return ret + "short";
@@ -146,59 +173,72 @@
                 all_ws();
                 if (consume(OTHER, "?")) {
                     if (obj.nullable) error("Can't nullable more than once");
                     obj.nullable = true;
                 }
                 else if (consume(OTHER, "[")) {
                     all_ws();
                     consume(OTHER, "]") || error("Unterminated array type");
-                    if (!obj.array) obj.array = 1;
-                    else obj.array++;
+                    if (!obj.array) {
+                        obj.array = 1;
+                        obj.nullableArray = [obj.nullable];
+                    }
+                    else {
+                        obj.array++;
+                        obj.nullableArray.push(obj.nullable);
+                    }
+                    obj.nullable = false;
                 }
                 else return;
             }
         };
         
         var single_type = function () {
             var prim = primitive_type()
-            ,   ret = { sequence: false, nullable: false, array: false, union: false }
+            ,   ret = { sequence: false, generic: null, nullable: false, array: false, union: false }
+            ,   name
+            ,   value
             ;
             if (prim) {
                 ret.idlType = prim;
             }
-            else if (consume(ID, "sequence")) {
+            else if (name = consume(ID)) {
+                value = name.value;
                 all_ws();
-                if (!consume(OTHER, "<")) {
-                    ret.idlType = "sequence";
-                }
-                else {
-                    ret.sequence = true;
-                    ret.idlType = type() || error("Error parsing sequence type");
+                // Generic types
+                if (consume(OTHER, "<")) {
+                    // backwards compat
+                    if (value === "sequence") {
+                        ret.sequence = true;
+                    }
+                    ret.generic = value;
+                    ret.idlType = type() || error("Error parsing generic type " + value);
                     all_ws();
-                    if (!consume(OTHER, ">")) error("Unterminated sequence");
+                    if (!consume(OTHER, ">")) error("Unterminated generic type " + value);
                     all_ws();
                     if (consume(OTHER, "?")) ret.nullable = true;
                     return ret;
                 }
+                else {
+                    ret.idlType = value;
+                }
             }
             else {
-                var name = consume(ID);
-                if (!name) return;
-                ret.idlType = name.value;
+                return;
             }
             type_suffix(ret);
-            if (ret.nullable && ret.idlType === "any") error("Type any cannot be made nullable");
+            if (ret.nullable && !ret.array && ret.idlType === "any") error("Type any cannot be made nullable");
             return ret;
         };
         
         var union_type = function () {
             all_ws();
             if (!consume(OTHER, "(")) return;
-            var ret = { sequence: false, nullable: false, array: false, union: true, idlType: [] };
+            var ret = { sequence: false, generic: null, nullable: false, array: false, union: true, idlType: [] };
             var fst = type() || error("Union type with no content");
             ret.idlType.push(fst);
             while (true) {
                 all_ws();
                 if (!consume(ID, "or")) break;
                 var typ = type() || error("No type after 'or' in union type");
                 ret.idlType.push(typ);
             }
@@ -206,97 +246,131 @@
             type_suffix(ret);
             return ret;
         };
         
         var type = function () {
             return single_type() || union_type();
         };
         
-        var argument = function () {
+        var argument = function (store) {
             var ret = { optional: false, variadic: false };
-            ret.extAttrs = extended_attrs();
-            all_ws();
-            if (consume(ID, "optional")) {
+            ret.extAttrs = extended_attrs(store);
+            all_ws(store, "pea");
+            var opt_token = consume(ID, "optional");
+            if (opt_token) {
                 ret.optional = true;
                 all_ws();
             }
             ret.idlType = type();
-            if (!ret.idlType) return;
+            if (!ret.idlType) {
+                if (opt_token) tokens.unshift(opt_token);
+                return;
+            }
+            var type_token = last_token;
             if (!ret.optional) {
                 all_ws();
                 if (tokens.length >= 3 &&
                     tokens[0].type === "other" && tokens[0].value === "." &&
                     tokens[1].type === "other" && tokens[1].value === "." &&
                     tokens[2].type === "other" && tokens[2].value === "."
                     ) {
                     tokens.shift();
                     tokens.shift();
                     tokens.shift();
                     ret.variadic = true;
                 }
             }
             all_ws();
-            var name = consume(ID) || error("No name in argument");
+            var name = consume(ID);
+            if (!name) {
+                if (opt_token) tokens.unshift(opt_token);
+                tokens.unshift(type_token);
+                return;
+            }
             ret.name = name.value;
             if (ret.optional) {
                 all_ws();
                 ret["default"] = default_();
             }
             return ret;
         };
         
-        var argument_list = function () {
-            var arg = argument(), ret = [];
-            if (!arg) return ret;
+        var argument_list = function (store) {
+            var ret = []
+            ,   arg = argument(store ? ret : null)
+            ;
+            if (!arg) return;
             ret.push(arg);
             while (true) {
-                all_ws();
+                all_ws(store ? ret : null);
                 if (!consume(OTHER, ",")) return ret;
-                all_ws();
-                var nxt = argument() || error("Trailing comma in arguments list");
+                var nxt = argument(store ? ret : null) || error("Trailing comma in arguments list");
                 ret.push(nxt);
             }
         };
         
-        var simple_extended_attr = function () {
+        var type_pair = function () {
+            all_ws();
+            var k = type();
+            if (!k) return;
+            all_ws()
+            if (!consume(OTHER, ",")) return;
+            all_ws();
+            var v = type();
+            if (!v) return;
+            return [k, v];
+        };
+        
+        var simple_extended_attr = function (store) {
             all_ws();
             var name = consume(ID);
             if (!name) return;
             var ret = {
                 name: name.value
             ,   "arguments": null
             };
             all_ws();
             var eq = consume(OTHER, "=");
             if (eq) {
                 all_ws();
                 ret.rhs = consume(ID);
                 if (!ret.rhs) return error("No right hand side to extended attribute assignment");
             }
             all_ws();
             if (consume(OTHER, "(")) {
-                ret["arguments"] = argument_list();
+                var args, pair;
+                // [Constructor(DOMString str)]
+                if (args = argument_list(store)) {
+                    ret["arguments"] = args;
+                }
+                // [MapClass(DOMString, DOMString)]
+                else if (pair = type_pair()) {
+                    ret.typePair = pair;
+                }
+                // [Constructor()]
+                else {
+                    ret["arguments"] = [];
+                }
                 all_ws();
-                consume(OTHER, ")") || error("Unclosed argument in extended attribute");
+                consume(OTHER, ")") || error("Unexpected token in extended attribute argument list or type pair");
             }
             return ret;
         };
         
         // Note: we parse something simpler than the official syntax. It's all that ever
         // seems to be used
-        var extended_attrs = function () {
+        var extended_attrs = function (store) {
             var eas = [];
-            all_ws();
+            all_ws(store);
             if (!consume(OTHER, "[")) return eas;
-            eas[0] = simple_extended_attr() || error("Extended attribute with not content");
+            eas[0] = simple_extended_attr(store) || error("Extended attribute with not content");
             all_ws();
             while (consume(OTHER, ",")) {
-                all_ws();
-                eas.push(simple_extended_attr() || error("Trailing comma in extended attribute"));
+                eas.push(simple_extended_attr(store) || error("Trailing comma in extended attribute"));
                 all_ws();
             }
             consume(OTHER, "]") || error("No end of extended attribute");
             return eas;
         };
         
         var default_ = function () {
             all_ws();
@@ -309,18 +383,18 @@
                 else {
                     var str = consume(STR) || error("No value for default");
                     str.value = str.value.replace(/^"/, "").replace(/"$/, "");
                     return str;
                 }
             }
         };
         
-        var const_ = function () {
-            all_ws();
+        var const_ = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "const")) return;
             var ret = { type: "const", nullable: false };
             all_ws();
             var typ = primitive_type();
             if (!typ) {
                 typ = consume(ID) || error("No type for const");
                 typ = typ.value;
             }
@@ -347,33 +421,33 @@
             all_ws();
             if (consume(OTHER, ":")) {
                 all_ws();
                 var inh = consume(ID) || error ("No type in inheritance");
                 return inh.value;
             }
         };
         
-        var operation_rest = function (ret) {
+        var operation_rest = function (ret, store) {
             all_ws();
             if (!ret) ret = {};
             var name = consume(ID);
             ret.name = name ? name.value : null;
             all_ws();
             consume(OTHER, "(") || error("Invalid operation");
-            ret["arguments"] = argument_list();
+            ret["arguments"] = argument_list(store) || [];
             all_ws();
             consume(OTHER, ")") || error("Unterminated operation");
             all_ws();
             consume(OTHER, ";") || error("Unterminated operation");
             return ret;
         };
         
-        var callback = function () {
-            all_ws();
+        var callback = function (store) {
+            all_ws(store, "pea");
             var ret;
             if (!consume(ID, "callback")) return;
             all_ws();
             var tok = consume(ID, "interface");
             if (tok) {
                 tokens.unshift(tok);
                 ret = interface_();
                 ret.type = "callback interface";
@@ -382,26 +456,26 @@
             var name = consume(ID) || error("No name for callback");
             ret = { type: "callback", name: name.value };
             all_ws();
             consume(OTHER, "=") || error("No assignment in callback");
             all_ws();
             ret.idlType = return_type();
             all_ws();
             consume(OTHER, "(") || error("No arguments in callback");
-            ret["arguments"] = argument_list();
+            ret["arguments"] = argument_list(store) || [];
             all_ws();
             consume(OTHER, ")") || error("Unterminated callback");
             all_ws();
             consume(OTHER, ";") || error("Unterminated callback");
             return ret;
         };
 
-        var attribute = function () {
-            all_ws();
+        var attribute = function (store) {
+            all_ws(store, "pea");
             var grabbed = []
             ,   ret = {
                 type:           "attribute"
             ,   "static":       false
             ,   stringifier:    false
             ,   inherit:        false
             ,   readonly:       false
             };
@@ -449,18 +523,18 @@
                 if (consume(ID, "void")) {
                     return "void";
                 }
                 else error("No return type");
             }
             return typ;
         };
         
-        var operation = function () {
-            all_ws();
+        var operation = function (store) {
+            all_ws(store, "pea");
             var ret = {
                 type:           "operation"
             ,   getter:         false
             ,   setter:         false
             ,   creator:        false
             ,   deleter:        false
             ,   legacycaller:   false
             ,   "static":       false
@@ -473,31 +547,31 @@
                 else if (consume(ID, "creator")) ret.creator = true;
                 else if (consume(ID, "deleter")) ret.deleter = true;
                 else if (consume(ID, "legacycaller")) ret.legacycaller = true;
                 else break;
             }
             if (ret.getter || ret.setter || ret.creator || ret.deleter || ret.legacycaller) {
                 all_ws();
                 ret.idlType = return_type();
-                operation_rest(ret);
+                operation_rest(ret, store);
                 return ret;
             }
             if (consume(ID, "static")) {
                 ret["static"] = true;
                 ret.idlType = return_type();
-                operation_rest(ret);
+                operation_rest(ret, store);
                 return ret;
             }
             else if (consume(ID, "stringifier")) {
                 ret.stringifier = true;
                 all_ws();
                 if (consume(OTHER, ";")) return ret;
                 ret.idlType = return_type();
-                operation_rest(ret);
+                operation_rest(ret, store);
                 return ret;
             }
             ret.idlType = return_type();
             all_ws();
             if (consume(ID, "iterator")) {
                 all_ws();
                 ret.type = "iterator";
                 if (consume(ID, "object")) {
@@ -508,35 +582,35 @@
                     var name = consume(ID) || error("No right hand side in iterator");
                     ret.iteratorObject = name.value;
                 }
                 all_ws();
                 consume(OTHER, ";") || error("Unterminated iterator");
                 return ret;
             }
             else {
-                operation_rest(ret);
+                operation_rest(ret, store);
                 return ret;
             }
         };
         
         var identifiers = function (arr) {
             while (true) {
                 all_ws();
                 if (consume(OTHER, ",")) {
                     all_ws();
                     var name = consume(ID) || error("Trailing comma in identifiers list");
                     arr.push(name.value);
                 }
                 else break;
             }
         };
         
-        var serialiser = function () {
-            all_ws();
+        var serialiser = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "serializer")) return;
             var ret = { type: "serializer" };
             all_ws();
             if (consume(OTHER, "=")) {
                 all_ws();
                 if (consume(OTHER, "{")) {
                     ret.patternMap = true;
                     all_ws();
@@ -584,124 +658,132 @@
                 return ret;
             }
             else if (consume(OTHER, ";")) {
                 // noop, just parsing
             }
             else {
                 ret.idlType = return_type();
                 all_ws();
-                ret.operation = operation_rest();
+                ret.operation = operation_rest(null, store);
             }
             return ret;
         };
         
-        var interface_ = function (isPartial) {
-            all_ws();
+        var interface_ = function (isPartial, store) {
+            all_ws(isPartial ? null : store, "pea");
             if (!consume(ID, "interface")) return;
             all_ws();
             var name = consume(ID) || error("No name for interface");
-            var ret = {
+            var mems = []
+            ,   ret = {
                 type:   "interface"
             ,   name:   name.value
             ,   partial:    false
-            ,   members:    []
+            ,   members:    mems
             };
             if (!isPartial) ret.inheritance = inheritance() || null;
             all_ws();
             consume(OTHER, "{") || error("Bodyless interface");
             while (true) {
-                all_ws();
+                all_ws(store ? mems : null);
                 if (consume(OTHER, "}")) {
                     all_ws();
                     consume(OTHER, ";") || error("Missing semicolon after interface");
                     return ret;
                 }
-                var ea = extended_attrs();
+                var ea = extended_attrs(store ? mems : null);
                 all_ws();
-                var cnt = const_();
+                var cnt = const_(store ? mems : null);
                 if (cnt) {
                     cnt.extAttrs = ea;
                     ret.members.push(cnt);
                     continue;
                 }
-                var mem = serialiser() || attribute() || operation() || error("Unknown member");
+                var mem = serialiser(store ? mems : null) ||
+                          attribute(store ? mems : null) ||
+                          operation(store ? mems : null) ||
+                          error("Unknown member");
                 mem.extAttrs = ea;
                 ret.members.push(mem);
             }
         };
         
-        var partial = function () {
-            all_ws();
+        var partial = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "partial")) return;
-            var thing = dictionary(true) || interface_(true) || error("Partial doesn't apply to anything");
+            var thing = dictionary(true, store) ||
+                        interface_(true, store) ||
+                        error("Partial doesn't apply to anything");
             thing.partial = true;
             return thing;
         };
         
-        var dictionary = function (isPartial) {
-            all_ws();
+        var dictionary = function (isPartial, store) {
+            all_ws(isPartial ? null : store, "pea");
             if (!consume(ID, "dictionary")) return;
             all_ws();
             var name = consume(ID) || error("No name for dictionary");
-            var ret = {
+            var mems = []
+            ,   ret = {
                 type:   "dictionary"
             ,   name:   name.value
             ,   partial:    false
-            ,   members:    []
+            ,   members:    mems
             };
             if (!isPartial) ret.inheritance = inheritance() || null;
             all_ws();
             consume(OTHER, "{") || error("Bodyless dictionary");
             while (true) {
-                all_ws();
+                all_ws(store ? mems : null);
                 if (consume(OTHER, "}")) {
                     all_ws();
                     consume(OTHER, ";") || error("Missing semicolon after dictionary");
                     return ret;
                 }
-                var ea = extended_attrs();
-                all_ws();
+                var ea = extended_attrs(store ? mems : null);
+                all_ws(store ? mems : null, "pea");
                 var typ = type() || error("No type for dictionary member");
                 all_ws();
                 var name = consume(ID) || error("No name for dictionary member");
                 ret.members.push({
                     type:       "field"
                 ,   name:       name.value
                 ,   idlType:    typ
                 ,   extAttrs:   ea
                 ,   "default":  default_()
                 });
                 all_ws();
                 consume(OTHER, ";") || error("Unterminated dictionary member");
             }
         };
         
-        var exception = function () {
-            all_ws();
+        var exception = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "exception")) return;
             all_ws();
             var name = consume(ID) || error("No name for exception");
-            var ret = {
+            var mems = []
+            ,   ret = {
                 type:   "exception"
             ,   name:   name.value
-            ,   members:    []
+            ,   members:    mems
             };
             ret.inheritance = inheritance() || null;
             all_ws();
             consume(OTHER, "{") || error("Bodyless exception");
             while (true) {
-                all_ws();
+                all_ws(store ? mems : null);
                 if (consume(OTHER, "}")) {
                     all_ws();
                     consume(OTHER, ";") || error("Missing semicolon after exception");
                     return ret;
                 }
-                var ea = extended_attrs();
-                all_ws();
+                var ea = extended_attrs(store ? mems : null);
+                all_ws(store ? mems : null, "pea");
                 var cnt = const_();
                 if (cnt) {
                     cnt.extAttrs = ea;
                     ret.members.push(cnt);
                 }
                 else {
                     var typ = type();
                     all_ws();
@@ -713,70 +795,72 @@
                     ,   name:       name.value
                     ,   idlType:    typ
                     ,   extAttrs:   ea
                     });
                 }
             }
         };
         
-        var enum_ = function () {
-            all_ws();
+        var enum_ = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "enum")) return;
             all_ws();
             var name = consume(ID) || error("No name for enum");
-            var ret = {
+            var vals = []
+            ,   ret = {
                 type:   "enum"
             ,   name:   name.value
-            ,   values: []
+            ,   values: vals
             };
             all_ws();
             consume(OTHER, "{") || error("No curly for enum");
             var saw_comma = false;
             while (true) {
-                all_ws();
+                all_ws(store ? vals : null);
                 if (consume(OTHER, "}")) {
                     all_ws();
                     if (saw_comma) error("Trailing comma in enum");
                     consume(OTHER, ";") || error("No semicolon after enum");
                     return ret;
                 }
                 var val = consume(STR) || error("Unexpected value in enum");
                 ret.values.push(val.value.replace(/"/g, ""));
-                all_ws();
+                all_ws(store ? vals : null);
                 if (consume(OTHER, ",")) {
-                    all_ws();
+                    if (store) vals.push({ type: "," });
+                    all_ws(store ? vals : null);
                     saw_comma = true;
                 }
                 else {
                     saw_comma = false;
                 }
             }
         };
         
-        var typedef = function () {
-            all_ws();
+        var typedef = function (store) {
+            all_ws(store, "pea");
             if (!consume(ID, "typedef")) return;
             var ret = {
                 type:   "typedef"
             };
             all_ws();
             ret.typeExtAttrs = extended_attrs();
-            all_ws();
+            all_ws(store, "tpea");
             ret.idlType = type() || error("No type in typedef");
             all_ws();
             var name = consume(ID) || error("No name in typedef");
             ret.name = name.value;
             all_ws();
             consume(OTHER, ";") || error("Unterminated typedef");
             return ret;
         };
         
-        var implements_ = function () {
-            all_ws();
+        var implements_ = function (store) {
+            all_ws(store, "pea");
             var target = consume(ID);
             if (!target) return;
             var w = all_ws();
             if (consume(ID, "implements")) {
                 var ret = {
                     type:   "implements"
                 ,   target: target.value
                 };
@@ -789,53 +873,52 @@
             }
             else {
                 // rollback
                 tokens.unshift(w);
                 tokens.unshift(target);
             }
         };
         
-        var definition = function () {
-            return  callback()      ||
-                    interface_()    ||
-                    partial()       ||
-                    dictionary()    ||
-                    exception()     ||
-                    enum_()         ||
-                    typedef()       ||
-                    implements_()
+        var definition = function (store) {
+            return  callback(store)             ||
+                    interface_(false, store)    ||
+                    partial(store)              ||
+                    dictionary(false, store)    ||
+                    exception(store)            ||
+                    enum_(store)                ||
+                    typedef(store)              ||
+                    implements_(store)
                     ;
         };
         
-        var definitions = function () {
+        var definitions = function (store) {
             if (!tokens.length) return [];
             var defs = [];
             while (true) {
-                var ea = extended_attrs()
-                ,   def = definition();
+                var ea = extended_attrs(store ? defs : null)
+                ,   def = definition(store ? defs : null);
                 if (!def) {
                     if (ea.length) error("Stray extended attributes");
                     break;
                 }
                 def.extAttrs = ea;
                 defs.push(def);
             }
             return defs;
         };
-        var res = definitions();
+        var res = definitions(opt.ws);
         if (tokens.length) error("Unrecognised tokens");
         return res;
     };
 
-    var obj = {
-        parse:  function (str) {
-            var tokens = tokenise(str);
-            return parse(tokens);
-        }
+    var inNode = typeof module !== "undefined" && module.exports
+    ,   obj = {
+            parse:  function (str, opt) {
+                if (!opt) opt = {};
+                var tokens = tokenise(str);
+                return parse(tokens, opt);
+            }
     };
-    if (typeof module !== "undefined" && module.exports) {
-        module.exports = obj;
-    }
-    else {
-        window.WebIDL2 = obj;
-    }
+
+    if (inNode) module.exports = obj;
+    else        window.WebIDL2 = obj;
 }());
--- a/dom/imptests/editing/conformancetest/test_runtest.html
+++ b/dom/imptests/editing/conformancetest/test_runtest.html
@@ -15,16 +15,20 @@ for documentation.
 <script>var testsJsLibraryOnly = true</script>
 <script src=../tests.js></script>
 <script src=data.js></script>
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script>
 "use strict";
 
+if (W3CTest.runner) {
+	W3CTest.runner.requestLongerTimeout(2);
+}
+
 runTests();
 
 function runTests() {
 	var startTime = Date.now();
 
 	// Make document.body.innerHTML more tidy by removing unnecessary things.
 	// We can't remove the testharness.js script, because at the time of this
 	// writing, for some reason that stops it from adding appropriate CSS.
--- a/dom/imptests/idlharness.js
+++ b/dom/imptests/idlharness.js
@@ -3,141 +3,17 @@ Distributed under both the W3C Test Suit
 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
 policies and contribution forms [3].
 
 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
 [3] http://www.w3.org/2004/10/27-testcases
 */
 
-/*
- * This file automatically generates browser tests for WebIDL interfaces, using
- * the testharness.js framework.  To use, first include the following:
- *
- *   <script src=/resources/testharness.js></script>
- *   <script src=/resources/testharnessreport.js></script>
- *   <script src=/resources/WebIDLParser.js></script>
- *   <script src=/resources/idlharness.js></script>
- *
- * Then you'll need some type of IDLs.  Here's some script that can be run on a
- * spec written in HTML, which will grab all the elements with class="idl",
- * concatenate them, and replace the body so you can copy-paste:
- *
-     var s = "";
-     [].forEach.call(document.getElementsByClassName("idl"), function(idl) {
-       //https://www.w3.org/Bugs/Public/show_bug.cgi?id=14914
-       if (!idl.classList.contains("extract"))
-       {
-         s += idl.textContent + "\n\n";
-       }
-     });
-     document.body.innerHTML = '<pre></pre>';
-     document.body.firstChild.textContent = s;
- *
- * (TODO: write this in Python or something so that it can be done from the
- * command line instead.)
- *
- * Once you have that, put it in your script somehow.  The easiest way is to
- * embed it literally in an HTML file with <script type=text/plain> or similar,
- * so that you don't have to do any escaping.  Another possibility is to put it
- * in a separate .idl file that's fetched via XHR or similar.  Sample usage:
- *
- *   var idl_array = new IdlArray();
- *   idl_array.add_untested_idls("interface Node { readonly attribute DOMString nodeName; };");
- *   idl_array.add_idls("interface Document : Node { readonly attribute DOMString URL; };");
- *   idl_array.add_objects({Document: ["document"]});
- *   idl_array.test();
- *
- * This tests that window.Document exists and meets all the requirements of
- * WebIDL.  It also tests that window.document (the result of evaluating the
- * string "document") has URL and nodeName properties that behave as they
- * should, and otherwise meets WebIDL's requirements for an object whose
- * primary interface is Document.  It does not test that window.Node exists,
- * which is what you want if the Node interface is already tested in some other
- * specification's suite and your specification only extends or refers to it.
- * Of course, each IDL string can define many different things, and calls to
- * add_objects() can register many different objects for different interfaces:
- * this is a very simple example.
- *
- * TODO: Write assert_writable, assert_enumerable, assert_configurable and
- * their inverses, and use those instead of just checking
- * getOwnPropertyDescriptor.
- *
- * == Public methods of IdlArray ==
- *
- * IdlArray objects can be obtained with new IdlArray().  Anything not
- * documented in this section should be considered an implementation detail,
- * and outside callers should not use it.
- *
- * add_idls(idl_string):
- *   Parses idl_string (throwing on parse error) and adds the results to the
- *   IdlArray.  All the definitions will be tested when you run test().  If
- *   some of the definitions refer to other definitions, those must be present
- *   too.  For instance, if idl_string says that Document inherits from Node,
- *   the Node interface must also have been provided in some call to add_idls()
- *   or add_untested_idls().
- *
- * add_untested_idls(idl_string):
- *   Like add_idls(), but the definitions will not be tested.  If an untested
- *   interface is added and then extended with a tested partial interface, the
- *   members of the partial interface will still be tested.  Also, all the
- *   members will still be tested for objects added with add_objects(), because
- *   you probably want to test that (for instance) window.document has all the
- *   properties from Node, not just Document, even if the Node interface itself
- *   is tested in a different test suite.
- *
- * add_objects(dict):
- *   dict should be an object whose keys are the names of interfaces or
- *   exceptions, and whose values are arrays of strings.  When an interface or
- *   exception is tested, every string registered for it with add_objects()
- *   will be evaluated, and tests will be run on the result to verify that it
- *   correctly implements that interface or exception.  This is the only way to
- *   test anything about [NoInterfaceObject] interfaces, and there are many
- *   tests that can't be run on any interface without an object to fiddle with.
- *
- *   The interface has to be the *primary* interface of all the objects
- *   provided.  For example, don't pass {Node: ["document"]}, but rather
- *   {Document: ["document"]}.  Assuming the Document interface was declared to
- *   inherit from Node, this will automatically test that document implements
- *   the Node interface too.
- *
- *   Warning: methods will be called on any provided objects, in a manner that
- *   WebIDL requires be safe.  For instance, if a method has mandatory
- *   arguments, the test suite will try calling it with too few arguments to
- *   see if it throws an exception.  If an implementation incorrectly runs the
- *   function instead of throwing, this might have side effects, possibly even
- *   preventing the test suite from running correctly.
- *
- * prevent_multiple_testing(name):
- *   This is a niche method for use in case you're testing many objects that
- *   implement the same interfaces, and don't want to retest the same
- *   interfaces every single time.  For instance, HTML defines many interfaces
- *   that all inherit from HTMLElement, so the HTML test suite has something
- *   like
- *     .add_objects({
- *         HTMLHtmlElement: ['document.documentElement'],
- *         HTMLHeadElement: ['document.head'],
- *         HTMLBodyElement: ['document.body'],
- *         ...
- *     })
- *   and so on for dozens of element types.  This would mean that it would
- *   retest that each and every one of those elements implements HTMLElement,
- *   Element, and Node, which would be thousands of basically redundant tests.
- *   The test suite therefore calls prevent_multiple_testing("HTMLElement").
- *   This means that once one object has been tested to implement HTMLElement
- *   and its ancestors, no other object will be.  Thus in the example code
- *   above, the harness would test that document.documentElement correctly
- *   implements HTMLHtmlElement, HTMLElement, Element, and Node; but
- *   document.head would only be tested for HTMLHeadElement, and so on for
- *   further objects.
- *
- * test():
- *   Run all tests.  This should be called after you've called all other
- *   methods to add IDLs and objects.
- */
+/* For user documentation see docs/idlharness.md */
 
 /**
  * Notes for people who want to edit this file (not just use it as a library):
  *
  * Most of the interesting stuff happens in the derived classes of IdlObject,
  * especially IdlInterface.  The entry point for all IdlObjects is .test(),
  * which is called by IdlArray.test().  An IdlObject is conceptually just
  * "thing we want to run tests on", and an IdlArray is an array of IdlObjects
@@ -545,16 +421,19 @@ IdlArray.prototype.assert_type_is = func
         case "double":
         case "unrestricted float":
         case "unrestricted double":
             // TODO: distinguish these cases
             assert_equals(typeof value, "number");
             return;
 
         case "DOMString":
+        case "ByteString":
+        case "USVString":
+            // TODO: https://github.com/w3c/testharness.js/issues/92
             assert_equals(typeof value, "string");
             return;
 
         case "object":
             assert_true(typeof value == "object" || typeof value == "function", "wrong type: not object or function");
             return;
     }
 
@@ -1308,211 +1187,296 @@ IdlInterface.prototype.test_self = funct
         assert_false(desc.enumerable, this.name + ".prototype.constructor is enumerable");
         assert_true(desc.configurable, this.name + ".prototype.constructor in not configurable");
         assert_equals(window[this.name].prototype.constructor, window[this.name],
                       this.name + '.prototype.constructor is not the same object as ' + this.name);
     }.bind(this), this.name + ' interface: existence and properties of interface prototype object\'s "constructor" property');
 };
 
 //@}
+IdlInterface.prototype.test_member_const = function(member)
+//@{
+{
+    test(function()
+    {
+        assert_own_property(window, this.name,
+                            "window does not have own property " + format_value(this.name));
+
+        // "For each constant defined on an interface A, there must be
+        // a corresponding property on the interface object, if it
+        // exists."
+        assert_own_property(window[this.name], member.name);
+        // "The value of the property is that which is obtained by
+        // converting the constant’s IDL value to an ECMAScript
+        // value."
+        assert_equals(window[this.name][member.name], constValue(member.value),
+                      "property has wrong value");
+        // "The property has attributes { [[Writable]]: false,
+        // [[Enumerable]]: true, [[Configurable]]: false }."
+        var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_false(desc.writable, "property is writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_false(desc.configurable, "property is configurable");
+    }.bind(this), this.name + " interface: constant " + member.name + " on interface object");
+    // "In addition, a property with the same characteristics must
+    // exist on the interface prototype object."
+    test(function()
+    {
+        assert_own_property(window, this.name,
+                            "window does not have own property " + format_value(this.name));
+
+        if (this.has_extended_attribute("Callback")) {
+            assert_false("prototype" in window[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(window[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        assert_own_property(window[this.name].prototype, member.name);
+        assert_equals(window[this.name].prototype[member.name], constValue(member.value),
+                      "property has wrong value");
+        var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_false(desc.writable, "property is writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_false(desc.configurable, "property is configurable");
+    }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
+};
+
+
+//@}
+IdlInterface.prototype.test_member_attribute = function(member)
+//@{
+{
+    test(function()
+    {
+        assert_own_property(window, this.name,
+                            "window does not have own property " + format_value(this.name));
+        assert_own_property(window[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        if (member["static"]) {
+            assert_own_property(window[this.name], member.name,
+                "The interface object must have a property " +
+                format_value(member.name));
+        }
+        else
+        {
+            assert_true(member.name in window[this.name].prototype,
+                "The prototype object must have a property " +
+                format_value(member.name));
+
+            if (!member.has_extended_attribute("LenientThis")) {
+                assert_throws(new TypeError(), function() {
+                    window[this.name].prototype[member.name];
+                }.bind(this), "getting property on prototype object must throw TypeError");
+            } else {
+                assert_equals(window[this.name].prototype[member.name], undefined,
+                              "getting property on prototype object must return undefined");
+            }
+            do_interface_attribute_asserts(window[this.name].prototype, member);
+        }
+    }.bind(this), this.name + " interface: attribute " + member.name);
+};
+
+//@}
+IdlInterface.prototype.test_member_operation = function(member)
+//@{
+{
+    test(function()
+    {
+        assert_own_property(window, this.name,
+                            "window does not have own property " + format_value(this.name));
+
+        if (this.has_extended_attribute("Callback")) {
+            assert_false("prototype" in window[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(window[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // "For each unique identifier of an operation defined on the
+        // interface, there must be a corresponding property on the
+        // interface prototype object (if it is a regular operation) or
+        // the interface object (if it is a static operation), unless
+        // the effective overload set for that identifier and operation
+        // and with an argument count of 0 (for the ECMAScript language
+        // binding) has no entries."
+        //
+        var prototypeOrInterfaceObject;
+        if (member["static"]) {
+            assert_own_property(window[this.name], member.name,
+                    "interface prototype object missing static operation");
+            prototypeOrInterfaceObject = window[this.name];
+        }
+        else
+        {
+            assert_own_property(window[this.name].prototype, member.name,
+                    "interface prototype object missing non-static operation");
+            prototypeOrInterfaceObject = window[this.name].prototype;
+        }
+
+        var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
+        // "The property has attributes { [[Writable]]: true,
+        // [[Enumerable]]: true, [[Configurable]]: true }."
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_true(desc.writable, "property is not writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_true(desc.configurable, "property is not configurable");
+        // "The value of the property is a Function object whose
+        // behavior is as follows . . ."
+        assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
+                      "property must be a function");
+        // "The value of the Function object’s “length” property is
+        // a Number determined as follows:
+        // ". . .
+        // "Return the length of the shortest argument list of the
+        // entries in S."
+        //
+        // TODO: Doesn't handle overloading or variadic arguments.
+        assert_equals(prototypeOrInterfaceObject[member.name].length,
+            member.arguments.filter(function(arg) {
+                return !arg.optional;
+            }).length,
+            "property has wrong .length");
+
+        // Make some suitable arguments
+        var args = member.arguments.map(function(arg) {
+            return create_suitable_object(arg.idlType);
+        });
+
+        // "Let O be a value determined as follows:
+        // ". . .
+        // "Otherwise, throw a TypeError."
+        // This should be hit if the operation is not static, there is
+        // no [ImplicitThis] attribute, and the this value is null.
+        //
+        // TODO: We currently ignore the [ImplicitThis] case.
+        if (!member["static"]) {
+            assert_throws(new TypeError(), function() {
+                window[this.name].prototype[member.name].apply(null, args);
+            }, "calling operation with this = null didn't throw TypeError");
+        }
+
+        // ". . . If O is not null and is also not a platform object
+        // that implements interface I, throw a TypeError."
+        //
+        // TODO: Test a platform object that implements some other
+        // interface.  (Have to be sure to get inheritance right.)
+        assert_throws(new TypeError(), function() {
+            window[this.name].prototype[member.name].apply({}, args);
+        }, "calling operation with this = {} didn't throw TypeError");
+    }.bind(this), this.name + " interface: operation " + member.name +
+    "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
+    ")");
+};
+
+//@}
+IdlInterface.prototype.test_member_stringifier = function(member)
+//@{
+{
+    test(function()
+    {
+        assert_own_property(window, this.name,
+                            "window does not have own property " + format_value(this.name));
+
+        if (this.has_extended_attribute("Callback")) {
+            assert_false("prototype" in window[this.name],
+                         this.name + ' should not have a "prototype" property');
+            return;
+        }
+
+        assert_own_property(window[this.name], "prototype",
+                            'interface "' + this.name + '" does not have own property "prototype"');
+
+        // ". . . the property exists on the interface prototype object."
+        var interfacePrototypeObject = window[this.name].prototype;
+        assert_own_property(window[this.name].prototype, "toString",
+                "interface prototype object missing non-static operation");
+
+        var desc = Object.getOwnPropertyDescriptor(interfacePrototypeObject, "toString");
+        // "The property has attributes { [[Writable]]: B,
+        // [[Enumerable]]: true, [[Configurable]]: B }, where B is false if the
+        // stringifier is unforgeable on the interface, and true otherwise."
+        assert_false("get" in desc, "property has getter");
+        assert_false("set" in desc, "property has setter");
+        assert_true(desc.writable, "property is not writable");
+        assert_true(desc.enumerable, "property is not enumerable");
+        assert_true(desc.configurable, "property is not configurable");
+        // "The value of the property is a Function object, which behaves as
+        // follows . . ."
+        assert_equals(typeof interfacePrototypeObject.toString, "function",
+                      "property must be a function");
+        // "The value of the Function object’s “length” property is the Number
+        // value 0."
+        assert_equals(interfacePrototypeObject.toString.length, 0,
+            "property has wrong .length");
+
+        // "Let O be the result of calling ToObject on the this value."
+        assert_throws(new TypeError(), function() {
+            window[this.name].prototype.toString.apply(null, []);
+        }, "calling stringifier with this = null didn't throw TypeError");
+
+        // "If O is not an object that implements the interface on which the
+        // stringifier was declared, then throw a TypeError."
+        //
+        // TODO: Test a platform object that implements some other
+        // interface.  (Have to be sure to get inheritance right.)
+        assert_throws(new TypeError(), function() {
+            window[this.name].prototype.toString.apply({}, []);
+        }, "calling stringifier with this = {} didn't throw TypeError");
+    }.bind(this), this.name + " interface: stringifier");
+};
+
+//@}
 IdlInterface.prototype.test_members = function()
 //@{
 {
     for (var i = 0; i < this.members.length; i++)
     {
         var member = this.members[i];
-        if (member.untested)
-        {
+        if (member.untested) {
             continue;
         }
-        if (member.type == "const")
-        {
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
 
-                // "For each constant defined on an interface A, there must be
-                // a corresponding property on the interface object, if it
-                // exists."
-                assert_own_property(window[this.name], member.name);
-                // "The value of the property is that which is obtained by
-                // converting the constant’s IDL value to an ECMAScript
-                // value."
-                assert_equals(window[this.name][member.name], constValue(member.value),
-                              "property has wrong value");
-                // "The property has attributes { [[Writable]]: false,
-                // [[Enumerable]]: true, [[Configurable]]: false }."
-                var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
-                assert_false("get" in desc, "property has getter");
-                assert_false("set" in desc, "property has setter");
-                assert_false(desc.writable, "property is writable");
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_false(desc.configurable, "property is configurable");
-            }.bind(this), this.name + " interface: constant " + member.name + " on interface object");
-            // "In addition, a property with the same characteristics must
-            // exist on the interface prototype object."
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-
-                if (this.has_extended_attribute("Callback")) {
-                    assert_false("prototype" in window[this.name],
-                                 this.name + ' should not have a "prototype" property');
-                    return;
-                }
-
-                assert_own_property(window[this.name], "prototype",
-                                    'interface "' + this.name + '" does not have own property "prototype"');
+        switch (member.type) {
+        case "const":
+            this.test_member_const(member);
+            break;
 
-                assert_own_property(window[this.name].prototype, member.name);
-                assert_equals(window[this.name].prototype[member.name], constValue(member.value),
-                              "property has wrong value");
-                var desc = Object.getOwnPropertyDescriptor(window[this.name], member.name);
-                assert_false("get" in desc, "property has getter");
-                assert_false("set" in desc, "property has setter");
-                assert_false(desc.writable, "property is writable");
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_false(desc.configurable, "property is configurable");
-            }.bind(this), this.name + " interface: constant " + member.name + " on interface prototype object");
-        }
-        else if (member.type == "attribute")
-        {
-            if (member.has_extended_attribute("Unforgeable"))
-            {
-                // We do the checks in test_interface_of instead
-                continue;
+        case "attribute":
+            // For unforgeable attributes, we do the checks in
+            // test_interface_of instead.
+            if (!member.has_extended_attribute("Unforgeable")) {
+                this.test_member_attribute(member);
             }
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-                assert_own_property(window[this.name], "prototype",
-                                    'interface "' + this.name + '" does not have own property "prototype"');
+            break;
 
-                if (member["static"]) {
-                    assert_own_property(window[this.name], member.name,
-                        "The interface object must have a property " +
-                        format_value(member.name));
-                }
-                else
-                {
-                    assert_true(member.name in window[this.name].prototype,
-                        "The prototype object must have a property " +
-                        format_value(member.name));
-
-                    // TODO: Needs to test for LenientThis.
-                    assert_throws(new TypeError(), function() {
-                        window[this.name].prototype[member.name];
-                    }.bind(this), "getting property on prototype object must throw TypeError");
-                    do_interface_attribute_asserts(window[this.name].prototype, member);
-                }
-            }.bind(this), this.name + " interface: attribute " + member.name);
-        }
-        else if (member.type == "operation")
-        {
+        case "operation":
             // TODO: Need to correctly handle multiple operations with the same
             // identifier.
-            if (!member.name)
-            {
-                // Unnamed getter or such
-                continue;
+            if (member.name) {
+                this.test_member_operation(member);
+            } else if (member.stringifier) {
+                this.test_member_stringifier(member);
             }
-            test(function()
-            {
-                assert_own_property(window, this.name,
-                                    "window does not have own property " + format_value(this.name));
-
-                if (this.has_extended_attribute("Callback")) {
-                    assert_false("prototype" in window[this.name],
-                                 this.name + ' should not have a "prototype" property');
-                    return;
-                }
-
-                assert_own_property(window[this.name], "prototype",
-                                    'interface "' + this.name + '" does not have own property "prototype"');
-
-                // "For each unique identifier of an operation defined on the
-                // interface, there must be a corresponding property on the
-                // interface prototype object (if it is a regular operation) or
-                // the interface object (if it is a static operation), unless
-                // the effective overload set for that identifier and operation
-                // and with an argument count of 0 (for the ECMAScript language
-                // binding) has no entries."
-                //
-                var prototypeOrInterfaceObject;
-                if (member["static"]) {
-                    assert_own_property(window[this.name], member.name,
-                            "interface prototype object missing static operation");
-                    prototypeOrInterfaceObject = window[this.name];
-                }
-                else
-                {
-                    assert_own_property(window[this.name].prototype, member.name,
-                            "interface prototype object missing non-static operation");
-                    prototypeOrInterfaceObject = window[this.name].prototype;
-                }
+            break;
 
-                var desc = Object.getOwnPropertyDescriptor(prototypeOrInterfaceObject, member.name);
-                // "The property has attributes { [[Writable]]: true,
-                // [[Enumerable]]: true, [[Configurable]]: true }."
-                assert_false("get" in desc, "property has getter");
-                assert_false("set" in desc, "property has setter");
-                assert_true(desc.writable, "property is not writable");
-                assert_true(desc.enumerable, "property is not enumerable");
-                assert_true(desc.configurable, "property is not configurable");
-                // "The value of the property is a Function object whose
-                // behavior is as follows . . ."
-                assert_equals(typeof prototypeOrInterfaceObject[member.name], "function",
-                              "property must be a function");
-                // "The value of the Function object’s “length” property is
-                // a Number determined as follows:
-                // ". . .
-                // "Return the length of the shortest argument list of the
-                // entries in S."
-                //
-                // TODO: Doesn't handle overloading or variadic arguments.
-                assert_equals(prototypeOrInterfaceObject[member.name].length,
-                    member.arguments.filter(function(arg) {
-                        return !arg.optional;
-                    }).length,
-                    "property has wrong .length");
-
-                // Make some suitable arguments
-                var args = member.arguments.map(function(arg) {
-                    return create_suitable_object(arg.idlType);
-                });
-
-                // "Let O be a value determined as follows:
-                // ". . .
-                // "Otherwise, throw a TypeError."
-                // This should be hit if the operation is not static, there is
-                // no [ImplicitThis] attribute, and the this value is null.
-                //
-                // TODO: We currently ignore the [ImplicitThis] case.
-                if (!member["static"]) {
-                    assert_throws(new TypeError(), function() {
-                        window[this.name].prototype[member.name].apply(null, args);
-                    }, "calling operation with this = null didn't throw TypeError");
-                }
-
-                // ". . . If O is not null and is also not a platform object
-                // that implements interface I, throw a TypeError."
-                //
-                // TODO: Test a platform object that implements some other
-                // interface.  (Have to be sure to get inheritance right.)
-                assert_throws(new TypeError(), function() {
-                    window[this.name].prototype[member.name].apply({}, args);
-                }, "calling operation with this = {} didn't throw TypeError");
-            }.bind(this), this.name + " interface: operation " + member.name +
-            "(" + member.arguments.map(function(m) { return m.idlType.idlType; }) +
-            ")");
+        default:
+            // TODO: check more member types.
+            break;
         }
-        // TODO: check more member types, like stringifier
     }
 };
 
 //@}
 IdlInterface.prototype.test_object = function(desc)
 //@{
 {
     var obj, exception = null;
@@ -1743,21 +1707,24 @@ function do_interface_attribute_asserts(
 
     // "The attribute getter is a Function object whose behavior when invoked
     // is as follows:
     // "...
     // "The value of the Function object’s “length” property is the Number
     // value 0."
     assert_equals(typeof desc.get, "function", "getter must be Function");
     assert_equals(desc.get.length, 0, "getter length must be 0");
-    // TODO: Account for LenientThis
-    assert_throws(new TypeError(), function()
-    {
-        desc.get.call({});
-    }.bind(this), "calling getter on wrong object type must throw TypeError");
+    if (!member.has_extended_attribute("LenientThis")) {
+        assert_throws(new TypeError(), function() {
+            desc.get.call({});
+        }.bind(this), "calling getter on wrong object type must throw TypeError");
+    } else {
+        assert_equals(desc.get.call({}), undefined,
+                      "calling getter on wrong object type must return undefined");
+    }
 
     // TODO: Test calling setter on the interface prototype (should throw
     // TypeError in most cases).
     //
     // "The attribute setter is undefined if the attribute is declared readonly
     // and has neither a [PutForwards] nor a [Replaceable] extended attribute
     // declared on it.  Otherwise, it is a Function object whose behavior when
     // invoked is as follows:
@@ -1769,16 +1736,24 @@ function do_interface_attribute_asserts(
     && !member.has_extended_attribute("Replaceable"))
     {
         assert_equals(desc.set, undefined, "setter must be undefined for readonly attributes");
     }
     else
     {
         assert_equals(typeof desc.set, "function", "setter must be function for PutForwards, Replaceable, or non-readonly attributes");
         assert_equals(desc.set.length, 1, "setter length must be 1");
+        if (!member.has_extended_attribute("LenientThis")) {
+            assert_throws(new TypeError(), function() {
+                desc.set.call({});
+            }.bind(this), "calling setter on wrong object type must throw TypeError");
+        } else {
+            assert_equals(desc.set.call({}), undefined,
+                          "calling setter on wrong object type must return undefined");
+        }
     }
 }
 //@}
 
 /// IdlInterfaceMember ///
 function IdlInterfaceMember(obj)
 //@{
 {
@@ -1821,16 +1796,18 @@ function create_suitable_object(type)
 
         case "byte": case "octet": case "short": case "unsigned short":
         case "long": case "unsigned long": case "long long":
         case "unsigned long long": case "float": case "double":
         case "unrestricted float": case "unrestricted double":
             return 7;
 
         case "DOMString":
+        case "ByteString":
+        case "USVString":
             return "foo";
 
         case "object":
             return {a: "b"};
 
         case "Node":
             return document.createTextNode("abc");
     }
--- a/dom/imptests/testharness.css
+++ b/dom/imptests/testharness.css
@@ -85,8 +85,23 @@ table#results span.expected {
     white-space:pre;
 }
 
 table#results span.actual {
     font-family:DejaVu Sans Mono, Bitstream Vera Sans Mono, Monospace;
     white-space:pre;
 }
 
+span.ok {
+    color:green;
+}
+
+tr.error {
+    color:red;
+}
+
+span.timeout {
+    color:red;
+}
+
+span.ok, span.timeout, span.error {
+    font-variant:small-caps;
+}
\ No newline at end of file
--- a/dom/imptests/testharness.js
+++ b/dom/imptests/testharness.js
@@ -1,475 +1,497 @@
+/*global self*/
+/*jshint latedef: nofunc*/
 /*
 Distributed under both the W3C Test Suite License [1] and the W3C
 3-clause BSD License [2]. To contribute to a W3C Test Suite, see the
 policies and contribution forms [3].
 
 [1] http://www.w3.org/Consortium/Legal/2008/04-testsuite-license
 [2] http://www.w3.org/Consortium/Legal/2008/03-bsd-license
 [3] http://www.w3.org/2004/10/27-testcases
 */
 
-/*
- * == Introduction ==
- *
- * This file provides a framework for writing testcases. It is intended to
- * provide a convenient API for making common assertions, and to work both
- * for testing synchronous and asynchronous DOM features in a way that
- * promotes clear, robust, tests.
- *
- * == Basic Usage ==
- *
- * To use this file, import the script and the testharnessreport script into
- * the test document:
- * <script src="/resources/testharness.js"></script>
- * <script src="/resources/testharnessreport.js"></script>
- *
- * Within each file one may define one or more tests. Each test is atomic
- * in the sense that a single test has a single result (pass/fail/timeout).
- * Within each test one may have a number of asserts. The test fails at the
- * first failing assert, and the remainder of the test is (typically) not run.
- *
- * If the file containing the tests is a HTML file with an element of id "log"
- * this will be populated with a table containing the test results after all
- * the tests have run.
- *
- * NOTE: By default tests must be created before the load event fires. For ways
- *       to create tests after the load event, see "Determining when all tests
- *       are complete", below
- *
- * == Synchronous Tests ==
- *
- * To create a synchronous test use the test() function:
- *
- * test(test_function, name, properties)
- *
- * test_function is a function that contains the code to test. For example a
- * trivial passing test would be:
- *
- * test(function() {assert_true(true)}, "assert_true with true")
- *
- * The function passed in is run in the test() call.
- *
- * properties is an object that overrides default test properties. The
- * recognised properties are:
- *    timeout - the test timeout in ms
- *
- * e.g.
- * test(test_function, "Sample test", {timeout:1000})
- *
- * would run test_function with a timeout of 1s.
- *
- * Additionally, test-specific metadata can be passed in the properties. These
- * are used when the individual test has different metadata from that stored
- * in the <head>.
- * The recognized metadata properties are:
- *
- *    help - The url of the part of the specification being tested
- *
- *    assert - A human readable description of what the test is attempting
- *             to prove
- *
- *    author - Name and contact information for the author of the test in the
- *             format: "Name <email_addr>" or "Name http://contact/url"
- *
- * == Asynchronous Tests ==
- *
- * Testing asynchronous features is somewhat more complex since the result of
- * a test may depend on one or more events or other callbacks. The API provided
- * for testing these features is indended to be rather low-level but hopefully
- * applicable to many situations.
- *
- * To create a test, one starts by getting a Test object using async_test:
- *
- * async_test(name, properties)
- *
- * e.g.
- * var t = async_test("Simple async test")
- *
- * Assertions can be added to the test by calling the step method of the test
- * object with a function containing the test assertions:
- *
- * t.step(function() {assert_true(true)});
- *
- * When all the steps are complete, the done() method must be called:
- *
- * t.done();
- *
- * As a convenience, async_test can also takes a function as first argument.
- * This function is called with the test object as both its `this` object and
- * first argument. The above example can be rewritten as:
- *
- * async_test(function(t) {
- *     object.some_event = function() {
- *         t.step(function (){assert_true(true); t.done();});
- *     };
- * }, "Simple async test");
- *
- * which avoids cluttering the global scope with references to async
- * tests instances.
- *
- * The properties argument is identical to that for test().
- *
- * In many cases it is convenient to run a step in response to an event or a
- * callback. A convenient method of doing this is through the step_func method
- * which returns a function that, when called runs a test step. For example
- *
- * object.some_event = t.step_func(function(e) {assert_true(e.a)});
- *
- * == Making assertions ==
- *
- * Functions for making assertions start assert_
- * The best way to get a list is to look in this file for functions names
- * matching that pattern. The general signature is
- *
- * assert_something(actual, expected, description)
- *
- * although not all assertions precisely match this pattern e.g. assert_true
- * only takes actual and description as arguments.
- *
- * The description parameter is used to present more useful error messages when
- * a test fails
- *
- * NOTE: All asserts must be located in a test() or a step of an async_test().
- *       asserts outside these places won't be detected correctly by the harness
- *       and may cause a file to stop testing.
- *
- * == Harness Timeout ==
- * 
- * The overall harness admits two timeout values "normal" (the
- * default) and "long", used for tests which have an unusually long
- * runtime. After the timeout is reached, the harness will stop
- * waiting for further async tests to complete. By default the
- * timeouts are set to 10s and 60s, respectively, but may be changed
- * when the test is run on hardware with different performance
- * characteristics to a common desktop computer.  In order to opt-in
- * to the longer test timeout, the test must specify a meta element:
- * <meta name="timeout" content="long">
- *
- * == Setup ==
- *
- * Sometimes tests require non-trivial setup that may fail. For this purpose
- * there is a setup() function, that may be called with one or two arguments.
- * The two argument version is:
- *
- * setup(func, properties)
- *
- * The one argument versions may omit either argument.
- * func is a function to be run synchronously. setup() becomes a no-op once
- * any tests have returned results. Properties are global properties of the test
- * harness. Currently recognised properties are:
- *
- *
- * explicit_done - Wait for an explicit call to done() before declaring all
- *                 tests complete (see below)
- *
- * output_document - The document to which results should be logged. By default
- *                   this is the current document but could be an ancestor
- *                   document in some cases e.g. a SVG test loaded in an HTML
- *                   wrapper
- *
- * explicit_timeout - disable file timeout; only stop waiting for results
- *                    when the timeout() function is called (typically for
- *                    use when integrating with some existing test framework
- *                    that has its own timeout mechanism).
- *
- * allow_uncaught_exception - don't treat an uncaught exception as an error;
- *                            needed when e.g. testing the window.onerror
- *                            handler.
- *
- * timeout_multiplier - Multiplier to apply to per-test timeouts.
- *
- * == Determining when all tests are complete ==
- *
- * By default the test harness will assume there are no more results to come
- * when:
- * 1) There are no Test objects that have been created but not completed
- * 2) The load event on the document has fired
- *
- * This behaviour can be overridden by setting the explicit_done property to
- * true in a call to setup(). If explicit_done is true, the test harness will
- * not assume it is done until the global done() function is called. Once done()
- * is called, the two conditions above apply like normal.
- *
- * == Generating tests ==
- *
- * NOTE: this functionality may be removed
- *
- * There are scenarios in which is is desirable to create a large number of
- * (synchronous) tests that are internally similar but vary in the parameters
- * used. To make this easier, the generate_tests function allows a single
- * function to be called with each set of parameters in a list:
- *
- * generate_tests(test_function, parameter_lists, properties)
- *
- * For example:
- *
- * generate_tests(assert_equals, [
- *     ["Sum one and one", 1+1, 2],
- *     ["Sum one and zero", 1+0, 1]
- *     ])
- *
- * Is equivalent to:
- *
- * test(function() {assert_equals(1+1, 2)}, "Sum one and one")
- * test(function() {assert_equals(1+0, 1)}, "Sum one and zero")
- *
- * Note that the first item in each parameter list corresponds to the name of
- * the test.
- *
- * The properties argument is identical to that for test(). This may be a
- * single object (used for all generated tests) or an array.
- *
- * == Callback API ==
- *
- * The framework provides callbacks corresponding to 3 events:
- *
- * start - happens when the first Test is created
- * result - happens when a test result is recieved
- * complete - happens when all results are recieved
- *
- * The page defining the tests may add callbacks for these events by calling
- * the following methods:
- *
- *   add_start_callback(callback) - callback called with no arguments
- *   add_result_callback(callback) - callback called with a test argument
- *   add_completion_callback(callback) - callback called with an array of tests
- *                                       and an status object
- *
- * tests have the following properties:
- *   status: A status code. This can be compared to the PASS, FAIL, TIMEOUT and
- *           NOTRUN properties on the test object
- *   message: A message indicating the reason for failure. In the future this
- *            will always be a string
- *
- *  The status object gives the overall status of the harness. It has the
- *  following properties:
- *    status: Can be compared to the OK, ERROR and TIMEOUT properties
- *    message: An error message set when the status is ERROR
- *
- * == External API ==
- *
- * In order to collect the results of multiple pages containing tests, the test
- * harness will, when loaded in a nested browsing context, attempt to call
- * certain functions in each ancestor and opener browsing context:
- *
- * start - start_callback
- * result - result_callback
- * complete - completion_callback
- *
- * These are given the same arguments as the corresponding internal callbacks
- * described above.
- *
- * == External API through cross-document messaging ==
- *
- * Where supported, the test harness will also send messages using
- * cross-document messaging to each ancestor and opener browsing context. Since
- * it uses the wildcard keyword (*), cross-origin communication is enabled and
- * script on different origins can collect the results.
- *
- * This API follows similar conventions as those described above only slightly
- * modified to accommodate message event API. Each message is sent by the harness
- * is passed a single vanilla object, available as the `data` property of the
- * event object. These objects are structures as follows:
- *
- * start - { type: "start" }
- * result - { type: "result", test: Test }
- * complete - { type: "complete", tests: [Test, ...], status: TestsStatus }
- *
- * == List of assertions ==
- *
- * assert_true(actual, description)
- *   asserts that /actual/ is strictly true
- *
- * assert_false(actual, description)
- *   asserts that /actual/ is strictly false
- *
- * assert_equals(actual, expected, description)
- *   asserts that /actual/ is the same value as /expected/
- *
- * assert_not_equals(actual, expected, description)
- *   asserts that /actual/ is a different value to /expected/. Yes, this means
- *   that "expected" is a misnomer
- *
- * assert_in_array(actual, expected, description)
- *   asserts that /expected/ is an Array, and /actual/ is equal to one of the
- *   members -- expected.indexOf(actual) != -1
- *
- * assert_array_equals(actual, expected, description)
- *   asserts that /actual/ and /expected/ have the same length and the value of
- *   each indexed property in /actual/ is the strictly equal to the corresponding
- *   property value in /expected/
- *
- * assert_approx_equals(actual, expected, epsilon, description)
- *   asserts that /actual/ is a number within +/- /epsilon/ of /expected/
- *
- * assert_less_than(actual, expected, description)
- *   asserts that /actual/ is a number less than /expected/
- *
- * assert_greater_than(actual, expected, description)
- *   asserts that /actual/ is a number greater than /expected/
- *
- * assert_less_than_equal(actual, expected, description)
- *   asserts that /actual/ is a number less than or equal to /expected/
- *
- * assert_greater_than_equal(actual, expected, description)
- *   asserts that /actual/ is a number greater than or equal to /expected/
- *
- * assert_regexp_match(actual, expected, description)
- *   asserts that /actual/ matches the regexp /expected/
- *
- * assert_class_string(object, class_name, description)
- *   asserts that the class string of /object/ as returned in
- *   Object.prototype.toString is equal to /class_name/.
- *
- * assert_own_property(object, property_name, description)
- *   assert that object has own property property_name
- *
- * assert_inherits(object, property_name, description)
- *   assert that object does not have an own property named property_name
- *   but that property_name is present in the prototype chain for object
- *
- * assert_idl_attribute(object, attribute_name, description)
- *   assert that an object that is an instance of some interface has the
- *   attribute attribute_name following the conditions specified by WebIDL
- *
- * assert_readonly(object, property_name, description)
- *   assert that property property_name on object is readonly
- *
- * assert_throws(code, func, description)
- *   code - the expected exception:
- *     o string: the thrown exception must be a DOMException with the given
- *               name, e.g., "TimeoutError" (for compatibility with existing
- *               tests, a constant is also supported, e.g., "TIMEOUT_ERR")
- *     o object: the thrown exception must have a property called "name" that
- *               matches code.name
- *     o null:   allow any exception (in general, one of the options above
- *               should be used)
- *   func - a function that should throw
- *
- * assert_unreached(description)
- *   asserts if called. Used to ensure that some codepath is *not* taken e.g.
- *   an event does not fire.
- *
- * assert_any(assert_func, actual, expected_array, extra_arg_1, ... extra_arg_N)
- *   asserts that one assert_func(actual, expected_array_N, extra_arg1, ..., extra_arg_N)
- *   is true for some expected_array_N in expected_array. This only works for assert_func
- *   with signature assert_func(actual, expected, args_1, ..., args_N). Note that tests
- *   with multiple allowed pass conditions are bad practice unless the spec specifically
- *   allows multiple behaviours. Test authors should not use this method simply to hide
- *   UA bugs.
- *
- * assert_exists(object, property_name, description)
- *   *** deprecated ***
- *   asserts that object has an own property property_name
- *
- * assert_not_exists(object, property_name, description)
- *   *** deprecated ***
- *   assert that object does not have own property property_name
- */
+/* Documentation is in docs/api.md */
 
 (function ()
 {
     var debug = false;
     // default timeout is 10 seconds, test can override if needed
     var settings = {
-      output:true,
-      harness_timeout:{"normal":10000,
-                       "long":60000},
-      test_timeout:null
+        output:true,
+        harness_timeout:{
+            "normal":10000,
+            "long":60000
+        },
+        test_timeout:null
     };
 
     var xhtml_ns = "http://www.w3.org/1999/xhtml";
 
-    // script_prefix is used by Output.prototype.show_results() to figure out
-    // where to get testharness.css from.  It's enclosed in an extra closure to
-    // not pollute the library's namespace with variables like "src".
-    var script_prefix = null;
-    (function ()
-    {
-        var scripts = document.getElementsByTagName("script");
-        for (var i = 0; i < scripts.length; i++)
-        {
-            if (scripts[i].src)
-            {
-                var src = scripts[i].src;
+    /*
+     * TestEnvironment is an abstraction for the environment in which the test
+     * harness is used. Each implementation of a test environment has to provide
+     * the following interface:
+     *
+     * interface TestEnvironment {
+     *   // Invoked after the global 'tests' object has been created and it's
+     *   // safe to call add_*_callback() to register event handlers.
+     *   void on_tests_ready();
+     *
+     *   // Invoked after setup() has been called to notify the test environment
+     *   // of changes to the test harness properties.
+     *   void on_new_harness_properties(object properties);
+     *
+     *   // Should return a new unique default test name.
+     *   DOMString next_default_test_name();
+     *
+     *   // Should return the test harness timeout duration in milliseconds.
+     *   float test_timeout();
+     *
+     *   // Should return the global scope object.
+     *   object global_scope();
+     * };
+     */
+
+    /*
+     * A test environment with a DOM. The global object is 'window'. By default
+     * test results are displayed in a table. Any parent windows receive
+     * callbacks or messages via postMessage() when test events occur. See
+     * apisample11.html and apisample12.html.
+     */
+    function WindowTestEnvironment() {
+        this.name_counter = 0;
+        this.window_cache = null;
+        this.output_handler = null;
+        this.all_loaded = false;
+        var this_obj = this;
+        on_event(window, 'load', function() {
+            this_obj.all_loaded = true;
+        });
+    }
+
+    WindowTestEnvironment.prototype._dispatch = function(selector, callback_args, message_arg) {
+        this._forEach_windows(
+                function(w, is_same_origin) {
+                    if (is_same_origin && selector in w) {
+                        try {
+                            w[selector].apply(undefined, callback_args);
+                        } catch (e) {
+                            if (debug) {
+                                throw e;
+                            }
+                        }
+                    }
+                    if (supports_post_message(w) && w !== self) {
+                        w.postMessage(message_arg, "*");
+                    }
+                });
+    };
+
+    WindowTestEnvironment.prototype._forEach_windows = function(callback) {
+        // Iterate of the the windows [self ... top, opener]. The callback is passed
+        // two objects, the first one is the windows object itself, the second one
+        // is a boolean indicating whether or not its on the same origin as the
+        // current window.
+        var cache = this.window_cache;
+        if (!cache) {
+            cache = [[self, true]];
+            var w = self;
+            var i = 0;
+            var so;
+            var origins = location.ancestorOrigins;
+            while (w != w.parent) {
+                w = w.parent;
+                // In WebKit, calls to parent windows' properties that aren't on the same
+                // origin cause an error message to be displayed in the error console but
+                // don't throw an exception. This is a deviation from the current HTML5
+                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
+                // The problem with WebKit's behavior is that it pollutes the error console
+                // with error messages that can't be caught.
+                //
+                // This issue can be mitigated by relying on the (for now) proprietary
+                // `location.ancestorOrigins` property which returns an ordered list of
+                // the origins of enclosing windows. See:
+                // http://trac.webkit.org/changeset/113945.
+                if (origins) {
+                    so = (location.origin == origins[i]);
+                } else {
+                    so = is_same_origin(w);
+                }
+                cache.push([w, so]);
+                i++;
             }
-            else if (scripts[i].href)
-            {
-                //SVG case
-                var src = scripts[i].href.baseVal;
+            w = window.opener;
+            if (w) {
+                // window.opener isn't included in the `location.ancestorOrigins` prop.
+                // We'll just have to deal with a simple check and an error msg on WebKit
+                // browsers in this case.
+                cache.push([w, is_same_origin(w)]);
             }
-            if (src && src.slice(src.length - "testharness.js".length) === "testharness.js")
-            {
-                script_prefix = src.slice(0, src.length - "testharness.js".length);
+            this.window_cache = cache;
+        }
+
+        forEach(cache,
+                function(a) {
+                    callback.apply(null, a);
+                });
+    };
+
+    WindowTestEnvironment.prototype.on_tests_ready = function() {
+        var output = new Output();
+        this.output_handler = output;
+
+        var this_obj = this;
+        add_start_callback(function (properties) {
+            this_obj.output_handler.init(properties);
+            this_obj._dispatch("start_callback", [properties],
+                           { type: "start", properties: properties });
+        });
+        add_test_state_callback(function(test) {
+            this_obj.output_handler.show_status();
+            this_obj._dispatch("test_state_callback", [test],
+                               { type: "test_state", test: test.structured_clone() });
+        });
+        add_result_callback(function (test) {
+            this_obj.output_handler.show_status();
+            this_obj._dispatch("result_callback", [test],
+                               { type: "result", test: test.structured_clone() });
+        });
+        add_completion_callback(function (tests, harness_status) {
+            this_obj.output_handler.show_results(tests, harness_status);
+            var cloned_tests = map(tests, function(test) { return test.structured_clone(); });
+            this_obj._dispatch("completion_callback", [tests, harness_status],
+                               { type: "complete", tests: cloned_tests,
+                                 status: harness_status.structured_clone() });
+        });
+    };
+
+    WindowTestEnvironment.prototype.next_default_test_name = function() {
+        //Don't use document.title to work around an Opera bug in XHTML documents
+        var title = document.getElementsByTagName("title")[0];
+        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return prefix + suffix;
+    };
+
+    WindowTestEnvironment.prototype.on_new_harness_properties = function(properties) {
+        this.output_handler.setup(properties);
+    };
+
+    WindowTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        on_event(window, 'load', callback);
+    };
+
+    WindowTestEnvironment.prototype.test_timeout = function() {
+        var metas = document.getElementsByTagName("meta");
+        for (var i = 0; i < metas.length; i++) {
+            if (metas[i].name == "timeout") {
+                if (metas[i].content == "long") {
+                    return settings.harness_timeout.long;
+                }
                 break;
             }
         }
-    })();
+        return settings.harness_timeout.normal;
+    };
+
+    WindowTestEnvironment.prototype.global_scope = function() {
+        return window;
+    };
+
+    /*
+     * Base TestEnvironment implementation for a generic web worker.
+     *
+     * Workers accumulate test results. One or more clients can connect and
+     * retrieve results from a worker at any time.
+     *
+     * WorkerTestEnvironment supports communicating with a client via a
+     * MessagePort.  The mechanism for determining the appropriate MessagePort
+     * for communicating with a client depends on the type of worker and is
+     * implemented by the various specializations of WorkerTestEnvironment
+     * below.
+     *
+     * A client document using testharness can use fetch_tests_from_worker() to
+     * retrieve results from a worker. See apisample16.html.
+     */
+    function WorkerTestEnvironment() {
+        this.name_counter = 0;
+        this.all_loaded = true;
+        this.message_list = [];
+        this.message_ports = [];
+    }
+
+    WorkerTestEnvironment.prototype._dispatch = function(message) {
+        this.message_list.push(message);
+        for (var i = 0; i < this.message_ports.length; ++i)
+        {
+            this.message_ports[i].postMessage(message);
+        }
+    };
+
+    // The only requirement is that port has a postMessage() method. It doesn't
+    // have to be an instance of a MessagePort, and often isn't.
+    WorkerTestEnvironment.prototype._add_message_port = function(port) {
+        this.message_ports.push(port);
+        for (var i = 0; i < this.message_list.length; ++i)
+        {
+            port.postMessage(this.message_list[i]);
+        }
+    };
+
+    WorkerTestEnvironment.prototype.next_default_test_name = function() {
+        var suffix = this.name_counter > 0 ? " " + this.name_counter : "";
+        this.name_counter++;
+        return "Untitled" + suffix;
+    };
+
+    WorkerTestEnvironment.prototype.on_new_harness_properties = function() {};
+
+    WorkerTestEnvironment.prototype.on_tests_ready = function() {
+        var this_obj = this;
+        add_start_callback(
+                function(properties) {
+                    this_obj._dispatch({
+                        type: "start",
+                        properties: properties,
+                    });
+                });
+        add_test_state_callback(
+                function(test) {
+                    this_obj._dispatch({
+                        type: "test_state",
+                        test: test.structured_clone()
+                    });
+                });
+        add_result_callback(
+                function(test) {
+                    this_obj._dispatch({
+                        type: "result",
+                        test: test.structured_clone()
+                    });
+                });
+        add_completion_callback(
+                function(tests, harness_status) {
+                    this_obj._dispatch({
+                        type: "complete",
+                        tests: map(tests,
+                            function(test) {
+                                return test.structured_clone();
+                            }),
+                        status: harness_status.structured_clone()
+                    });
+                });
+    };
+
+    WorkerTestEnvironment.prototype.add_on_loaded_callback = function() {};
+
+    WorkerTestEnvironment.prototype.test_timeout = function() {
+        // Tests running in a worker don't have a default timeout. I.e. all
+        // worker tests behave as if settings.explicit_timeout is true.
+        return null;
+    };
+
+    WorkerTestEnvironment.prototype.global_scope = function() {
+        return self;
+    };
+
+    /*
+     * Dedicated web workers.
+     * https://html.spec.whatwg.org/multipage/workers.html#dedicatedworkerglobalscope
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a dedicated worker.
+     */
+    function DedicatedWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        // self is an instance of DedicatedWorkerGlobalScope which exposes
+        // a postMessage() method for communicating via the message channel
+        // established when the worker is created.
+        this._add_message_port(self);
+    }
+    DedicatedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    DedicatedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+        // In the absence of an onload notification, we a require dedicated
+        // workers to explicitly signal when the tests are done.
+        tests.wait_for_finish = true;
+    };
+
+    /*
+     * Shared web workers.
+     * https://html.spec.whatwg.org/multipage/workers.html#sharedworkerglobalscope
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a shared web worker.
+     */
+    function SharedWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        var this_obj = this;
+        // Shared workers receive message ports via the 'onconnect' event for
+        // each connection.
+        self.addEventListener("connect",
+                function(message_event) {
+                    this_obj._add_message_port(message_event.source);
+                });
+    }
+    SharedWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    SharedWorkerTestEnvironment.prototype.on_tests_ready = function() {
+        WorkerTestEnvironment.prototype.on_tests_ready.call(this);
+        // In the absence of an onload notification, we a require shared
+        // workers to explicitly signal when the tests are done.
+        tests.wait_for_finish = true;
+    };
+
+    /*
+     * Service workers.
+     * http://www.w3.org/TR/service-workers/
+     *
+     * This class is used as the test_environment when testharness is running
+     * inside a service worker.
+     */
+    function ServiceWorkerTestEnvironment() {
+        WorkerTestEnvironment.call(this);
+        this.all_loaded = false;
+        this.on_loaded_callback = null;
+        var this_obj = this;
+        self.addEventListener("message",
+                function(event) {
+                    if (event.data.type && event.data.type === "connect") {
+                        this_obj._add_message_port(event.ports[0]);
+                        event.ports[0].start();
+                    }
+                });
+
+        // The oninstall event is received after the service worker script and
+        // all imported scripts have been fetched and executed. It's the
+        // equivalent of an onload event for a document. All tests should have
+        // been added by the time this event is received, thus it's not
+        // necessary to wait until the onactivate event.
+        on_event(self, "install",
+                function(event) {
+                    this_obj.all_loaded = true;
+                    if (this_obj.on_loaded_callback) {
+                        this_obj.on_loaded_callback();
+                    }
+                });
+    }
+    ServiceWorkerTestEnvironment.prototype = Object.create(WorkerTestEnvironment.prototype);
+
+    ServiceWorkerTestEnvironment.prototype.add_on_loaded_callback = function(callback) {
+        if (this.all_loaded) {
+            callback();
+        } else {
+            this.on_loaded_callback = callback;
+        }
+    };
+
+    function create_test_environment() {
+        if ('document' in self) {
+            return new WindowTestEnvironment();
+        }
+        if ('DedicatedWorkerGlobalScope' in self &&
+            self instanceof DedicatedWorkerGlobalScope) {
+            return new DedicatedWorkerTestEnvironment();
+        }
+        if ('SharedWorkerGlobalScope' in self &&
+            self instanceof SharedWorkerGlobalScope) {
+            return new SharedWorkerTestEnvironment();
+        }
+        if ('ServiceWorkerGlobalScope' in self &&
+            self instanceof ServiceWorkerGlobalScope) {
+            return new ServiceWorkerTestEnvironment();
+        }
+        throw new Error("Unsupported test environment");
+    }
+
+    var test_environment = create_test_environment();
+
+    function is_shared_worker(worker) {
+        return 'SharedWorker' in self && worker instanceof SharedWorker;
+    }
+
+    function is_service_worker(worker) {
+        return 'ServiceWorker' in self && worker instanceof ServiceWorker;
+    }
 
     /*
      * API functions
      */
 
-    var name_counter = 0;
-    function next_default_name()
-    {
-        //Don't use document.title to work around an Opera bug in XHTML documents
-        var title = document.getElementsByTagName("title")[0];
-        var prefix = (title && title.firstChild && title.firstChild.data) || "Untitled";
-        var suffix = name_counter > 0 ? " " + name_counter : "";
-        name_counter++;
-        return prefix + suffix;
-    }
-
     function test(func, name, properties)
     {
-        var test_name = name ? name : next_default_name();
+        var test_name = name ? name : test_environment.next_default_test_name();
         properties = properties ? properties : {};
         var test_obj = new Test(test_name, properties);
-        test_obj.step(func);
+        test_obj.step(func, test_obj, test_obj);
         if (test_obj.phase === test_obj.phases.STARTED) {
             test_obj.done();
         }
     }
 
     function async_test(func, name, properties)
     {
         if (typeof func !== "function") {
             properties = name;
             name = func;
             func = null;
         }
-        var test_name = name ? name : next_default_name();
+        var test_name = name ? name : test_environment.next_default_test_name();
         properties = properties ? properties : {};
         var test_obj = new Test(test_name, properties);
         if (func) {
             test_obj.step(func, test_obj, test_obj);
         }
         return test_obj;
     }
 
+    function promise_test(func, name, properties) {
+        var test = async_test(name, properties);
+        Promise.resolve(test.step(func, test, test))
+            .then(
+                function() {
+                    test.done();
+                })
+            .catch(test.step_func(
+                function(value) {
+                    if (value instanceof AssertionError) {
+                        throw value;
+                    }
+                    assert(false, "promise_test", null,
+                           "Unhandled rejection with value: ${value}", {value:value});
+                }));
+    }
+
     function setup(func_or_properties, maybe_properties)
     {
         var func = null;
         var properties = {};
         if (arguments.length === 2) {
             func = func_or_properties;
             properties = maybe_properties;
-        } else if (func_or_properties instanceof Function){
+        } else if (func_or_properties instanceof Function) {
             func = func_or_properties;
         } else {
             properties = func_or_properties;
         }
         tests.setup(func, properties);
-        output.setup(properties);
+        test_environment.on_new_harness_properties(properties);
     }
 
     function done() {
+        if (tests.tests.length === 0) {
+            tests.set_file_is_test();
+        }
+        if (tests.file_is_test) {
+            tests.tests[0].done();
+        }
         tests.end_wait();
     }
 
     function generate_tests(func, args, properties) {
         forEach(args, function(x, i)
                 {
                     var name = x[0];
                     test(function()
@@ -478,21 +500,22 @@ policies and contribution forms [3].
                          },
                          name,
                          Array.isArray(properties) ? properties[i] : properties);
                 });
     }
 
     function on_event(object, event, callback)
     {
-      object.addEventListener(event, callback, false);
+        object.addEventListener(event, callback, false);
     }
 
     expose(test, 'test');
     expose(async_test, 'async_test');
+    expose(promise_test, 'promise_test');
     expose(generate_tests, 'generate_tests');
     expose(setup, 'setup');
     expose(done, 'done');
     expose(on_event, 'on_event');
 
     /*
      * Return a string truncated to the given length, with ... added at the end
      * if it was longer.
@@ -509,27 +532,23 @@ policies and contribution forms [3].
      * Return true if object is probably a Node object.
      */
     function is_node(object)
     {
         // I use duck-typing instead of instanceof, because
         // instanceof doesn't work if the node is from another window (like an
         // iframe's contentWindow):
         // http://www.w3.org/Bugs/Public/show_bug.cgi?id=12295
-        if ("nodeType" in object
-        && "nodeName" in object
-        && "nodeValue" in object
-        && "childNodes" in object)
-        {
-            try
-            {
+        if ("nodeType" in object &&
+            "nodeName" in object &&
+            "nodeValue" in object &&
+            "childNodes" in object) {
+            try {
                 object.nodeType;
-            }
-            catch (e)
-            {
+            } catch (e) {
                 // The object is probably Node.prototype or another prototype
                 // object that inherits from it, and not a Node instance.
                 return false;
             }
             return true;
         }
         return false;
     }
@@ -537,35 +556,30 @@ policies and contribution forms [3].
     /*
      * Convert a value to a nice, human-readable string
      */
     function format_value(val, seen)
     {
         if (!seen) {
             seen = [];
         }
-        if (typeof val === "object" && val !== null)
-        {
-            if (seen.indexOf(val) >= 0)
-            {
+        if (typeof val === "object" && val !== null) {
+            if (seen.indexOf(val) >= 0) {
                 return "[...]";
             }
             seen.push(val);
         }
-        if (Array.isArray(val))
-        {
-            return "[" + val.map(function(x) {return format_value(x, seen)}).join(", ") + "]";
+        if (Array.isArray(val)) {
+            return "[" + val.map(function(x) {return format_value(x, seen);}).join(", ") + "]";
         }
 
-        switch (typeof val)
-        {
+        switch (typeof val) {
         case "string":
             val = val.replace("\\", "\\\\");
-            for (var i = 0; i < 32; i++)
-            {
+            for (var i = 0; i < 32; i++) {
                 var replace = "\\";
                 switch (i) {
                 case 0: replace += "0"; break;
                 case 1: replace += "x01"; break;
                 case 2: replace += "x02"; break;
                 case 3: replace += "x03"; break;
                 case 4: replace += "x04"; break;
                 case 5: replace += "x05"; break;
@@ -600,40 +614,35 @@ policies and contribution forms [3].
             }
             return '"' + val.replace(/"/g, '\\"') + '"';
         case "boolean":
         case "undefined":
             return String(val);
         case "number":
             // In JavaScript, -0 === 0 and String(-0) == "0", so we have to
             // special-case.
-            if (val === -0 && 1/val === -Infinity)
-            {
+            if (val === -0 && 1/val === -Infinity) {
                 return "-0";
             }
             return String(val);
         case "object":
-            if (val === null)
-            {
+            if (val === null) {
                 return "null";
             }
 
             // Special-case Node objects, since those come up a lot in my tests.  I
             // ignore namespaces.
-            if (is_node(val))
-            {
-                switch (val.nodeType)
-                {
+            if (is_node(val)) {
+                switch (val.nodeType) {
                 case Node.ELEMENT_NODE:
-                    var ret = "<" + val.tagName.toLowerCase();
-                    for (var i = 0; i < val.attributes.length; i++)
-                    {
+                    var ret = "<" + val.localName;
+                    for (var i = 0; i < val.attributes.length; i++) {
                         ret += " " + val.attributes[i].name + '="' + val.attributes[i].value + '"';
                     }
-                    ret += ">" + val.innerHTML + "</" + val.tagName.toLowerCase() + ">";
+                    ret += ">" + val.innerHTML + "</" + val.localName + ">";
                     return "Element node " + truncate(ret, 60);
                 case Node.TEXT_NODE:
                     return 'Text node "' + truncate(val.data, 60) + '"';
                 case Node.PROCESSING_INSTRUCTION_NODE:
                     return "ProcessingInstruction node with target " + format_value(truncate(val.target, 60)) + " and data " + format_value(truncate(val.data, 60));
                 case Node.COMMENT_NODE:
                     return "Comment node <!--" + truncate(val.data, 60) + "-->";
                 case Node.DOCUMENT_NODE:
@@ -642,87 +651,81 @@ policies and contribution forms [3].
                     return "DocumentType node";
                 case Node.DOCUMENT_FRAGMENT_NODE:
                     return "DocumentFragment node with " + val.childNodes.length + (val.childNodes.length == 1 ? " child" : " children");
                 default:
                     return "Node object of unknown type";
                 }
             }
 
-            // Fall through to default
+        /* falls through */
         default:
             return typeof val + ' "' + truncate(String(val), 60) + '"';
         }
     }
     expose(format_value, "format_value");
 
     /*
      * Assertions
      */
 
     function assert_true(actual, description)
     {
         assert(actual === true, "assert_true", description,
                                 "expected true got ${actual}", {actual:actual});
-    };
+    }
     expose(assert_true, "assert_true");
 
     function assert_false(actual, description)
     {
         assert(actual === false, "assert_false", description,
                                  "expected false got ${actual}", {actual:actual});
-    };
+    }
     expose(assert_false, "assert_false");
 
     function same_value(x, y) {
-        if (y !== y)
-        {
+        if (y !== y) {
             //NaN case
             return x !== x;
         }
-        else if (x === 0 && y === 0) {
+        if (x === 0 && y === 0) {
             //Distinguish +0 and -0
             return 1/x === 1/y;
         }
-        else
-        {
-            //typical case
-            return x === y;
-        }
+        return x === y;
     }
 
     function assert_equals(actual, expected, description)
     {
          /*
           * Test if two primitives are equal or two objects
           * are the same object
           */
-        if (typeof actual != typeof expected)
-        {
+        if (typeof actual != typeof expected) {
             assert(false, "assert_equals", description,
                           "expected (" + typeof expected + ") ${expected} but got (" + typeof actual + ") ${actual}",
                           {expected:expected, actual:actual});
             return;
         }
         assert(same_value(actual, expected), "assert_equals", description,
                                              "expected ${expected} but got ${actual}",
                                              {expected:expected, actual:actual});
-    };
+    }
     expose(assert_equals, "assert_equals");
 
     function assert_not_equals(actual, expected, description)
     {
          /*
           * Test if two primitives are unequal or two objects
           * are different objects
           */
         assert(!same_value(actual, expected), "assert_not_equals", description,
                                               "got disallowed value ${actual}",
                                               {actual:actual});
-    };
+    }
     expose(assert_not_equals, "assert_not_equals");
 
     function assert_in_array(actual, expected, description)
     {
         assert(expected.indexOf(actual) != -1, "assert_in_array", description,
                                                "value ${actual} not in array ${expected}",
                                                {actual:actual, expected:expected});
     }
@@ -731,56 +734,49 @@ policies and contribution forms [3].
     function assert_object_equals(actual, expected, description)
     {
          //This needs to be improved a great deal
          function check_equal(actual, expected, stack)
          {
              stack.push(actual);
 
              var p;
-             for (p in actual)
-             {
+             for (p in actual) {
                  assert(expected.hasOwnProperty(p), "assert_object_equals", description,
                                                     "unexpected property ${p}", {p:p});
 
-                 if (typeof actual[p] === "object" && actual[p] !== null)
-                 {
-                     if (stack.indexOf(actual[p]) === -1)
-                     {
+                 if (typeof actual[p] === "object" && actual[p] !== null) {
+                     if (stack.indexOf(actual[p]) === -1) {
                          check_equal(actual[p], expected[p], stack);
                      }
-                 }
-                 else
-                 {
+                 } else {
                      assert(same_value(actual[p], expected[p]), "assert_object_equals", description,
                                                        "property ${p} expected ${expected} got ${actual}",
                                                        {p:p, expected:expected, actual:actual});
                  }
              }
-             for (p in expected)
-             {
+             for (p in expected) {
                  assert(actual.hasOwnProperty(p),
                         "assert_object_equals", description,
                         "expected property ${p} missing", {p:p});
              }
              stack.pop();
          }
          check_equal(actual, expected, []);
-    };
+    }
     expose(assert_object_equals, "assert_object_equals");
 
     function assert_array_equals(actual, expected, description)
     {
         assert(actual.length === expected.length,
                "assert_array_equals", description,
                "lengths differ, expected ${expected} got ${actual}",
                {expected:expected.length, actual:actual.length});
 
-        for (var i=0; i < actual.length; i++)
-        {
+        for (var i = 0; i < actual.length; i++) {
             assert(actual.hasOwnProperty(i) === expected.hasOwnProperty(i),
                    "assert_array_equals", description,
                    "property ${i}, property expected to be $expected but was $actual",
                    {i:i, expected:expected.hasOwnProperty(i) ? "present" : "missing",
                    actual:actual.hasOwnProperty(i) ? "present" : "missing"});
             assert(same_value(expected[i], actual[i]),
                    "assert_array_equals", description,
                    "property ${i}, expected ${expected} but got ${actual}",
@@ -798,85 +794,85 @@ policies and contribution forms [3].
                "assert_approx_equals", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(Math.abs(actual - expected) <= epsilon,
                "assert_approx_equals", description,
                "expected ${expected} +/- ${epsilon} but got ${actual}",
                {expected:expected, actual:actual, epsilon:epsilon});
-    };
+    }
     expose(assert_approx_equals, "assert_approx_equals");
 
     function assert_less_than(actual, expected, description)
     {
         /*
          * Test if a primitive number is less than another
          */
         assert(typeof actual === "number",
                "assert_less_than", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(actual < expected,
                "assert_less_than", description,
                "expected a number less than ${expected} but got ${actual}",
                {expected:expected, actual:actual});
-    };
+    }
     expose(assert_less_than, "assert_less_than");
 
     function assert_greater_than(actual, expected, description)
     {
         /*
          * Test if a primitive number is greater than another
          */
         assert(typeof actual === "number",
                "assert_greater_than", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(actual > expected,
                "assert_greater_than", description,
                "expected a number greater than ${expected} but got ${actual}",
                {expected:expected, actual:actual});
-    };
+    }
     expose(assert_greater_than, "assert_greater_than");
 
     function assert_less_than_equal(actual, expected, description)
     {
         /*
          * Test if a primitive number is less than or equal to another
          */
         assert(typeof actual === "number",
                "assert_less_than_equal", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(actual <= expected,
                "assert_less_than", description,
                "expected a number less than or equal to ${expected} but got ${actual}",
                {expected:expected, actual:actual});
-    };
+    }
     expose(assert_less_than_equal, "assert_less_than_equal");
 
     function assert_greater_than_equal(actual, expected, description)
     {
         /*
          * Test if a primitive number is greater than or equal to another
          */
         assert(typeof actual === "number",
                "assert_greater_than_equal", description,
                "expected a number but got a ${type_actual}",
                {type_actual:typeof actual});
 
         assert(actual >= expected,
                "assert_greater_than_equal", description,
                "expected a number greater than or equal to ${expected} but got ${actual}",
                {expected:expected, actual:actual});
-    };
+    }
     expose(assert_greater_than_equal, "assert_greater_than_equal");
 
     function assert_regexp_match(actual, expected, description) {
         /*
          * Test if a string (actual) matches a regexp (expected)
          */
         assert(expected.test(actual),
                "assert_regexp_match", description,
@@ -903,17 +899,17 @@ policies and contribution forms [3].
     expose(_assert_own_property("assert_exists"), "assert_exists");
     expose(_assert_own_property("assert_own_property"), "assert_own_property");
 
     function assert_not_exists(object, property_name, description)
     {
         assert(!object.hasOwnProperty(property_name),
                "assert_not_exists", description,
                "unexpected property ${p} found", {p:property_name});
-    };
+    }
     expose(assert_not_exists, "assert_not_exists");
 
     function _assert_inherits(name) {
         return function (object, property_name, description)
         {
             assert(typeof object === "object",
                    name, description,
                    "provided value is not an object");
@@ -942,43 +938,36 @@ policies and contribution forms [3].
          try {
              //Note that this can have side effects in the case where
              //the property has PutForwards
              object[property_name] = initial_value + "a"; //XXX use some other value here?
              assert(same_value(object[property_name], initial_value),
                     "assert_readonly", description,
                     "changing property ${p} succeeded",
                     {p:property_name});
-         }
-         finally
-         {
+         } finally {
              object[property_name] = initial_value;
          }
-    };
+    }
     expose(assert_readonly, "assert_readonly");
 
     function assert_throws(code, func, description)
     {
-        try
-        {
+        try {
             func.call(this);
             assert(false, "assert_throws", description,
                    "${func} did not throw", {func:func});
-        }
-        catch(e)
-        {
+        } catch (e) {
             if (e instanceof AssertionError) {
-                throw(e);
+                throw e;
             }
-            if (code === null)
-            {
+            if (code === null) {
                 return;
             }
-            if (typeof code === "object")
-            {
+            if (typeof code === "object") {
                 assert(typeof e == "object" && "name" in e && e.name == code.name,
                        "assert_throws", description,
                        "${func} threw ${actual} (${actual_name}) expected ${expected} (${expected_name})",
                                     {func:func, actual:e, actual_name:e.name,
                                      expected:code,
                                      expected_name:code.name});
                 return;
             }
@@ -1035,42 +1024,39 @@ policies and contribution forms [3].
                 UnknownError: 0,
                 ConstraintError: 0,
                 DataError: 0,
                 TransactionInactiveError: 0,
                 ReadOnlyError: 0,
                 VersionError: 0
             };
 
-            if (!(name in name_code_map))
-            {
+            if (!(name in name_code_map)) {
                 throw new AssertionError('Test bug: unrecognized DOMException code "' + code + '" passed to assert_throws()');
             }
 
             var required_props = { code: name_code_map[name] };
 
-            if (required_props.code === 0
-            || ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException"))
-            {
+            if (required_props.code === 0 ||
+               ("name" in e && e.name !== e.name.toUpperCase() && e.name !== "DOMException")) {
                 // New style exception: also test the name property.
                 required_props.name = name;
             }
 
             //We'd like to test that e instanceof the appropriate interface,
             //but we can't, because we don't know what window it was created
             //in.  It might be an instanceof the appropriate interface on some
             //unknown other window.  TODO: Work around this somehow?
 
             assert(typeof e == "object",
                    "assert_throws", description,
                    "${func} threw ${e} with type ${type}, not an object",
                    {func:func, e:e, type:typeof e});
 
-            for (var prop in required_props)
-            {
+            for (var prop in required_props) {
                 assert(typeof e == "object" && prop in e && e[prop] == required_props[prop],
                        "assert_throws", description,
                        "${func} threw ${e} that is not a DOMException " + code + ": property ${prop} is equal to ${actual}, expected ${expected}",
                        {func:func, e:e, prop:prop, actual:e[prop], expected:required_props[prop]});
             }
         }
     }
     expose(assert_throws, "assert_throws");
@@ -1078,122 +1064,123 @@ policies and contribution forms [3].
     function assert_unreached(description) {
          assert(false, "assert_unreached", description,
                 "Reached unreachable code");
     }
     expose(assert_unreached, "assert_unreached");
 
     function assert_any(assert_func, actual, expected_array)
     {
-        var args = [].slice.call(arguments, 3)
-        var errors = []
+        var args = [].slice.call(arguments, 3);
+        var errors = [];
         var passed = false;
         forEach(expected_array,
                 function(expected)
                 {
                     try {
-                        assert_func.apply(this, [actual, expected].concat(args))
+                        assert_func.apply(this, [actual, expected].concat(args));
                         passed = true;
-                    } catch(e) {
+                    } catch (e) {
                         errors.push(e.message);
                     }
                 });
         if (!passed) {
             throw new AssertionError(errors.join("\n\n"));
         }
     }
     expose(assert_any, "assert_any");
 
     function Test(name, properties)
     {
+        if (tests.file_is_test && tests.tests.length) {
+            throw new Error("Tried to create a test with file_is_test");
+        }
         this.name = name;
 
-        this.phases = {
-            INITIAL:0,
-            STARTED:1,
-            HAS_RESULT:2,
-            COMPLETE:3
-        };
         this.phase = this.phases.INITIAL;
 
         this.status = this.NOTRUN;
         this.timeout_id = null;
+        this.index = null;
 
         this.properties = properties;
-        var timeout = properties.timeout ? properties.timeout : settings.test_timeout
-        if (timeout != null) {
+        var timeout = properties.timeout ? properties.timeout : settings.test_timeout;
+        if (timeout !== null) {
             this.timeout_length = timeout * tests.timeout_multiplier;
         } else {
             this.timeout_length = null;
         }
 
         this.message = null;
 
-        var this_obj = this;
         this.steps = [];
 
+        this.cleanup_callbacks = [];
+
         tests.push(this);
     }
 
     Test.statuses = {
         PASS:0,
         FAIL:1,
         TIMEOUT:2,
         NOTRUN:3
     };
 
     Test.prototype = merge({}, Test.statuses);
 
+    Test.prototype.phases = {
+        INITIAL:0,
+        STARTED:1,
+        HAS_RESULT:2,
+        COMPLETE:3
+    };
+
     Test.prototype.structured_clone = function()
     {
-        if(!this._structured_clone)
-        {
+        if (!this._structured_clone) {
             var msg = this.message;
             msg = msg ? String(msg) : msg;
             this._structured_clone = merge({
                 name:String(this.name),
-                status:this.status,
-                message:msg
+                properties:merge({}, this.properties),
             }, Test.statuses);
         }
+        this._structured_clone.status = this.status;
+        this._structured_clone.message = this.message;
+        this._structured_clone.index = this.index;
         return this._structured_clone;
     };
 
     Test.prototype.step = function(func, this_obj)
     {
-        if (this.phase > this.phases.STARTED)
-        {
-          return;
+        if (this.phase > this.phases.STARTED) {
+            return;
         }
         this.phase = this.phases.STARTED;
         //If we don't get a result before the harness times out that will be a test timout
         this.set_status(this.TIMEOUT, "Test timed out");
 
         tests.started = true;
+        tests.notify_test_state(this);
 
-        if (this.timeout_id === null)
-        {
+        if (this.timeout_id === null) {
             this.set_timeout();
         }
 
         this.steps.push(func);
 
-        if (arguments.length === 1)
-        {
+        if (arguments.length === 1) {
             this_obj = this;
         }
 
-        try
-        {
+        try {
             return func.apply(this_obj, Array.prototype.slice.call(arguments, 2));
-        }
-        catch(e)
-        {
-            if (this.phase >= this.phases.HAS_RESULT)
-            {
+        } catch (e) {
+            if (this.phase >= this.phases.HAS_RESULT) {
                 return;
             }
             var message = (typeof e === "object" && e !== null) ? e.message : e;
             if (typeof e.stack != "undefined" && typeof e.message == "string") {
                 //Try to make it more informative for some exceptions, at least
                 //in Gecko and WebKit.  This results in a stack dump instead of
                 //just errors like "Cannot read property 'parentNode' of null"
                 //or "root is null".  Makes it a lot longer, of course.
@@ -1204,91 +1191,248 @@ policies and contribution forms [3].
             this.done();
         }
     };
 
     Test.prototype.step_func = function(func, this_obj)
     {
         var test_this = this;
 
-        if (arguments.length === 1)
-        {
+        if (arguments.length === 1) {
             this_obj = test_this;
         }
 
         return function()
         {
-            test_this.step.apply(test_this, [func, this_obj].concat(
+            return test_this.step.apply(test_this, [func, this_obj].concat(
                 Array.prototype.slice.call(arguments)));
         };
     };
 
     Test.prototype.step_func_done = function(func, this_obj)
     {
         var test_this = this;
 
-        if (arguments.length === 1)
-        {
+        if (arguments.length === 1) {
             this_obj = test_this;
         }
 
         return function()
         {
-            test_this.step.apply(test_this, [func, this_obj].concat(
-                Array.prototype.slice.call(arguments)));
+            if (func) {
+                test_this.step.apply(test_this, [func, this_obj].concat(
+                    Array.prototype.slice.call(arguments)));
+            }
             test_this.done();
         };
-    }
+    };
+
+    Test.prototype.unreached_func = function(description)
+    {
+        return this.step_func(function() {
+            assert_unreached(description);
+        });
+    };
+
+    Test.prototype.add_cleanup = function(callback) {
+        this.cleanup_callbacks.push(callback);
+    };
+
+    Test.prototype.force_timeout = function() {
+        this.set_status(this.TIMEOUT);
+        this.phase = this.phases.HAS_RESULT;
+    };
 
     Test.prototype.set_timeout = function()
     {
-        if (this.timeout_length !== null)
-        {
+        if (this.timeout_length !== null) {
             var this_obj = this;
             this.timeout_id = setTimeout(function()
                                          {
                                              this_obj.timeout();
                                          }, this.timeout_length);
         }
-    }
+    };
 
     Test.prototype.set_status = function(status, message)
     {
         this.status = status;
         this.message = message;
-    }
+    };
 
     Test.prototype.timeout = function()
     {
         this.timeout_id = null;
-        this.set_status(this.TIMEOUT, "Test timed out")
+        this.set_status(this.TIMEOUT, "Test timed out");
         this.phase = this.phases.HAS_RESULT;
         this.done();
     };
 
     Test.prototype.done = function()
     {
         if (this.phase == this.phases.COMPLETE) {
             return;
-        } else if (this.phase <= this.phases.STARTED)
-        {
-            this.set_status(this.PASS, null);
         }
 
-        if (this.status == this.NOTRUN)
-        {
-            alert(this.phase);
+        if (this.phase <= this.phases.STARTED) {
+            this.set_status(this.PASS, null);
         }
 
         this.phase = this.phases.COMPLETE;
 
         clearTimeout(this.timeout_id);
         tests.result(this);
+        this.cleanup();
+    };
+
+    Test.prototype.cleanup = function() {
+        forEach(this.cleanup_callbacks,
+                function(cleanup_callback) {
+                    cleanup_callback();
+                });
+    };
+
+    /*
+     * A RemoteTest object mirrors a Test object on a remote worker. The
+     * associated RemoteWorker updates the RemoteTest object in response to
+     * received events. In turn, the RemoteTest object replicates these events
+     * on the local document. This allows listeners (test result reporting
+     * etc..) to transparently handle local and remote events.
+     */
+    function RemoteTest(clone) {
+        var this_obj = this;
+        Object.keys(clone).forEach(
+                function(key) {
+                    this_obj[key] = clone[key];
+                });
+        this.index = null;
+        this.phase = this.phases.INITIAL;
+        this.update_state_from(clone);
+        tests.push(this);
+    }
+
+    RemoteTest.prototype.structured_clone = function() {
+        var clone = {};
+        Object.keys(this).forEach(
+                function(key) {
+                    if (typeof(this[key]) === "object") {
+                        clone[key] = merge({}, this[key]);
+                    } else {
+                        clone[key] = this[key];
+                    }
+                });
+        clone.phases = merge({}, this.phases);
+        return clone;
     };
 
+    RemoteTest.prototype.cleanup = function() {};
+    RemoteTest.prototype.phases = Test.prototype.phases;
+    RemoteTest.prototype.update_state_from = function(clone) {
+        this.status = clone.status;
+        this.message = clone.message;
+        if (this.phase === this.phases.INITIAL) {
+            this.phase = this.phases.STARTED;
+        }
+    };
+    RemoteTest.prototype.done = function() {
+        this.phase = this.phases.COMPLETE;
+    }
+
+    /*
+     * A RemoteWorker listens for test events from a worker. These events are
+     * then used to construct and maintain RemoteTest objects that mirror the
+     * tests running on the remote worker.
+     */
+    function RemoteWorker(worker) {
+        this.running = true;
+        this.tests = new Array();
+
+        var this_obj = this;
+        worker.onerror = function(error) { this_obj.worker_error(error); };
+
+        var message_port;
+
+        if (is_service_worker(worker)) {
+            // The ServiceWorker's implicit MessagePort is currently not
+            // reliably accessible from the ServiceWorkerGlobalScope due to
+            // Blink setting MessageEvent.source to null for messages sent via
+            // ServiceWorker.postMessage(). Until that's resolved, create an
+            // explicit MessageChannel and pass one end to the worker.
+            var message_channel = new MessageChannel();
+            message_port = message_channel.port1;
+            message_port.start();
+            worker.postMessage({type: "connect"}, [message_channel.port2]);
+        } else if (is_shared_worker(worker)) {
+            message_port = worker.port;
+        } else {
+            message_port = worker;
+        }
+
+        // Keeping a reference to the worker until worker_done() is seen
+        // prevents the Worker object and its MessageChannel from going away
+        // before all the messages are dispatched.
+        this.worker = worker;
+
+        message_port.onmessage =
+            function(message) {
+                if (this_obj.running && (message.data.type in this_obj.message_handlers)) {
+                    this_obj.message_handlers[message.data.type].call(this_obj, message.data);
+                }
+            };
+    }
+
+    RemoteWorker.prototype.worker_error = function(error) {
+        var message = error.message || String(error);
+        var filename = (error.filename ? " " + error.filename: "");
+        // FIXME: Display worker error states separately from main document
+        // error state.
+        this.worker_done({
+            status: {
+                status: tests.status.ERROR,
+                message: "Error in worker" + filename + ": " + message
+            }
+        });
+        error.preventDefault();
+    };
+
+    RemoteWorker.prototype.test_state = function(data) {
+        var remote_test = this.tests[data.test.index];
+        if (!remote_test) {
+            remote_test = new RemoteTest(data.test);
+            this.tests[data.test.index] = remote_test;
+        }
+        remote_test.update_state_from(data.test);
+        tests.notify_test_state(remote_test);
+    };
+
+    RemoteWorker.prototype.test_done = function(data) {
+        var remote_test = this.tests[data.test.index];
+        remote_test.update_state_from(data.test);
+        remote_test.done();
+        tests.result(remote_test);
+    };
+
+    RemoteWorker.prototype.worker_done = function(data) {
+        if (tests.status.status === null &&
+            data.status.status !== data.status.OK) {
+            tests.status.status = data.status.status;
+            tests.status.message = data.status.message;
+        }
+        this.running = false;
+        this.worker = null;
+        if (tests.all_done()) {
+            tests.complete();
+        }
+    };
+
+    RemoteWorker.prototype.message_handlers = {
+        test_state: RemoteWorker.prototype.test_state,
+        result: RemoteWorker.prototype.test_done,
+        complete: RemoteWorker.prototype.worker_done
+    };
 
     /*
      * Harness
      */
 
     function TestsStatus()
     {
         this.status = null;
@@ -1300,18 +1444,17 @@ policies and contribution forms [3].
         ERROR:1,
         TIMEOUT:2
     };
 
     TestsStatus.prototype = merge({}, TestsStatus.statuses);
 
     TestsStatus.prototype.structured_clone = function()
     {
-        if(!this._structured_clone)
-        {
+        if (!this._structured_clone) {
             var msg = this.message;
             msg = msg ? String(msg) : msg;
             this._structured_clone = merge({
                 status:this.status,
                 message:msg
             }, TestsStatus.statuses);
         }
         return this._structured_clone;
@@ -1328,132 +1471,113 @@ policies and contribution forms [3].
             HAVE_TESTS:2,
             HAVE_RESULTS:3,
             COMPLETE:4
         };
         this.phase = this.phases.INITIAL;
 
         this.properties = {};
 
-        //All tests can't be done until the load event fires
-        this.all_loaded = false;
         this.wait_for_finish = false;
         this.processing_callbacks = false;
 
         this.allow_uncaught_exception = false;
 
+        this.file_is_test = false;
+
         this.timeout_multiplier = 1;
-        this.timeout_length = this.get_timeout();
+        this.timeout_length = test_environment.test_timeout();
         this.timeout_id = null;
 
         this.start_callbacks = [];
+        this.test_state_callbacks = [];
         this.test_done_callbacks = [];
         this.all_done_callbacks = [];
 
+        this.pending_workers = [];
+
         this.status = new TestsStatus();
 
         var this_obj = this;
 
-        on_event(window, "load",
-                 function()
-                 {
-                     this_obj.all_loaded = true;
-                     if (this_obj.all_done())
-                     {
-                         this_obj.complete();
-                     }
-                 });
+        test_environment.add_on_loaded_callback(function() {
+            if (this_obj.all_done()) {
+                this_obj.complete();
+            }
+        });
 
         this.set_timeout();
     }
 
     Tests.prototype.setup = function(func, properties)
     {
-        if (this.phase >= this.phases.HAVE_RESULTS)
-        {
+        if (this.phase >= this.phases.HAVE_RESULTS) {
             return;
         }
-        if (this.phase < this.phases.SETUP)
-        {
+
+        if (this.phase < this.phases.SETUP) {
             this.phase = this.phases.SETUP;
         }
 
         this.properties = properties;
 
-        for (var p in properties)
-        {
-            if (properties.hasOwnProperty(p))
-            {
-                var value = properties[p]
+        for (var p in properties) {
+            if (properties.hasOwnProperty(p)) {
+                var value = properties[p];
                 if (p == "allow_uncaught_exception") {
                     this.allow_uncaught_exception = value;
-                }
-                else if (p == "explicit_done" && value)
-                {
+                } else if (p == "explicit_done" && value) {
                     this.wait_for_finish = true;
-                }
-                else if (p == "explicit_timeout" && value) {
+                } else if (p == "explicit_timeout" && value) {
                     this.timeout_length = null;
                     if (this.timeout_id)
                     {
                         clearTimeout(this.timeout_id);
                     }
-                }
-                else if (p == "timeout_multiplier")
-                {
+                } else if (p == "timeout_multiplier") {
                     this.timeout_multiplier = value;
                 }
             }
         }
 
-        if (func)
-        {
-            try
-            {
+        if (func) {
+            try {
                 func();
-            } catch(e)
-            {
+            } catch (e) {
                 this.status.status = this.status.ERROR;
-                this.status.message = e;
-            };
+                this.status.message = String(e);
+            }
         }
         this.set_timeout();
     };
 
-    Tests.prototype.get_timeout = function()
-    {
-        var metas = document.getElementsByTagName("meta");
-        for (var i=0; i<metas.length; i++)
-        {
-            if (metas[i].name == "timeout")
-            {
-                if (metas[i].content == "long")
-                {
-                    return settings.harness_timeout.long;
-                }
-                break;
-            }
+    Tests.prototype.set_file_is_test = function() {
+        if (this.tests.length > 0) {
+            throw new Error("Tried to set file as test after creating a test");
         }
-        return settings.harness_timeout.normal;
-    }
+        this.wait_for_finish = true;
+        this.file_is_test = true;
+        // Create the test, which will add it to the list of tests
+        async_test();
+    };
 
-    Tests.prototype.set_timeout = function()
-    {
+    Tests.prototype.set_timeout = function() {
         var this_obj = this;
         clearTimeout(this.timeout_id);
-        if (this.timeout_length !== null)
-        {
+        if (this.timeout_length !== null) {
             this.timeout_id = setTimeout(function() {
                                              this_obj.timeout();
                                          }, this.timeout_length);
         }
     };
 
     Tests.prototype.timeout = function() {
-        this.status.status = this.status.TIMEOUT;
+        if (this.status.status === null) {
+            this.status.status = this.status.TIMEOUT;
+        }
         this.complete();
     };
 
     Tests.prototype.end_wait = function()
     {
         this.wait_for_finish = false;
         if (this.all_done()) {
             this.complete();
@@ -1461,227 +1585,157 @@ policies and contribution forms [3].
     };
 
     Tests.prototype.push = function(test)
     {
         if (this.phase < this.phases.HAVE_TESTS) {
             this.start();
         }
         this.num_pending++;
-        this.tests.push(test);
+        test.index = this.tests.push(test);
+        this.notify_test_state(test);
+    };
+
+    Tests.prototype.notify_test_state = function(test) {
+        var this_obj = this;
+        forEach(this.test_state_callbacks,
+                function(callback) {
+                    callback(test, this_obj);
+                });
     };
 
     Tests.prototype.all_done = function() {
-        return (this.all_loaded && this.num_pending === 0 &&
-                !this.wait_for_finish && !this.processing_callbacks);
+        return (this.tests.length > 0 && test_environment.all_loaded &&
+                this.num_pending === 0 && !this.wait_for_finish &&
+                !this.processing_callbacks &&
+                !this.pending_workers.some(function(w) { return w.running; }));
     };
 
     Tests.prototype.start = function() {
         this.phase = this.phases.HAVE_TESTS;
         this.notify_start();
     };
 
     Tests.prototype.notify_start = function() {
         var this_obj = this;
         forEach (this.start_callbacks,
                  function(callback)
                  {
                      callback(this_obj.properties);
                  });
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin && w.start_callback)
-                    {
-                        try
-                        {
-                            w.start_callback(this_obj.properties);
-                        }
-                        catch(e)
-                        {
-                            if (debug)
-                            {
-                                throw(e);
-                            }
-                        }
-                    }
-                    if (supports_post_message(w) && w !== self)
-                    {
-                        w.postMessage({
-                            type: "start",
-                            properties: this_obj.properties
-                        }, "*");
-                    }
-                });
     };
 
     Tests.prototype.result = function(test)
     {
-        if (this.phase > this.phases.HAVE_RESULTS)
-        {
+        if (this.phase > this.phases.HAVE_RESULTS) {
             return;
         }
         this.phase = this.phases.HAVE_RESULTS;
         this.num_pending--;
         this.notify_result(test);
     };
 
     Tests.prototype.notify_result = function(test) {
         var this_obj = this;
         this.processing_callbacks = true;
         forEach(this.test_done_callbacks,
                 function(callback)
                 {
                     callback(test, this_obj);
                 });
-
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin && w.result_callback)
-                    {
-                        try
-                        {
-                            w.result_callback(test);
-                        }
-                        catch(e)
-                        {
-                            if(debug) {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (supports_post_message(w) && w !== self)
-                    {
-                        w.postMessage({
-                            type: "result",
-                            test: test.structured_clone()
-                        }, "*");
-                    }
-                });
         this.processing_callbacks = false;
-        if (this_obj.all_done())
-        {
+        if (this_obj.all_done()) {
             this_obj.complete();
         }
     };
 
     Tests.prototype.complete = function() {
         if (this.phase === this.phases.COMPLETE) {
             return;
         }
         this.phase = this.phases.COMPLETE;
         var this_obj = this;
         this.tests.forEach(
             function(x)
             {
-                if(x.status === x.NOTRUN)
-                {
+                if (x.phase < x.phases.COMPLETE) {
                     this_obj.notify_result(x);
+                    x.cleanup();
+                    x.phase = x.phases.COMPLETE;
                 }
             }
         );
         this.notify_complete();
     };
 
-    Tests.prototype.notify_complete = function()
-    {
-        clearTimeout(this.timeout_id);
+    Tests.prototype.notify_complete = function() {
         var this_obj = this;
-        var tests = map(this_obj.tests,
-                        function(test)
-                        {
-                            return test.structured_clone();
-                        });
-        if (this.status.status === null)
-        {
+        if (this.status.status === null) {
             this.status.status = this.status.OK;
         }
 
         forEach (this.all_done_callbacks,
                  function(callback)
                  {
                      callback(this_obj.tests, this_obj.status);
                  });
-
-        forEach_windows(
-                function(w, is_same_origin)
-                {
-                    if(is_same_origin && w.completion_callback)
-                    {
-                        try
-                        {
-                            w.completion_callback(this_obj.tests, this_obj.status);
-                        }
-                        catch(e)
-                        {
-                            if (debug)
-                            {
-                                throw e;
-                            }
-                        }
-                    }
-                    if (supports_post_message(w) && w !== self)
-                    {
-                        w.postMessage({
-                            type: "complete",
-                            tests: tests,
-                            status: this_obj.status.structured_clone()
-                        }, "*");
-                    }
-                });
     };
 
-    var tests = new Tests();
+    Tests.prototype.fetch_tests_from_worker = function(worker) {
+        if (this.phase >= this.phases.COMPLETE) {
+            return;
+        }
 
-    window.onerror = function(msg) {
-        if (!tests.allow_uncaught_exception)
-        {
-            tests.status.status = tests.status.ERROR;
-            tests.status.message = msg;
-            tests.complete();
-        }
+        this.pending_workers.push(new RemoteWorker(worker));
+    };
+
+    function fetch_tests_from_worker(port) {
+        tests.fetch_tests_from_worker(port);
     }
+    expose(fetch_tests_from_worker, 'fetch_tests_from_worker');
 
     function timeout() {
-        if (tests.timeout_length === null)
-        {
+        if (tests.timeout_length === null) {
             tests.timeout();
         }
     }
     expose(timeout, 'timeout');
 
     function add_start_callback(callback) {
         tests.start_callbacks.push(callback);
     }
 
+    function add_test_state_callback(callback) {
+        tests.test_state_callbacks.push(callback);
+    }
+
     function add_result_callback(callback)
     {
         tests.test_done_callbacks.push(callback);
     }
 
     function add_completion_callback(callback)
     {
        tests.all_done_callbacks.push(callback);
     }
 
     expose(add_start_callback, 'add_start_callback');
+    expose(add_test_state_callback, 'add_test_state_callback');
     expose(add_result_callback, 'add_result_callback');
     expose(add_completion_callback, 'add_completion_callback');
 
     /*
      * Output listener
     */
 
     function Output() {
-      this.output_document = document;
-      this.output_node = null;
-      this.done_count = 0;
-      this.enabled = settings.output;
-      this.phase = this.INITIAL;
+        this.output_document = document;
+        this.output_node = null;
+        this.enabled = settings.output;
+        this.phase = this.INITIAL;
     }
 
     Output.prototype.INITIAL = 0;
     Output.prototype.STARTED = 1;
     Output.prototype.HAVE_RESULTS = 2;
     Output.prototype.COMPLETE = 3;
 
     Output.prototype.setup = function(properties) {
@@ -1690,106 +1744,115 @@ policies and contribution forms [3].
         }
 
         //If output is disabled in testharnessreport.js the test shouldn't be
         //able to override that
         this.enabled = this.enabled && (properties.hasOwnProperty("output") ?
                                         properties.output : settings.output);
     };
 
-    Output.prototype.init = function(properties)
-    {
+    Output.prototype.init = function(properties) {
         if (this.phase >= this.STARTED) {
             return;
         }
         if (properties.output_document) {
             this.output_document = properties.output_document;
         } else {
             this.output_document = document;
         }
         this.phase = this.STARTED;
     };
 
-    Output.prototype.resolve_log = function()
-    {
+    Output.prototype.resolve_log = function() {
         var output_document;
-        if (typeof this.output_document === "function")
-        {
+        if (typeof this.output_document === "function") {
             output_document = this.output_document.apply(undefined);
-        } else 
-        {
+        } else {
             output_document = this.output_document;
         }
-        if (!output_document)
-        {
+        if (!output_document) {
             return;
         }
         var node = output_document.getElementById("log");
-        if (node)
-        {
-            this.output_document = output_document;
-            this.output_node = node;
+        if (!node) {
+            if (!document.body || document.readyState == "loading") {
+                return;
+            }
+            node = output_document.createElement("div");
+            node.id = "log";
+            output_document.body.appendChild(node);
         }
+        this.output_document = output_document;
+        this.output_node = node;
     };
 
-    Output.prototype.show_status = function(test)
-    {
-        if (this.phase < this.STARTED)
-        {
+    Output.prototype.show_status = function() {
+        if (this.phase < this.STARTED) {
             this.init();
         }
-        if (!this.enabled)
-        {
+        if (!this.enabled) {
             return;
         }
-        if (this.phase < this.HAVE_RESULTS)
-        {
+        if (this.phase < this.HAVE_RESULTS) {
             this.resolve_log();
             this.phase = this.HAVE_RESULTS;
         }
-        this.done_count++;
-        if (this.output_node)
-        {
-            if (this.done_count < 100
-            || (this.done_count < 1000 && this.done_count % 100 == 0)
-            || this.done_count % 1000 == 0) {
-                this.output_node.textContent = "Running, "
-                    + this.done_count + " complete, "
-                    + tests.num_pending + " remain";
+        var done_count = tests.tests.length - tests.num_pending;
+        if (this.output_node) {
+            if (done_count < 100 ||
+                (done_count < 1000 && done_count % 100 === 0) ||
+                done_count % 1000 === 0) {
+                this.output_node.textContent = "Running, " +
+                    done_count + " complete, " +
+                    tests.num_pending + " remain";
             }
         }
     };
 
-    Output.prototype.show_results = function (tests, harness_status)
-    {
+    Output.prototype.show_results = function (tests, harness_status) {
         if (this.phase >= this.COMPLETE) {
             return;
         }
-        if (!this.enabled)
-        {
+        if (!this.enabled) {
             return;
         }
         if (!this.output_node) {
             this.resolve_log();
         }
         this.phase = this.COMPLETE;
 
         var log = this.output_node;
-        if (!log)
-        {
+        if (!log) {
             return;
         }
         var output_document = this.output_document;
 
-        while (log.lastChild)
-        {
+        while (log.lastChild) {
             log.removeChild(log.lastChild);
         }
 
-        if (script_prefix != null) {
+        var script_prefix = null;
+        var scripts = document.getElementsByTagName("script");
+        for (var i = 0; i < scripts.length; i++) {
+            var src;
+            if (scripts[i].src) {
+                src = scripts[i].src;
+            } else if (scripts[i].href) {
+                //SVG case
+                src = scripts[i].href.baseVal;
+            }
+
+            var matches = src && src.match(/^(.*\/|)testharness\.js$/);
+            if (matches) {
+                script_prefix = matches[1];
+                break;
+            }
+        }
+
+        if (script_prefix !== null) {
             var stylesheet = output_document.createElementNS(xhtml_ns, "link");
             stylesheet.setAttribute("rel", "stylesheet");
             stylesheet.setAttribute("href", script_prefix + "testharness.css");
             var heads = output_document.getElementsByTagName("head");
             if (heads.length) {
                 heads[0].appendChild(stylesheet);
             }
         }
@@ -1801,88 +1864,78 @@ policies and contribution forms [3].
 
         var status_text = {};
         status_text[Test.prototype.PASS] = "Pass";
         status_text[Test.prototype.FAIL] = "Fail";
         status_text[Test.prototype.TIMEOUT] = "Timeout";
         status_text[Test.prototype.NOTRUN] = "Not Run";
 
         var status_number = {};
-        forEach(tests, function(test) {
+        forEach(tests,
+                function(test) {
                     var status = status_text[test.status];
-                    if (status_number.hasOwnProperty(status))
-                    {
+                    if (status_number.hasOwnProperty(status)) {
                         status_number[status] += 1;
                     } else {
                         status_number[status] = 1;
                     }
                 });
 
         function status_class(status)
         {
             return status.replace(/\s/g, '').toLowerCase();
         }
 
         var summary_template = ["section", {"id":"summary"},
                                 ["h2", {}, "Summary"],
-                                function(vars)
+                                function()
                                 {
-                                    if (harness_status.status === harness_status.OK)
-                                    {
-                                        return null;
-                                    }
-                                    else
-                                    {
-                                        var status = status_text_harness[harness_status.status];
-                                        var rv = [["p", {"class":status_class(status)}]];
 
-                                        if (harness_status.status === harness_status.ERROR)
-                                        {
-                                            rv[0].push("Harness encountered an error:");
-                                            rv.push(["pre", {}, harness_status.message]);
-                                        }
-                                        else if (harness_status.status === harness_status.TIMEOUT)
-                                        {
-                                            rv[0].push("Harness timed out.");
-                                        }
-                                        else
-                                        {
-                                            rv[0].push("Harness got an unexpected status.");
-                                        }
+                                    var status = status_text_harness[harness_status.status];
+                                    var rv = [["section", {},
+                                               ["p", {},
+                                                "Harness status: ",
+                                                ["span", {"class":status_class(status)},
+                                                 status
+                                                ],
+                                               ]
+                                              ]];
 
-                                        return rv;
+                                    if (harness_status.status === harness_status.ERROR) {
+                                        rv[0].push(["pre", {}, harness_status.message]);
                                     }
+                                    return rv;
                                 },
                                 ["p", {}, "Found ${num_tests} tests"],
-                                function(vars) {
+                                function() {
                                     var rv = [["div", {}]];
-                                    var i=0;
+                                    var i = 0;
                                     while (status_text.hasOwnProperty(i)) {
                                         if (status_number.hasOwnProperty(status_text[i])) {
                                             var status = status_text[i];
                                             rv[0].push(["div", {"class":status_class(status)},
                                                         ["label", {},
                                                          ["input", {type:"checkbox", checked:"checked"}],
                                                          status_number[status] + " " + status]]);
                                         }
                                         i++;
                                     }
                                     return rv;
-                                }];
+                                },
+                               ];
 
         log.appendChild(render(summary_template, {num_tests:tests.length}, output_document));
 
         forEach(output_document.querySelectorAll("section#summary label"),
                 function(element)
                 {
                     on_event(element, "click",
                              function(e)
                              {
-                                 if (output_document.getElementById("results") === null)
-                                 {
+                                 if (output_document.getElementById("results") === null) {
                                      e.preventDefault();
                                      return;
                                  }
                                  var result_class = element.parentNode.getAttribute("class");
                                  var style_element = output_document.querySelector("style#hide-" + result_class);
                                  var input_element = element.querySelector("input");
                                  if (!style_element && !input_element.checked) {
                                      style_element = output_document.createElementNS(xhtml_ns, "style");
@@ -1925,49 +1978,44 @@ policies and contribution forms [3].
                 }
                 return test.properties.assert;
             }
             return '';
         }
 
         log.appendChild(document.createElementNS(xhtml_ns, "section"));
         var assertions = has_assertions();
-        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">"
-            + "<thead><tr><th>Result</th><th>Test Name</th>"
-            + (assertions ? "<th>Assertion</th>" : "")
-            + "<th>Message</th></tr></thead>"
-            + "<tbody>";
+        var html = "<h2>Details</h2><table id='results' " + (assertions ? "class='assertions'" : "" ) + ">" +
+            "<thead><tr><th>Result</th><th>Test Name</th>" +
+            (assertions ? "<th>Assertion</th>" : "") +
+            "<th>Message</th></tr></thead>" +
+            "<tbody>";
         for (var i = 0; i < tests.length; i++) {
-            html += '<tr class="'
-                + escape_html(status_class(status_text[tests[i].status]))
-                + '"><td>'
-                + escape_html(status_text[tests[i].status])
-                + "</td><td>"
-                + escape_html(tests[i].name)
-                + "</td><td>"
-                + (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "")
-                + escape_html(tests[i].message ? tests[i].message : " ")
-                + "</td></tr>";
+            html += '<tr class="' +
+                escape_html(status_class(status_text[tests[i].status])) +
+                '"><td>' +
+                escape_html(status_text[tests[i].status]) +
+                "</td><td>" +
+                escape_html(tests[i].name) +
+                "</td><td>" +
+                (assertions ? escape_html(get_assertion(tests[i])) + "</td><td>" : "") +
+                escape_html(tests[i].message ? tests[i].message : " ") +
+                "</td></tr>";
         }
         html += "</tbody></table>";
         try {
             log.lastChild.innerHTML = html;
         } catch (e) {
             log.appendChild(document.createElementNS(xhtml_ns, "p"))
                .textContent = "Setting innerHTML for the log threw an exception.";
             log.appendChild(document.createElementNS(xhtml_ns, "pre"))
                .textContent = html;
         }
     };
 
-    var output = new Output();
-    add_start_callback(function (properties) {output.init(properties);});
-    add_result_callback(function (test) {output.show_status(tests);});
-    add_completion_callback(function (tests, harness_status) {output.show_results(tests, harness_status);});
-
     /*
      * Template code
      *
      * A template is just a javascript structure. An element is represented as:
      *
      * [tag_name, {attr_name:attr_value}, child1, child2]
      *
      * the children can either be strings (which act like text nodes), other templates or
@@ -2000,369 +2048,306 @@ policies and contribution forms [3].
     {
         return typeof template[0] === "string";
     }
 
     function substitute(template, substitutions)
     {
         if (typeof template === "function") {
             var replacement = template(substitutions);
-            if (replacement)
-            {
-                var rv = substitute(replacement, substitutions);
-                return rv;
-            }
-            else
-            {
+            if (!replacement) {
                 return null;
             }
+
+            return substitute(replacement, substitutions);
         }
-        else if (is_single_node(template))
-        {
+
+        if (is_single_node(template)) {
             return substitute_single(template, substitutions);
         }
-        else
-        {
-            return filter(map(template, function(x) {
-                                  return substitute(x, substitutions);
-                              }), function(x) {return x !== null;});
-        }
+
+        return filter(map(template, function(x) {
+                              return substitute(x, substitutions);
+                          }), function(x) {return x !== null;});
     }
 
     function substitute_single(template, substitutions)
     {
-        var substitution_re = /\${([^ }]*)}/g;
+        var substitution_re = /\$\{([^ }]*)\}/g;
 
         function do_substitution(input) {
             var components = input.split(substitution_re);
             var rv = [];
-            for (var i=0; i<components.length; i+=2)
-            {
+            for (var i = 0; i < components.length; i += 2) {
                 rv.push(components[i]);
-                if (components[i+1])
-                {
-                    rv.push(String(substitutions[components[i+1]]));
+                if (components[i + 1]) {
+                    rv.push(String(substitutions[components[i + 1]]));
+                }
+            }
+            return rv;
+        }
+
+        function substitute_attrs(attrs, rv)
+        {
+            rv[1] = {};
+            for (var name in template[1]) {
+                if (attrs.hasOwnProperty(name)) {
+                    var new_name = do_substitution(name).join("");
+                    var new_value = do_substitution(attrs[name]).join("");
+                    rv[1][new_name] = new_value;
+                }
+            }
+        }
+
+        function substitute_children(children, rv)
+        {
+            for (var i = 0; i < children.length; i++) {
+                if (children[i] instanceof Object) {
+                    var replacement = substitute(children[i], substitutions);
+                    if (replacement !== null) {
+                        if (is_single_node(replacement)) {
+                            rv.push(replacement);
+                        } else {
+                            extend(rv, replacement);
+                        }
+                    }
+                } else {
+                    extend(rv, do_substitution(String(children[i])));
                 }
             }
             return rv;
         }
 
         var rv = [];
         rv.push(do_substitution(String(template[0])).join(""));
 
         if (template[0] === "{text}") {
             substitute_children(template.slice(1), rv);
         } else {
             substitute_attrs(template[1], rv);
             substitute_children(template.slice(2), rv);
         }
 
-        function substitute_attrs(attrs, rv)
-        {
-            rv[1] = {};
-            for (var name in template[1])
-            {
-                if (attrs.hasOwnProperty(name))
-                {
-                    var new_name = do_substitution(name).join("");
-                    var new_value = do_substitution(attrs[name]).join("");
-                    rv[1][new_name] = new_value;
-                };
-            }
-        }
-
-        function substitute_children(children, rv)
-        {
-            for (var i=0; i<children.length; i++)
-            {
-                if (children[i] instanceof Object) {
-                    var replacement = substitute(children[i], substitutions);
-                    if (replacement !== null)
-                    {
-                        if (is_single_node(replacement))
-                        {
-                            rv.push(replacement);
-                        }
-                        else
-                        {
-                            extend(rv, replacement);
-                        }
-                    }
-                }
-                else
-                {
-                    extend(rv, do_substitution(String(children[i])));
-                }
-            }
-            return rv;
-        }
-
         return rv;
     }
 
- function make_dom_single(template, doc)
- {
-     var output_document = doc || document;
-     if (template[0] === "{text}")
-     {
-         var element = output_document.createTextNode("");
-         for (var i=1; i<template.length; i++)
-         {
-             element.data += template[i];
-         }
-     }
-     else
-     {
-         var element = output_document.createElementNS(xhtml_ns, template[0]);
-         for (var name in template[1]) {
-             if (template[1].hasOwnProperty(name))
-             {
-                 element.setAttribute(name, template[1][name]);
-             }
-         }
-         for (var i=2; i<template.length; i++)
-         {
-             if (template[i] instanceof Object)
-             {
-                 var sub_element = make_dom(template[i]);
-                 element.appendChild(sub_element);
-             }
-             else
-             {
-                 var text_node = output_document.createTextNode(template[i]);
-                 element.appendChild(text_node);
-             }
-         }
-     }
+    function make_dom_single(template, doc)
+    {
+        var output_document = doc || document;
+        var element;
+        if (template[0] === "{text}") {
+            element = output_document.createTextNode("");
+            for (var i = 1; i < template.length; i++) {
+                element.data += template[i];
+            }
+        } else {
+            element = output_document.createElementNS(xhtml_ns, template[0]);
+            for (var name in template[1]) {
+                if (template[1].hasOwnProperty(name)) {
+                    element.setAttribute(name, template[1][name]);
+                }
+            }
+            for (var i = 2; i < template.length; i++) {
+                if (template[i] instanceof Object) {
+                    var sub_element = make_dom(template[i]);
+                    element.appendChild(sub_element);
+                } else {
+                    var text_node = output_document.createTextNode(template[i]);
+                    element.appendChild(text_node);
+                }
+            }
+        }
 
-     return element;
- }
-
-
+        return element;
+    }
 
- function make_dom(template, substitutions, output_document)
+    function make_dom(template, substitutions, output_document)
     {
-        if (is_single_node(template))
-        {
+        if (is_single_node(template)) {
             return make_dom_single(template, output_document);
         }
-        else
-        {
-            return map(template, function(x) {
-                           return make_dom_single(x, output_document);
-                       });
-        }
+
+        return map(template, function(x) {
+                       return make_dom_single(x, output_document);
+                   });
     }
 
- function render(template, substitutions, output_document)
+    function render(template, substitutions, output_document)
     {
         return make_dom(substitute(template, substitutions), output_document);
     }
 
     /*
      * Utility funcions
      */
     function assert(expected_true, function_name, description, error, substitutions)
     {
-        if (expected_true !== true)
-        {
-            throw new AssertionError(make_message(function_name, description,
-                                                  error, substitutions));
+        if (tests.tests.length === 0) {
+            tests.set_file_is_test();
+        }
+        if (expected_true !== true) {
+            var msg = make_message(function_name, description,
+                                   error, substitutions);
+            throw new AssertionError(msg);
         }
     }
 
     function AssertionError(message)
     {
         this.message = message;
     }
 
+    AssertionError.prototype.toString = function() {
+        return this.message;
+    };
+
     function make_message(function_name, description, error, substitutions)
     {
         for (var p in substitutions) {
             if (substitutions.hasOwnProperty(p)) {
                 substitutions[p] = format_value(substitutions[p]);
             }
         }
         var node_form = substitute(["{text}", "${function_name}: ${description}" + error],
                                    merge({function_name:function_name,
                                           description:(description?description + " ":"")},
                                           substitutions));
         return node_form.slice(1).join("");
     }
 
     function filter(array, callable, thisObj) {
         var rv = [];
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
                 var pass = callable.call(thisObj, array[i], i, array);
                 if (pass) {
                     rv.push(array[i]);
                 }
             }
         }
         return rv;
     }
 
     function map(array, callable, thisObj)
     {
         var rv = [];
         rv.length = array.length;
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
                 rv[i] = callable.call(thisObj, array[i], i, array);
             }
         }
         return rv;
     }
 
     function extend(array, items)
     {
         Array.prototype.push.apply(array, items);
     }
 
     function forEach (array, callback, thisObj)
     {
-        for (var i=0; i<array.length; i++)
-        {
-            if (array.hasOwnProperty(i))
-            {
+        for (var i = 0; i < array.length; i++) {
+            if (array.hasOwnProperty(i)) {
                 callback.call(thisObj, array[i], i, array);
             }
         }
     }
 
     function merge(a,b)
     {
         var rv = {};
         var p;
-        for (p in a)
-        {
+        for (p in a) {
             rv[p] = a[p];
         }
         for (p in b) {
             rv[p] = b[p];
         }
         return rv;
     }
 
     function expose(object, name)
     {
         var components = name.split(".");
-        var target = window;
-        for (var i=0; i<components.length - 1; i++)
-        {
-            if (!(components[i] in target))
-            {
+        var target = test_environment.global_scope();
+        for (var i = 0; i < components.length - 1; i++) {
+            if (!(components[i] in target)) {
                 target[components[i]] = {};
             }
             target = target[components[i]];
         }
         target[components[components.length - 1]] = object;
     }
 
-    function forEach_windows(callback) {
-        // Iterate of the the windows [self ... top, opener]. The callback is passed
-        // two objects, the first one is the windows object itself, the second one
-        // is a boolean indicating whether or not its on the same origin as the
-        // current window.
-        var cache = forEach_windows.result_cache;
-        if (!cache) {
-            cache = [[self, true]];
-            var w = self;
-            var i = 0;
-            var so;
-            var origins = location.ancestorOrigins;
-            while (w != w.parent)
-            {
-                w = w.parent;
-                // In WebKit, calls to parent windows' properties that aren't on the same
-                // origin cause an error message to be displayed in the error console but
-                // don't throw an exception. This is a deviation from the current HTML5
-                // spec. See: https://bugs.webkit.org/show_bug.cgi?id=43504
-                // The problem with WebKit's behavior is that it pollutes the error console
-                // with error messages that can't be caught.
-                //
-                // This issue can be mitigated by relying on the (for now) proprietary
-                // `location.ancestorOrigins` property which returns an ordered list of
-                // the origins of enclosing windows. See:
-                // http://trac.webkit.org/changeset/113945.
-                if(origins) {
-                    so = (location.origin == origins[i]);
-                }
-                else
-                {
-                    so = is_same_origin(w);
-                }
-                cache.push([w, so]);
-                i++;
-            }
-            w = window.opener;
-            if(w)
-            {
-                // window.opener isn't included in the `location.ancestorOrigins` prop.
-                // We'll just have to deal with a simple check and an error msg on WebKit
-                // browsers in this case.
-                cache.push([w, is_same_origin(w)]);
-            }
-            forEach_windows.result_cache = cache;
-        }
-
-        forEach(cache,
-                function(a)
-                {
-                    callback.apply(null, a);
-                });
-    }
-
     function is_same_origin(w) {
         try {
             'random_prop' in w;
             return true;
-        } catch(e) {
+        } catch (e) {
             return false;
         }
     }
 
     function supports_post_message(w)
     {
         var supports;
         var type;
         // Given IE  implements postMessage across nested iframes but not across
         // windows or tabs, you can't infer cross-origin communication from the presence
         // of postMessage on the current window object only.
         //
         // Touching the postMessage prop on a window can throw if the window is
         // not from the same origin AND post message is not supported in that
         // browser. So just doing an existence test here won't do, you also need
         // to wrap it in a try..cacth block.
-        try
-        {
+        try {
             type = typeof w.postMessage;
-            if (type === "function")
-            {
+            if (type === "function") {
                 supports = true;
             }
+
             // IE8 supports postMessage, but implements it as a host object which
             // returns "object" as its `typeof`.
-            else if (type === "object")
-            {
+            else if (type === "object") {
                 supports = true;
             }
+
             // This is the case where postMessage isn't supported AND accessing a
             // window property across origins does NOT throw (e.g. old Safari browser).
-            else
-            {
+            else {
                 supports = false;
             }
-        }
-        catch(e) {
+        } catch (e) {
             // This is the case where postMessage isn't supported AND accessing a
             // window property across origins throws (e.g. old Firefox browser).
             supports = false;
         }
         return supports;
     }
+
+    /**
+     * Setup globals
+     */
+
+    var tests = new Tests();
+
+    addEventListener("error", function(e) {
+        if (tests.file_is_test) {
+            var test = tests.tests[0];
+            if (test.phase >= test.phases.HAS_RESULT) {
+                return;
+            }
+            var message = e.message;
+            test.set_status(test.FAIL, message);
+            test.phase = test.phases.HAS_RESULT;
+            test.done();
+            done();
+        } else if (!tests.allow_uncaught_exception) {
+            tests.status.status = tests.status.ERROR;
+            tests.status.message = e.message;
+        }
+    });
+
+    test_environment.on_tests_ready();
+
 })();
 // vim: set expandtab shiftwidth=4 tabstop=4:
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1684,21 +1684,17 @@ MediaDecoder::IsRawEnabled()
 {
   return Preferences::GetBool("media.raw.enabled");
 }
 #endif
 
 bool
 MediaDecoder::IsOpusEnabled()
 {
-#ifdef MOZ_OPUS
   return Preferences::GetBool("media.opus.enabled");
-#else
-  return false;
-#endif
 }
 
 bool
 MediaDecoder::IsOggEnabled()
 {
   return Preferences::GetBool("media.ogg.enabled");
 }
 
--- a/dom/media/encoder/MediaEncoder.cpp
+++ b/dom/media/encoder/MediaEncoder.cpp
@@ -7,21 +7,18 @@
 #include "nsIPrincipal.h"
 #include "nsMimeTypes.h"
 #include "prlog.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPtr.h"
 
 #include"GeckoProfiler.h"
 #include "OggWriter.h"
-#ifdef MOZ_OPUS
 #include "OpusTrackEncoder.h"
 
-#endif
-
 #ifdef MOZ_VORBIS
 #include "VorbisTrackEncoder.h"
 #endif
 #ifdef MOZ_WEBM_ENCODER
 #include "VorbisTrackEncoder.h"
 #include "VP8TrackEncoder.h"
 #include "WebMWriter.h"
 #endif
--- a/dom/media/encoder/moz.build
+++ b/dom/media/encoder/moz.build
@@ -6,33 +6,31 @@
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += ['fmp4_muxer']
 
 EXPORTS += [
     'ContainerWriter.h',
     'EncodedFrameContainer.h',
     'MediaEncoder.h',
+    'OpusTrackEncoder.h',
     'TrackEncoder.h',
     'TrackMetadataBase.h',
 ]
 
 UNIFIED_SOURCES += [
     'MediaEncoder.cpp',
+    'OpusTrackEncoder.cpp',
     'TrackEncoder.cpp',
 ]
 
 if CONFIG['MOZ_OMX_ENCODER']:
     EXPORTS += ['OmxTrackEncoder.h']
     UNIFIED_SOURCES += ['OmxTrackEncoder.cpp']
 
-if CONFIG['MOZ_OPUS']:
-    EXPORTS += ['OpusTrackEncoder.h']
-    UNIFIED_SOURCES += ['OpusTrackEncoder.cpp']
-
 if CONFIG['MOZ_WEBM_ENCODER']:
     EXPORTS += ['VorbisTrackEncoder.h',
                 'VP8TrackEncoder.h',
     ]
     UNIFIED_SOURCES += ['VorbisTrackEncoder.cpp',
                         'VP8TrackEncoder.cpp',
     ]
     LOCAL_INCLUDES += ['/media/libyuv/include']
--- a/dom/media/ogg/OggCodecState.cpp
+++ b/dom/media/ogg/OggCodecState.cpp
@@ -44,20 +44,18 @@ OggCodecState*
 OggCodecState::Create(ogg_page* aPage)
 {
   NS_ASSERTION(ogg_page_bos(aPage), "Only call on BOS page!");
   nsAutoPtr<OggCodecState> codecState;
   if (aPage->body_len > 6 && memcmp(aPage->body+1, "theora", 6) == 0) {
     codecState = new TheoraState(aPage);
   } else if (aPage->body_len > 6 && memcmp(aPage->body+1, "vorbis", 6) == 0) {
     codecState = new VorbisState(aPage);
-#ifdef MOZ_OPUS
   } else if (aPage->body_len > 8 && memcmp(aPage->body, "OpusHead", 8) == 0) {
     codecState = new OpusState(aPage);
-#endif
   } else if (aPage->body_len > 8 && memcmp(aPage->body, "fishead\0", 8) == 0) {
     codecState = new SkeletonState(aPage);
   } else {
     codecState = new OggCodecState(aPage, false);
   }
   return codecState->OggCodecState::Init() ? codecState.forget() : nullptr;
 }
 
@@ -790,17 +788,16 @@ nsresult VorbisState::ReconstructVorbisG
 
   mPrevVorbisBlockSize = vorbis_packet_blocksize(&mInfo, last);
   mPrevVorbisBlockSize = std::max(static_cast<long>(0), mPrevVorbisBlockSize);
   mGranulepos = last->granulepos;
 
   return NS_OK;
 }
 
-#ifdef MOZ_OPUS
 OpusState::OpusState(ogg_page* aBosPage) :
   OggCodecState(aBosPage, true),
   mParser(nullptr),
   mDecoder(nullptr),
   mSkip(0),
   mPrevPacketGranulepos(0),
   mPrevPageGranulepos(0)
 {
@@ -1073,17 +1070,16 @@ bool OpusState::ReconstructOpusGranulepo
   // total number of samples decodable from the first page with completed
   // packets. This requires looking at the duration of the first packet, too.
   // We MUST reject such streams.
   if (!mDoneReadingHeaders && GetOpusDeltaGP(mUnstamped[0]) > gp)
     return false;
   mPrevPageGranulepos = last->granulepos;
   return true;
 }
-#endif /* MOZ_OPUS */
 
 SkeletonState::SkeletonState(ogg_page* aBosPage) :
   OggCodecState(aBosPage, true),
   mVersion(0),
   mPresentationTime(0),
   mLength(0)
 {
   MOZ_COUNT_CTOR(SkeletonState);
--- a/dom/media/ogg/OggCodecState.h
+++ b/dom/media/ogg/OggCodecState.h
@@ -8,24 +8,22 @@
 
 #include <ogg/ogg.h>
 #include <theora/theoradec.h>
 #ifdef MOZ_TREMOR
 #include <tremor/ivorbiscodec.h>
 #else
 #include <vorbis/codec.h>
 #endif
-#ifdef MOZ_OPUS
 #include <opus/opus.h>
 #include "opus/opus_multistream.h"
 // For MOZ_SAMPLE_TYPE_*
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaDecoderReader.h"
-#endif
 #include <nsAutoRef.h>
 #include <nsDeque.h>
 #include <nsTArray.h>
 #include <nsClassHashtable.h>
 #include "VideoUtils.h"
 
 #include <stdint.h>
 
@@ -318,17 +316,16 @@ private:
   // the stream, with the last packet having a known granulepos. Using this
   // known granulepos, and the known frame numbers, we recover the granulepos
   // of all frames in the array. This enables us to determine their timestamps.
   void ReconstructTheoraGranulepos();
 
 };
 
 class OpusState : public OggCodecState {
-#ifdef MOZ_OPUS
 public:
   explicit OpusState(ogg_page* aBosPage);
   virtual ~OpusState();
 
   CodecType GetType() { return TYPE_OPUS; }
   bool DecodeHeader(ogg_packet* aPacket);
   int64_t Time(int64_t aGranulepos);
   bool Init();
@@ -370,17 +367,16 @@ private:
   // of all frames in the array. This enables us to determine their timestamps.
   bool ReconstructOpusGranulepos();
 
   // Granule position (end sample) of the last decoded Opus page. This is
   // used to calculate the Opus per-packet granule positions on the last page,
   // where we may need to trim some samples from the end.
   int64_t mPrevPageGranulepos;
 
-#endif /* MOZ_OPUS */
 };
 
 // Constructs a 32bit version number out of two 16 bit major,minor
 // version numbers.
 #define SKELETON_VERSION(major, minor) (((major)<<16)|(minor))
 
 enum EMsgHeaderType {
   eContentType,
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -8,22 +8,20 @@
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaDecoder.h"
 #include "OggReader.h"
 #include "VideoUtils.h"
 #include "theora/theoradec.h"
 #include <algorithm>
-#ifdef MOZ_OPUS
 #include "opus/opus.h"
 extern "C" {
 #include "opus/opus_multistream.h"
 }
-#endif
 #include "mozilla/dom/TimeRanges.h"
 #include "mozilla/TimeStamp.h"
 #include "VorbisUtils.h"
 #include "MediaMetadataManager.h"
 #include "nsISeekableStream.h"
 #include "gfx2DGlue.h"
 
 using namespace mozilla::gfx;
@@ -57,19 +55,17 @@ extern PRLogModuleInfo* gMediaDecoderLog
 // lands between the seek target and SEEK_FUZZ_USECS microseconds before the
 // seek target.  This is becaue it's usually quicker to just keep downloading
 // from an exisiting connection than to do another bisection inside that
 // small range, which would open a new HTTP connetion.
 static const uint32_t SEEK_FUZZ_USECS = 500000;
 
 // The number of microseconds of "pre-roll" we use for Opus streams.
 // The specification recommends 80 ms.
-#ifdef MOZ_OPUS
 static const int64_t SEEK_OPUS_PREROLL = 80 * USECS_PER_MS;
-#endif /* MOZ_OPUS */
 
 enum PageSyncResult {
   PAGE_SYNC_ERROR = 1,
   PAGE_SYNC_END_OF_RANGE= 2,
   PAGE_SYNC_OK = 3
 };
 
 // Reads a page from the media resource.
@@ -131,20 +127,18 @@ static void InitTrack(MessageField* aMsg
               aEnable);
 }
 
 OggReader::OggReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder),
     mMonitor("OggReader"),
     mTheoraState(nullptr),
     mVorbisState(nullptr),
-#ifdef MOZ_OPUS
     mOpusState(nullptr),
     mOpusEnabled(MediaDecoder::IsOpusEnabled()),
-#endif /* MOZ_OPUS */
     mSkeletonState(nullptr),
     mVorbisSerial(0),
     mOpusSerial(0),
     mTheoraSerial(0),
     mOpusPreSkip(0),
     mIsChained(false),
     mDecodedAudioFrames(0)
 {
@@ -178,21 +172,19 @@ nsresult OggReader::ResetDecode(bool sta
     res = NS_ERROR_FAILURE;
   }
 
   // Discard any previously buffered packets/pages.
   ogg_sync_reset(&mOggState);
   if (mVorbisState && NS_FAILED(mVorbisState->Reset())) {
     res = NS_ERROR_FAILURE;
   }
-#ifdef MOZ_OPUS
   if (mOpusState && NS_FAILED(mOpusState->Reset(start))) {
     res = NS_ERROR_FAILURE;
   }
-#endif /* MOZ_OPUS */
   if (mTheoraState && NS_FAILED(mTheoraState->Reset())) {
     res = NS_ERROR_FAILURE;
   }
 
   return res;
 }
 
 bool OggReader::ReadHeaders(OggCodecState* aState)
@@ -212,20 +204,18 @@ void OggReader::BuildSerialList(nsTArray
 {
   // Obtaining seek index information for currently active bitstreams.
   if (HasVideo()) {
     aTracks.AppendElement(mTheoraState->mSerial);
   }
   if (HasAudio()) {
     if (mVorbisState) {
       aTracks.AppendElement(mVorbisState->mSerial);
-#ifdef MOZ_OPUS
     } else if (mOpusState) {
       aTracks.AppendElement(mOpusState->mSerial);
-#endif /* MOZ_OPUS */
     }
   }
 }
 
 void OggReader::SetupTargetTheora(TheoraState* aTheoraState)
 {
   if (mTheoraState) {
     mTheoraState->Reset();
@@ -354,31 +344,29 @@ void OggReader::SetupMediaTracksInfo(con
 
       if (msgInfo) {
         InitTrack(msgInfo, &mInfo.mAudio.mTrackInfo, mVorbisState == vorbisState);
       }
 
       mInfo.mAudio.mHasAudio = true;
       mInfo.mAudio.mRate = vorbisState->mInfo.rate;
       mInfo.mAudio.mChannels = vorbisState->mInfo.channels;
-#ifdef MOZ_OPUS
     } else if (codecState->GetType() == OggCodecState::TYPE_OPUS) {
       OpusState* opusState = static_cast<OpusState*>(codecState);
       if (!(mOpusState && mOpusState->mSerial == opusState->mSerial)) {
         continue;
       }
 
       if (msgInfo) {
         InitTrack(msgInfo, &mInfo.mAudio.mTrackInfo, mOpusState == opusState);
       }
 
       mInfo.mAudio.mHasAudio = true;
       mInfo.mAudio.mRate = opusState->mRate;
       mInfo.mAudio.mChannels = opusState->mChannels;
-#endif
     }
   }
 }
 
 nsresult OggReader::ReadMetadata(MediaInfo* aInfo,
                                  MetadataTags** aTags)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
@@ -445,31 +433,29 @@ nsresult OggReader::ReadMetadata(MediaIn
       } else if (s->GetType() == OggCodecState::TYPE_VORBIS && ReadHeaders(s)) {
         if (!mVorbisState) {
           VorbisState* vorbisState = static_cast<VorbisState*>(s);
           SetupTargetVorbis(vorbisState);
           *aTags = vorbisState->GetTags();
         } else {
           s->Deactivate();
         }
-#ifdef MOZ_OPUS
       } else if (s->GetType() == OggCodecState::TYPE_OPUS && ReadHeaders(s)) {
         if (mOpusEnabled) {
           if (!mOpusState) {
             OpusState* opusState = static_cast<OpusState*>(s);
             SetupTargetOpus(opusState);
             *aTags = opusState->GetTags();
           } else {
             s->Deactivate();
           }
         } else {
           NS_WARNING("Opus decoding disabled."
                      " See media.opus.enabled in about:config");
         }
-#endif // MOZ_OPUS
       } else if (s->GetType() == OggCodecState::TYPE_SKELETON && !mSkeletonState) {
         mSkeletonState = static_cast<SkeletonState*>(s);
       } else {
         // Deactivate any non-primary bitstreams.
         s->Deactivate();
       }
 
     }
@@ -565,17 +551,17 @@ nsresult OggReader::DecodeVorbis(ogg_pac
 
     endFrame -= frames;
     if (vorbis_synthesis_read(&mVorbisState->mDsp, frames) != 0) {
       return NS_ERROR_FAILURE;
     }
   }
   return NS_OK;
 }
-#ifdef MOZ_OPUS
+
 nsresult OggReader::DecodeOpus(ogg_packet* aPacket) {
   NS_ASSERTION(aPacket->granulepos != -1, "Must know opus granulepos!");
 
   // Maximum value is 63*2880, so there's no chance of overflow.
   int32_t frames_number = opus_packet_get_nb_frames(aPacket->packet,
                                                     aPacket->bytes);
   if (frames_number <= 0)
     return NS_ERROR_FAILURE; // Invalid packet header.
@@ -677,57 +663,49 @@ nsresult OggReader::DecodeOpus(ogg_packe
                                  buffer.forget(),
                                  channels,
                                  mOpusState->mRate));
 
   mDecodedAudioFrames += frames;
 
   return NS_OK;
 }
-#endif /* MOZ_OPUS */
 
 bool OggReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
-  DebugOnly<bool> haveCodecState = mVorbisState != nullptr
-#ifdef MOZ_OPUS
-    || mOpusState != nullptr
-#endif /* MOZ_OPUS */
-    ;
+  DebugOnly<bool> haveCodecState = mVorbisState != nullptr ||
+                                   mOpusState != nullptr;
   NS_ASSERTION(haveCodecState, "Need audio codec state to decode audio");
 
   // Read the next data packet. Skip any non-data packets we encounter.
   ogg_packet* packet = 0;
   OggCodecState* codecState;
   if (mVorbisState)
     codecState = static_cast<OggCodecState*>(mVorbisState);
-#ifdef MOZ_OPUS
   else
     codecState = static_cast<OggCodecState*>(mOpusState);
-#endif /* MOZ_OPUS */
   do {
     if (packet) {
       OggCodecState::ReleasePacket(packet);
     }
     packet = NextOggPacket(codecState);
   } while (packet && codecState->IsHeader(packet));
 
   if (!packet) {
     return false;
   }
 
   NS_ASSERTION(packet && packet->granulepos != -1,
     "Must have packet with known granulepos");
   nsAutoRef<ogg_packet> autoRelease(packet);
   if (mVorbisState) {
     DecodeVorbis(packet);
-#ifdef MOZ_OPUS
   } else if (mOpusState) {
     DecodeOpus(packet);
-#endif
   }
 
   if ((packet->e_o_s) && (!ReadOggChain())) {
     // We've encountered an end of bitstream packet, or we've hit the end of
     // file while trying to decode, so inform the audio queue that there'll
     // be no more samples.
     return false;
   }
@@ -744,19 +722,17 @@ void OggReader::SetChained(bool aIsChain
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     mDecoder->SetMediaSeekable(false);
   }
 }
 
 bool OggReader::ReadOggChain()
 {
   bool chained = false;
-#ifdef MOZ_OPUS
   OpusState* newOpusState = nullptr;
-#endif /* MOZ_OPUS */
   VorbisState* newVorbisState = nullptr;
   nsAutoPtr<MetadataTags> tags;
 
   if (HasVideo() || HasSkeleton() || !HasAudio()) {
     return false;
   }
 
   ogg_page page;
@@ -773,21 +749,19 @@ bool OggReader::ReadOggChain()
   codecState = OggCodecState::Create(&page);
   if (!codecState) {
     return false;
   }
 
   if (mVorbisState && (codecState->GetType() == OggCodecState::TYPE_VORBIS)) {
     newVorbisState = static_cast<VorbisState*>(codecState.get());
   }
-#ifdef MOZ_OPUS
   else if (mOpusState && (codecState->GetType() == OggCodecState::TYPE_OPUS)) {
     newOpusState = static_cast<OpusState*>(codecState.get());
   }
-#endif
   else {
     return false;
   }
   OggCodecState* state;
 
   mCodecStore.Add(serial, codecState.forget());
   state = mCodecStore.Get(serial);
 
@@ -814,33 +788,31 @@ bool OggReader::ReadOggChain()
     }
     mInfo.mAudio.mRate = newVorbisState->mInfo.rate;
     mInfo.mAudio.mChannels = newVorbisState->mInfo.channels;
 
     chained = true;
     tags = newVorbisState->GetTags();
   }
 
-#ifdef MOZ_OPUS
   if ((newOpusState && ReadHeaders(newOpusState)) &&
       (mOpusState->mRate == newOpusState->mRate) &&
       (mOpusState->mChannels == newOpusState->mChannels)) {
 
     SetupTargetOpus(newOpusState);
 
     if (msgInfo) {
       InitTrack(msgInfo, &mInfo.mAudio.mTrackInfo, true);
     }
     mInfo.mAudio.mRate = newOpusState->mRate;
     mInfo.mAudio.mChannels = newOpusState->mChannels;
 
     chained = true;
     tags = newOpusState->GetTags();
   }
-#endif
 
   if (chained) {
     SetChained(true);
     {
       mInfo.mAudio.mHasAudio = HasAudio();
       mInfo.mVideo.mHasVideo = HasVideo();
       nsAutoPtr<MediaInfo> info(new MediaInfo());
       *info = mInfo;
@@ -1436,22 +1408,20 @@ nsresult OggReader::SeekInUnbuffered(int
   // don't do this offsetting when seeking in a buffered range,
   // as the extra decoding causes a noticeable speed hit when all the data
   // is buffered (compared to just doing a bisection to exactly find the
   // keyframe).
   int64_t keyframeOffsetMs = 0;
   if (HasVideo() && mTheoraState) {
     keyframeOffsetMs = mTheoraState->MaxKeyframeOffset();
   }
-#ifdef MOZ_OPUS
   // Add in the Opus pre-roll if necessary, as well.
   if (HasAudio() && mOpusState) {
     keyframeOffsetMs = std::max(keyframeOffsetMs, SEEK_OPUS_PREROLL);
   }
-#endif /* MOZ_OPUS */
   int64_t seekTarget = std::max(aStartTime, aTarget - keyframeOffsetMs);
   // Minimize the bisection search space using the known timestamps from the
   // buffered ranges.
   SeekRange k = SelectSeekRange(aRanges, seekTarget, aStartTime, aEndTime, false);
   return SeekBisection(seekTarget, k, SEEK_FUZZ_USECS);
 }
 
 void OggReader::Seek(int64_t aTarget,
@@ -1471,21 +1441,19 @@ nsresult OggReader::SeekInternal(int64_t
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
   if (mIsChained)
     return NS_ERROR_FAILURE;
   LOG(PR_LOG_DEBUG, ("%p About to seek to %lld", mDecoder, aTarget));
   nsresult res;
   MediaResource* resource = mDecoder->GetResource();
   NS_ENSURE_TRUE(resource != nullptr, NS_ERROR_FAILURE);
   int64_t adjustedTarget = aTarget;
-#ifdef MOZ_OPUS
   if (HasAudio() && mOpusState){
     adjustedTarget = std::max(aStartTime, aTarget - SEEK_OPUS_PREROLL);
   }
-#endif /* MOZ_OPUS */
 
   if (adjustedTarget == aStartTime) {
     // We've seeked to the media start. Just seek to the offset of the first
     // content page.
     res = resource->Seek(nsISeekableStream::NS_SEEK_SET, 0);
     NS_ENSURE_SUCCESS(res,res);
 
     res = ResetDecode(true);
@@ -1783,20 +1751,18 @@ nsresult OggReader::SeekBisection(int64_
           NS_ENSURE_TRUE(ret == 0, NS_ERROR_FAILURE);
         }
 
         ogg_int64_t granulepos = ogg_page_granulepos(&page);
 
         if (HasAudio() && granulepos > 0 && audioTime == -1) {
           if (mVorbisState && serial == mVorbisState->mSerial) {
             audioTime = mVorbisState->Time(granulepos);
-#ifdef MOZ_OPUS
           } else if (mOpusState && serial == mOpusState->mSerial) {
             audioTime = mOpusState->Time(granulepos);
-#endif
           }
         }
 
         if (HasVideo() &&
             granulepos > 0 &&
             serial == mTheoraState->mSerial &&
             videoTime == -1) {
           videoTime = mTheoraState->Time(granulepos);
@@ -1961,22 +1927,20 @@ nsresult OggReader::GetBuffered(dom::Tim
         continue;
       }
 
       uint32_t serial = ogg_page_serialno(&page);
       if (mVorbisState && serial == mVorbisSerial) {
         startTime = VorbisState::Time(&mVorbisInfo, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       }
-#ifdef MOZ_OPUS
       else if (mOpusState && serial == mOpusSerial) {
         startTime = OpusState::Time(mOpusPreSkip, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       }
-#endif /* MOZ_OPUS */
       else if (mTheoraState && serial == mTheoraSerial) {
         startTime = TheoraState::Time(&mTheoraInfo, granulepos);
         NS_ASSERTION(startTime > 0, "Must have positive start time");
       }
       else if (mCodecStore.Contains(serial)) {
         // Stream is not the theora or vorbis stream we're playing,
         // but is one that we have header data for.
         startOffset += page.header_len + page.body_len;
--- a/dom/media/ogg/OggReader.h
+++ b/dom/media/ogg/OggReader.h
@@ -60,21 +60,18 @@ public:
 
   // If the Theora granulepos has not been captured, it may read several packets
   // until one with a granulepos has been captured, to ensure that all packets
   // read have valid time info.
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                   int64_t aTimeThreshold);
 
   virtual bool HasAudio() {
-    return (mVorbisState != 0 && mVorbisState->mActive)
-#ifdef MOZ_OPUS
-      || (mOpusState != 0 && mOpusState->mActive)
-#endif /* MOZ_OPUS */
-      ;
+    return (mVorbisState != 0 && mVorbisState->mActive) ||
+           (mOpusState != 0 && mOpusState->mActive);
   }
 
   virtual bool HasVideo() {
     return mTheoraState != 0 && mTheoraState->mActive;
   }
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags);
@@ -280,25 +277,23 @@ private:
   OggCodecStore mCodecStore;
 
   // Decode state of the Theora bitstream we're decoding, if we have video.
   TheoraState* mTheoraState;
 
   // Decode state of the Vorbis bitstream we're decoding, if we have audio.
   VorbisState* mVorbisState;
 
-#ifdef MOZ_OPUS
   // Decode state of the Opus bitstream we're decoding, if we have one.
   OpusState *mOpusState;
 
   // Represents the user pref media.opus.enabled at the time our
   // contructor was called. We can't check it dynamically because
   // we're not on the main thread;
   bool mOpusEnabled;
-#endif /* MOZ_OPUS */
 
   // Decode state of the Skeleton bitstream.
   SkeletonState* mSkeletonState;
 
   // Ogg decoding state.
   ogg_sync_state mOggState;
 
   // Vorbis/Opus/Theora data used to compute timestamps. This is written on the
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -164,34 +164,30 @@ ogg_packet InitOggPacket(const unsigned 
 #if defined(MOZ_PDM_VPX)
 static bool sIsIntelDecoderEnabled = false;
 #endif
 
 WebMReader::WebMReader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
   , mContext(nullptr)
   , mPacketCount(0)
-#ifdef MOZ_OPUS
   , mOpusDecoder(nullptr)
   , mSkip(0)
   , mSeekPreroll(0)
-#endif
   , mVideoTrack(0)
   , mAudioTrack(0)
   , mAudioStartUsec(-1)
   , mAudioFrames(0)
   , mLastVideoFrameTime(0)
   , mAudioCodec(-1)
   , mVideoCodec(-1)
   , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
   , mHasVideo(false)
   , mHasAudio(false)
-#ifdef MOZ_OPUS
   , mPaddingDiscarded(false)
-#endif
 {
   MOZ_COUNT_CTOR(WebMReader);
 #ifdef PR_LOGGING
   if (!gNesteggLog) {
     gNesteggLog = PR_NewLogModule("Nestegg");
   }
 #endif
   // Zero these member vars to avoid crashes in VP8 destroy and Vorbis clear
@@ -299,25 +295,23 @@ nsresult WebMReader::ResetDecode()
     res = NS_ERROR_FAILURE;
   }
 
   if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
     // Ignore failed results from vorbis_synthesis_restart. They
     // aren't fatal and it fails when ResetDecode is called at a
     // time when no vorbis data has been read.
     vorbis_synthesis_restart(&mVorbisDsp);
-#ifdef MOZ_OPUS
   } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
     if (mOpusDecoder) {
       // Reset the decoder.
       opus_multistream_decoder_ctl(mOpusDecoder, OPUS_RESET_STATE);
       mSkip = mOpusParser->mPreSkip;
       mPaddingDiscarded = false;
     }
-#endif
   }
 
   mVideoPackets.Reset();
   mAudioPackets.Reset();
 
   return res;
 }
 
@@ -509,17 +503,16 @@ nsresult WebMReader::ReadMetadata(MediaI
         r = vorbis_block_init(&mVorbisDsp, &mVorbisBlock);
         if (r != 0) {
           Cleanup();
           return NS_ERROR_FAILURE;
         }
 
         mInfo.mAudio.mRate = mVorbisDsp.vi->rate;
         mInfo.mAudio.mChannels = mVorbisDsp.vi->channels;
-#ifdef MOZ_OPUS
       } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
         unsigned char* data = 0;
         size_t length = 0;
         r = nestegg_track_codec_data(mContext, track, 0, &data, &length);
         if (r == -1) {
           Cleanup();
           return NS_ERROR_FAILURE;
         }
@@ -542,17 +535,16 @@ nsresult WebMReader::ReadMetadata(MediaI
           Cleanup();
           return NS_ERROR_FAILURE;
         }
 
         mInfo.mAudio.mRate = mOpusParser->mRate;
 
         mInfo.mAudio.mChannels = mOpusParser->mChannels;
         mSeekPreroll = params.seek_preroll;
-#endif
       } else {
         Cleanup();
         return NS_ERROR_FAILURE;
       }
     }
   }
 
   *aInfo = mInfo;
@@ -563,17 +555,16 @@ nsresult WebMReader::ReadMetadata(MediaI
 }
 
 bool
 WebMReader::IsMediaSeekable()
 {
   return mContext && nestegg_has_cues(mContext);
 }
 
-#ifdef MOZ_OPUS
 bool WebMReader::InitOpusDecoder()
 {
   int r;
 
   NS_ASSERTION(mOpusDecoder == nullptr, "leaking OpusDecoder");
 
   mOpusDecoder = opus_multistream_decoder_create(mOpusParser->mRate,
                                                  mOpusParser->mChannels,
@@ -581,17 +572,16 @@ bool WebMReader::InitOpusDecoder()
                                                  mOpusParser->mCoupledStreams,
                                                  mOpusParser->mMappingTable,
                                                  &r);
   mSkip = mOpusParser->mPreSkip;
   mPaddingDiscarded = false;
 
   return r == OPUS_OK;
 }
-#endif
 
 bool WebMReader::DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset)
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   int r = 0;
   unsigned int count = 0;
   r = nestegg_packet_count(aPacket, &count);
@@ -648,21 +638,19 @@ bool WebMReader::DecodeAudioPacket(neste
     if (r == -1) {
       return false;
     }
     if (mAudioCodec == NESTEGG_CODEC_VORBIS) {
       if (!DecodeVorbis(data, length, aOffset, tstamp_usecs, &total_frames)) {
         return false;
       }
     } else if (mAudioCodec == NESTEGG_CODEC_OPUS) {
-#ifdef MOZ_OPUS
       if (!DecodeOpus(data, length, aOffset, tstamp_usecs, aPacket)) {
         return false;
       }
-#endif
     }
   }
 
   return true;
 }
 
 bool WebMReader::DecodeVorbis(const unsigned char* aData, size_t aLength,
                               int64_t aOffset, uint64_t aTstampUsecs,
@@ -734,17 +722,16 @@ bool WebMReader::DecodeVorbis(const unsi
     }
 
     frames = vorbis_synthesis_pcmout(&mVorbisDsp, &pcm);
   }
 
   return true;
 }
 
-#ifdef MOZ_OPUS
 bool WebMReader::DecodeOpus(const unsigned char* aData, size_t aLength,
                             int64_t aOffset, uint64_t aTstampUsecs,
                             nestegg_packet* aPacket)
 {
   uint32_t channels = mOpusParser->mChannels;
   // No channel mapping for more than 8 channels.
   if (channels > 8) {
     return false;
@@ -871,17 +858,16 @@ bool WebMReader::DecodeOpus(const unsign
                                   buffer.forget(),
                                   mInfo.mAudio.mChannels,
                                   mInfo.mAudio.mRate));
 
   mAudioFrames += frames;
 
   return true;
 }
-#endif /* MOZ_OPUS */
 
 nsReturnRef<NesteggPacketHolder> WebMReader::NextPacket(TrackType aTrackType)
 {
   // The packet queue that packets will be pushed on if they
   // are not the type we are interested in.
   WebMPacketQueue& otherPackets =
     aTrackType == VIDEO ? mAudioPackets : mVideoPackets;
 
--- a/dom/media/webm/WebMReader.h
+++ b/dom/media/webm/WebMReader.h
@@ -19,19 +19,17 @@
 #include "mozilla/layers/LayersTypes.h"
 
 #ifdef MOZ_TREMOR
 #include "tremor/ivorbiscodec.h"
 #else
 #include "vorbis/codec.h"
 #endif
 
-#ifdef MOZ_OPUS
 #include "OpusParser.h"
-#endif
 
 // Holds a nestegg_packet, and its file offset. This is needed so we
 // know the offset in the file we've played up to, in order to calculate
 // whether it's likely we can play through to the end without needing
 // to stop to buffer, given the current download rate.
 class NesteggPacketHolder {
 public:
   NesteggPacketHolder(nestegg_packet* aPacket, int64_t aOffset)
@@ -185,36 +183,32 @@ public:
   nsIntRect GetPicture();
   nsIntSize GetInitialFrame();
   uint64_t GetLastVideoFrameTime();
   void SetLastVideoFrameTime(uint64_t aFrameTime);
   layers::LayersBackend GetLayersBackendType() { return mLayersBackendType; }
   MediaTaskQueue* GetTaskQueue() { return mTaskQueue; }
 
 protected:
-#ifdef MOZ_OPUS
   // Setup opus decoder
   bool InitOpusDecoder();
-#endif
 
   // Decode a nestegg packet of audio data. Push the audio data on the
   // audio queue. Returns true when there's more audio to decode,
   // false if the audio is finished, end of file has been reached,
   // or an un-recoverable read error has occured. The reader's monitor
   // must be held during this call. The caller is responsible for freeing
   // aPacket.
   bool DecodeAudioPacket(nestegg_packet* aPacket, int64_t aOffset);
   bool DecodeVorbis(const unsigned char* aData, size_t aLength,
                     int64_t aOffset, uint64_t aTstampUsecs,
                     int32_t* aTotalFrames);
-#ifdef MOZ_OPUS
   bool DecodeOpus(const unsigned char* aData, size_t aLength,
                   int64_t aOffset, uint64_t aTstampUsecs,
                   nestegg_packet* aPacket);
-#endif
 
   // Release context and set to null. Called when an error occurs during
   // reading metadata or destruction of the reader itself.
   void Cleanup();
 
   virtual nsresult SeekInternal(int64_t aTime, int64_t aStartTime);
 
   // Initializes mLayersBackendType if possible.
@@ -230,23 +224,21 @@ private:
 
   // Vorbis decoder state
   vorbis_info mVorbisInfo;
   vorbis_comment mVorbisComment;
   vorbis_dsp_state mVorbisDsp;
   vorbis_block mVorbisBlock;
   int64_t mPacketCount;
 
-#ifdef MOZ_OPUS
   // Opus decoder state
   nsAutoPtr<OpusParser> mOpusParser;
   OpusMSDecoder *mOpusDecoder;
   uint16_t mSkip;        // Samples left to trim before playback.
   uint64_t mSeekPreroll; // Nanoseconds to discard after seeking.
-#endif
 
   // Queue of video and audio packets that have been read but not decoded. These
   // must only be accessed from the state machine thread.
   WebMPacketQueue mVideoPackets;
   WebMPacketQueue mAudioPackets;
 
   // Index of video and audio track to play
   uint32_t mVideoTrack;
@@ -283,19 +275,17 @@ private:
 
   layers::LayersBackend mLayersBackendType;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
 
   // Booleans to indicate if we have audio and/or video data
   bool mHasVideo;
   bool mHasAudio;
 
-#ifdef MOZ_OPUS
   // Opus padding should only be discarded on the final packet.  Once this
   // is set to true, if the reader attempts to decode any further packets it
   // will raise an error so we can indicate that the file is invalid.
   bool mPaddingDiscarded;
-#endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -148,17 +148,16 @@ th_decode_headerin
 th_decode_packetin
 th_decode_ycbcr_out
 th_granule_frame
 th_info_clear
 th_info_init
 th_packet_isheader
 th_packet_iskeyframe
 th_setup_free
-#ifdef MOZ_OPUS
 opus_decoder_create
 opus_decoder_destroy
 opus_decoder_ctl
 opus_decoder_get_nb_samples
 opus_decode
 opus_decode_float
 opus_multistream_decoder_create
 opus_multistream_decoder_ctl
@@ -167,17 +166,16 @@ opus_multistream_decode_float
 opus_multistream_decode
 opus_packet_get_nb_frames
 opus_packet_get_samples_per_frame
 opus_encoder_create
 opus_encoder_destroy
 opus_encoder_ctl
 opus_encode
 opus_encode_float
-#endif
 #ifndef MOZ_NATIVE_PNG
 MOZ_APNG_get_first_frame_is_hidden
 MOZ_APNG_get_next_frame_blend_op
 MOZ_APNG_get_next_frame_delay_den
 MOZ_APNG_get_next_frame_delay_num
 MOZ_APNG_get_next_frame_dispose_op
 MOZ_APNG_set_prog_frame_fn
 MOZ_APNG_get_next_frame_height
--- a/layout/xul/tree/nsTreeBodyFrame.cpp
+++ b/layout/xul/tree/nsTreeBodyFrame.cpp
@@ -3215,39 +3215,39 @@ nsTreeBodyFrame::PaintCell(int32_t      
       if (srcX <= cellRect.x + cellRect.width) {
         nscoord destX = currX + twistyRect.width;
         if (destX > cellRect.x + cellRect.width)
           destX = cellRect.x + cellRect.width;
         if (isRTL) {
           srcX = currX + remainingWidth - (srcX - cellRect.x);
           destX = currX + remainingWidth - (destX - cellRect.x);
         }
-        Point p1(pc->CSSPixelsToDevPixels(srcX),
-                 pc->CSSPixelsToDevPixels(lineY + mRowHeight / 2));
-        Point p2(pc->CSSPixelsToDevPixels(destX),
-                 pc->CSSPixelsToDevPixels(lineY + mRowHeight / 2));
+        Point p1(pc->AppUnitsToGfxUnits(srcX),
+                 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
+        Point p2(pc->AppUnitsToGfxUnits(destX),
+                 pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2));
         SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget);
         drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
       }
 
       int32_t currentParent = aRowIndex;
       for (int32_t i = level; i > 0; i--) {
         if (srcX <= cellRect.x + cellRect.width) {
           // Paint full vertical line only if we have next sibling.
           bool hasNextSibling;
           mView->HasNextSibling(currentParent, aRowIndex, &hasNextSibling);
-          Point p1(pc->CSSPixelsToDevPixels(srcX),
-                   pc->CSSPixelsToDevPixels(lineY));
+          Point p1(pc->AppUnitsToGfxUnits(srcX),
+                   pc->AppUnitsToGfxUnits(lineY));
           Point p2;
-          p2.x = pc->CSSPixelsToDevPixels(srcX);
+          p2.x = pc->AppUnitsToGfxUnits(srcX);
 
           if (hasNextSibling)
-            p2.y = pc->CSSPixelsToDevPixels(lineY + mRowHeight);
+            p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight);
           else if (i == level)
-            p2.y = pc->CSSPixelsToDevPixels(lineY + mRowHeight / 2);
+            p2.y = pc->AppUnitsToGfxUnits(lineY + mRowHeight / 2);
 
           SnapLineToDevicePixelsForStroking(p1, p2, *drawTarget);
           drawTarget->StrokeLine(p1, p2, colorPatt, strokeOptions);
         }
 
         int32_t parent;
         if (NS_FAILED(mView->GetParentIndex(currentParent, &parent)) || parent < 0)
           break;
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -271,19 +271,17 @@ pref("media.fragmented-mp4.exposed", fal
 // just outputs blank frames/audio instead of actually decoding. The blank
 // decoder works on all platforms.
 pref("media.fragmented-mp4.use-blank-decoder", false);
 #endif
 #ifdef MOZ_RAW
 pref("media.raw.enabled", true);
 #endif
 pref("media.ogg.enabled", true);
-#ifdef MOZ_OPUS
 pref("media.opus.enabled", true);
-#endif
 #ifdef MOZ_WAVE
 pref("media.wave.enabled", true);
 #endif
 #ifdef MOZ_WEBM
 pref("media.webm.enabled", true);
 #if defined(MOZ_FMP4) && defined(MOZ_WMF)
 pref("media.webm.intel_decoder.enabled", false);
 #endif
--- a/security/manager/boot/src/RootCertificateTelemetryUtils.cpp
+++ b/security/manager/boot/src/RootCertificateTelemetryUtils.cpp
@@ -4,16 +4,17 @@
  * 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 "RootCertificateTelemetryUtils.h"
 
 #include "prlog.h"
 #include "RootHashes.inc" // Note: Generated by genRootCAHashes.js
 #include "ScopedNSSTypes.h"
+#include "mozilla/ArrayUtils.h"
 
 // Note: New CAs will show up as UNKNOWN_ROOT until
 // RootHashes.inc is updated to include them. 0 is reserved by
 // genRootCAHashes.js for the unknowns.
 #define UNKNOWN_ROOT  0
 #define HASH_FAILURE -1
 
 namespace mozilla { namespace psm { 
--- a/services/sync/modules/engines/tabs.js
+++ b/services/sync/modules/engines/tabs.js
@@ -1,19 +1,18 @@
 /* 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/. */
 
-this.EXPORTED_SYMBOLS = ['TabEngine', 'TabSetRecord'];
+this.EXPORTED_SYMBOLS = ["TabEngine", "TabSetRecord"];
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-const TABS_TTL = 604800; // 7 days
+const TABS_TTL = 604800;           // 7 days.
+const TAB_ENTRIES_LIMIT = 25;      // How many URLs to include in tab history.
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/engines/clients.js");
 Cu.import("resource://services-sync/record.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://services-sync/constants.js");
@@ -22,60 +21,60 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 this.TabSetRecord = function TabSetRecord(collection, id) {
   CryptoWrapper.call(this, collection, id);
 }
 TabSetRecord.prototype = {
   __proto__: CryptoWrapper.prototype,
   _logName: "Sync.Record.Tabs",
-  ttl: TABS_TTL
+  ttl: TABS_TTL,
 };
 
 Utils.deferGetSet(TabSetRecord, "cleartext", ["clientName", "tabs"]);
 
 
 this.TabEngine = function TabEngine(service) {
   SyncEngine.call(this, "Tabs", service);
 
-  // Reset the client on every startup so that we fetch recent tabs
+  // Reset the client on every startup so that we fetch recent tabs.
   this._resetClient();
 }
 TabEngine.prototype = {
   __proto__: SyncEngine.prototype,
   _storeObj: TabStore,
   _trackerObj: TabTracker,
   _recordObj: TabSetRecord,
 
   syncPriority: 3,
 
-  getChangedIDs: function getChangedIDs() {
+  getChangedIDs: function () {
     // No need for a proper timestamp (no conflict resolution needed).
     let changedIDs = {};
     if (this._tracker.modified)
       changedIDs[this.service.clientsEngine.localID] = 0;
     return changedIDs;
   },
 
-  // API for use by Weave UI code to give user choices of tabs to open:
-  getAllClients: function TabEngine_getAllClients() {
+  // API for use by Sync UI code to give user choices of tabs to open.
+  getAllClients: function () {
     return this._store._remoteClients;
   },
 
-  getClientById: function TabEngine_getClientById(id) {
+  getClientById: function (id) {
     return this._store._remoteClients[id];
   },
 
-  _resetClient: function TabEngine__resetClient() {
+  _resetClient: function () {
     SyncEngine.prototype._resetClient.call(this);
     this._store.wipe();
     this._tracker.modified = true;
   },
 
-  removeClientData: function removeClientData() {
+  removeClientData: function () {
     let url = this.engineURL + "/" + this.service.clientsEngine.localID;
     this.service.resource(url).delete();
   },
 
   /**
    * Return a Set of open URLs.
    */
   getOpenURLs: function () {
@@ -89,17 +88,17 @@ TabEngine.prototype = {
 
 
 function TabStore(name, engine) {
   Store.call(this, name, engine);
 }
 TabStore.prototype = {
   __proto__: Store.prototype,
 
-  itemExists: function TabStore_itemExists(id) {
+  itemExists: function (id) {
     return id == this.engine.service.clientsEngine.localID;
   },
 
   getWindowEnumerator: function () {
     return Services.wm.getEnumerator("navigator:browser");
   },
 
   shouldSkipWindow: function (win) {
@@ -126,41 +125,58 @@ TabStore.prototype = {
       for (let tab of win.gBrowser.tabs) {
         tabState = this.getTabState(tab);
 
         // Make sure there are history entries to look at.
         if (!tabState || !tabState.entries.length) {
           continue;
         }
 
-        // Until we store full or partial history, just grab the current entry.
-        // index is 1 based, so make sure we adjust.
-        let entry = tabState.entries[tabState.index - 1];
+        let acceptable = !filter ? (url) => url :
+                                   (url) => url && !filteredUrls.test(url);
 
-        // Filter out some urls if necessary. SessionStore can return empty
-        // tabs in some cases - easiest thing is to just ignore them for now.
-        if (!entry.url || filter && filteredUrls.test(entry.url)) {
+        let entries = tabState.entries;
+        let index = tabState.index;
+        let current = entries[index - 1];
+
+        // We ignore the tab completely if the current entry url is
+        // not acceptable (we need something accurate to open).
+        if (!acceptable(current.url)) {
           continue;
         }
 
-        // I think it's also possible that attributes[.image] might not be set
-        // so handle that as well.
+        // The element at `index` is the current page. Previous URLs were
+        // previously visited URLs; subsequent URLs are in the 'forward' stack,
+        // which we can't represent in Sync, so we truncate here.
+        let candidates = (entries.length == index) ?
+                         entries :
+                         entries.slice(0, index);
+
+        let urls = candidates.map((entry) => entry.url)
+                             .filter(acceptable)
+                             .reverse();                       // Because Sync puts current at index 0, and history after.
+
+        // Truncate if necessary.
+        if (urls.length > TAB_ENTRIES_LIMIT) {
+          urls.length = TAB_ENTRIES_LIMIT;
+        }
+
         allTabs.push({
-          title: entry.title || "",
-          urlHistory: [entry.url],
+          title: current.title || "",
+          urlHistory: urls,
           icon: tabState.attributes && tabState.attributes.image || "",
-          lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000)
+          lastUsed: Math.floor((tabState.lastAccessed || 0) / 1000),
         });
       }
     }
 
     return allTabs;
   },
 
-  createRecord: function createRecord(id, collection) {
+  createRecord: function (id, collection) {
     let record = new TabSetRecord(collection, id);
     record.clientName = this.engine.service.clientsEngine.localName;
 
     // Sort tabs in descending-used order to grab the most recently used
     let tabs = this.getAllTabs(true).sort(function (a, b) {
       return b.lastUsed - a.lastUsed;
     });
 
@@ -183,17 +199,17 @@ TabStore.prototype = {
     tabs.forEach(function (tab) {
       this._log.trace("Wrapping tab: " + JSON.stringify(tab));
     }, this);
 
     record.tabs = tabs;
     return record;
   },
 
-  getAllIDs: function TabStore_getAllIds() {
+  getAllIDs: function () {
     // Don't report any tabs if all windows are in private browsing for
     // first syncs.
     let ids = {};
     let allWindowsArePrivate = false;
     let wins = Services.wm.getEnumerator("navigator:browser");
     while (wins.hasMoreElements()) {
       if (PrivateBrowsingUtils.isWindowPrivate(wins.getNext())) {
         // Ensure that at least there is a private window.
@@ -209,80 +225,88 @@ TabStore.prototype = {
         !PrivateBrowsingUtils.permanentPrivateBrowsing) {
       return ids;
     }
 
     ids[this.engine.service.clientsEngine.localID] = true;
     return ids;
   },
 
-  wipe: function TabStore_wipe() {
+  wipe: function () {
     this._remoteClients = {};
   },
 
-  create: function TabStore_create(record) {
+  create: function (record) {
     this._log.debug("Adding remote tabs from " + record.clientName);
     this._remoteClients[record.id] = record.cleartext;
 
-    // Lose some precision, but that's good enough (seconds)
+    // Lose some precision, but that's good enough (seconds).
     let roundModify = Math.floor(record.modified / 1000);
     let notifyState = Svc.Prefs.get("notifyTabState");
-    // If there's no existing pref, save this first modified time
-    if (notifyState == null)
+
+    // If there's no existing pref, save this first modified time.
+    if (notifyState == null) {
       Svc.Prefs.set("notifyTabState", roundModify);
-    // Don't change notifyState if it's already 0 (don't notify)
-    else if (notifyState == 0)
+      return;
+    }
+
+    // Don't change notifyState if it's already 0 (don't notify).
+    if (notifyState == 0) {
       return;
-    // We must have gotten a new tab that isn't the same as last time
-    else if (notifyState != roundModify)
+    }
+
+    // We must have gotten a new tab that isn't the same as last time.
+    if (notifyState != roundModify) {
       Svc.Prefs.set("notifyTabState", 0);
+    }
   },
 
-  update: function update(record) {
+  update: function (record) {
     this._log.trace("Ignoring tab updates as local ones win");
-  }
+  },
 };
 
 
 function TabTracker(name, engine) {
   Tracker.call(this, name, engine);
   Svc.Obs.add("weave:engine:start-tracking", this);
   Svc.Obs.add("weave:engine:stop-tracking", this);
 
-  // Make sure "this" pointer is always set correctly for event listeners
+  // Make sure "this" pointer is always set correctly for event listeners.
   this.onTab = Utils.bind2(this, this.onTab);
   this._unregisterListeners = Utils.bind2(this, this._unregisterListeners);
 }
 TabTracker.prototype = {
   __proto__: Tracker.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
-  loadChangedIDs: function loadChangedIDs() {
+  loadChangedIDs: function () {
     // Don't read changed IDs from disk at start up.
   },
 
-  clearChangedIDs: function clearChangedIDs() {
+  clearChangedIDs: function () {
     this.modified = false;
   },
 
   _topics: ["pageshow", "TabOpen", "TabClose", "TabSelect"],
-  _registerListenersForWindow: function registerListenersFW(window) {
+
+  _registerListenersForWindow: function (window) {
     this._log.trace("Registering tab listeners in window");
     for each (let topic in this._topics) {
       window.addEventListener(topic, this.onTab, false);
     }
     window.addEventListener("unload", this._unregisterListeners, false);
   },
 
-  _unregisterListeners: function unregisterListeners(event) {
+  _unregisterListeners: function (event) {
     this._unregisterListenersForWindow(event.target);
   },
 
-  _unregisterListenersForWindow: function unregisterListenersFW(window) {
+  _unregisterListenersForWindow: function (window) {
     this._log.trace("Removing tab listeners in window");
     window.removeEventListener("unload", this._unregisterListeners, false);
     for each (let topic in this._topics) {
       window.removeEventListener(topic, this.onTab, false);
     }
   },
 
   startTracking: function () {
@@ -313,28 +337,29 @@ TabTracker.prototype = {
         };
 
         // Add tab listeners now that a window has opened.
         subject.addEventListener("load", onLoad, false);
         break;
     }
   },
 
-  onTab: function onTab(event) {
+  onTab: function (event) {
     if (event.originalTarget.linkedBrowser) {
       let browser = event.originalTarget.linkedBrowser;
       if (PrivateBrowsingUtils.isBrowserPrivate(browser) &&
           !PrivateBrowsingUtils.permanentPrivateBrowsing) {
         this._log.trace("Ignoring tab event from private browsing.");
         return;
       }
     }
 
     this._log.trace("onTab event: " + event.type);
     this.modified = true;
 
     // For page shows, bump the score 10% of the time, emulating a partial
     // score. We don't want to sync too frequently. For all other page
     // events, always bump the score.
-    if (event.type != "pageshow" || Math.random() < .1)
+    if (event.type != "pageshow" || Math.random() < .1) {
       this.score += SCORE_INCREMENT_SMALL;
+    }
   },
-}
+};
--- a/services/sync/tests/unit/head_helpers.js
+++ b/services/sync/tests/unit/head_helpers.js
@@ -133,36 +133,41 @@ function mockShouldSkipWindow (win) {
   return win.closed ||
          win.mockIsPrivate;
 }
 
 function mockGetTabState (tab) {
   return tab;
 }
 
-function mockGetWindowEnumerator(url, numWindows, numTabs) {
+function mockGetWindowEnumerator(url, numWindows, numTabs, indexes, moreURLs) {
   let elements = [];
+
+  function url2entry(url) {
+    return {
+      url: ((typeof url == "function") ? url() : url),
+      title: "title"
+    };
+  }
+
   for (let w = 0; w < numWindows; ++w) {
     let tabs = [];
     let win = {
       closed: false,
       mockIsPrivate: false,
       gBrowser: {
         tabs: tabs,
       },
     };
     elements.push(win);
 
     for (let t = 0; t < numTabs; ++t) {
       tabs.push(TestingUtils.deepCopy({
-        index: 1,
-        entries: [{
-          url: ((typeof url == "string") ? url : url()),
-          title: "title"
-        }],
+        index: indexes ? indexes() : 1,
+        entries: (moreURLs ? [url].concat(moreURLs()) : [url]).map(url2entry),
         attributes: {
           image: "image"
         },
         lastAccessed: 1499
       }));
     }
   }
 
--- a/services/sync/tests/unit/test_tab_store.js
+++ b/services/sync/tests/unit/test_tab_store.js
@@ -47,33 +47,53 @@ function test_create() {
   // reset the notifyTabState
   Svc.Prefs.reset("notifyTabState");
 }
 
 function test_getAllTabs() {
   let store = getMockStore();
   let tabs;
 
-  store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1);
+  let threeUrls = ["http://foo.com", "http://fuubar.com", "http://barbar.com"];
+
+  store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://bar.com", 1, 1, () => 2, () => threeUrls);
 
   _("Get all tabs.");
   tabs = store.getAllTabs();
   _("Tabs: " + JSON.stringify(tabs));
   do_check_eq(tabs.length, 1);
   do_check_eq(tabs[0].title, "title");
-  do_check_eq(tabs[0].urlHistory.length, 1);
-  do_check_eq(tabs[0].urlHistory[0], ["http://foo.com"]);
+  do_check_eq(tabs[0].urlHistory.length, 2);
+  do_check_eq(tabs[0].urlHistory[0], "http://foo.com");
+  do_check_eq(tabs[0].urlHistory[1], "http://bar.com");
   do_check_eq(tabs[0].icon, "image");
   do_check_eq(tabs[0].lastUsed, 1);
 
   _("Get all tabs, and check that filtering works.");
-  store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "about:foo", 1, 1);
+  let twoUrls = ["about:foo", "http://fuubar.com"];
+  store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://foo.com", 1, 1, () => 2, () => twoUrls);
   tabs = store.getAllTabs(true);
   _("Filtered: " + JSON.stringify(tabs));
   do_check_eq(tabs.length, 0);
+
+  _("Get all tabs, and check that the entries safety limit works.");
+  let allURLs = [];
+  for (let i = 0; i < 50; i++) {
+    allURLs.push("http://foo" + i + ".bar");
+  }
+  allURLs.splice(35, 0, "about:foo", "about:bar", "about:foobar");
+
+  store.getWindowEnumerator = mockGetWindowEnumerator.bind(this, "http://bar.com", 1, 1, () => 45, () => allURLs);
+  tabs = store.getAllTabs((url) => url.startsWith("about"));
+
+  _("Sliced: " + JSON.stringify(tabs));
+  do_check_eq(tabs.length, 1);
+  do_check_eq(tabs[0].urlHistory.length, 25);
+  do_check_eq(tabs[0].urlHistory[0], "http://foo40.bar");
+  do_check_eq(tabs[0].urlHistory[24], "http://foo16.bar");
 }
 
 function test_createRecord() {
   let store = getMockStore();
   let record;
 
   store.getTabState = mockGetTabState;
   store.shouldSkipWindow = mockShouldSkipWindow;
--- a/uriloader/exthandler/nsExternalHelperAppService.cpp
+++ b/uriloader/exthandler/nsExternalHelperAppService.cpp
@@ -453,19 +453,17 @@ static nsDefaultMimeTypeEntry defaultMim
   { APPLICATION_XPINSTALL, "xpi" },
   { "application/xhtml+xml", "xhtml" },
   { "application/xhtml+xml", "xht" },
   { TEXT_PLAIN, "txt" },
   { VIDEO_OGG, "ogv" },
   { VIDEO_OGG, "ogg" },
   { APPLICATION_OGG, "ogg" },
   { AUDIO_OGG, "oga" },
-#ifdef MOZ_OPUS
   { AUDIO_OGG, "opus" },
-#endif
 #ifdef MOZ_WEBM
   { VIDEO_WEBM, "webm" },
   { AUDIO_WEBM, "webm" },
 #endif
 #if defined(MOZ_GSTREAMER) || defined(MOZ_WMF)
   { VIDEO_MP4, "mp4" },
   { AUDIO_MP4, "m4a" },
   { AUDIO_MP3, "mp3" },