Merge inbound to m-c a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Wed, 22 Apr 2015 17:15:53 -0700
changeset 240608 a5af73b32ac80d81490a9007708333bab37b9cc7
parent 240379 a9311ec2dd398db8ef77d23ecfefd05f584c542f (current diff)
parent 240607 bf525e8a7de7763f08a49307628782a128fa2112 (diff)
child 240609 0b202671c9e24cecd28be9eabf81249064dfe458
child 240623 b26876eb22927c046a42038f920d90032ef25f27
child 240640 b86d1ff7338131c387db19ad1184a1c6a002b1a9
child 240646 bcf0d7ae9eedc3166420f5e36d9f7d3c8bcda6d1
push id28636
push userkwierso@gmail.com
push dateThu, 23 Apr 2015 00:16:12 +0000
treeherdermozilla-central@a5af73b32ac8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c a=merge CLOSED TREE
dom/push/PushServiceLauncher.js
dom/webidl/InstallEvent.webidl
js/src/jit-test/tests/debug/Memory-onGarbageCollection-01.js
js/src/jit-test/tests/debug/Memory-onGarbageCollection-02.js
js/src/jit-test/tests/debug/Memory-onGarbageCollection-03.js
js/src/jit-test/tests/debug/Memory-onGarbageCollection-04.js
js/src/shell/js-gdb.gdb
testing/web-platform/meta/websockets/interfaces/WebSocket/readyState/003.html.ini
testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.js.ini
testing/web-platform/meta/workers/interfaces/WorkerGlobalScope/onerror/exception-in-onerror.html.ini
testing/web-platform/meta/workers/interfaces/WorkerGlobalScope/onerror/not-handled.html.ini
testing/web-platform/meta/workers/interfaces/WorkerGlobalScope/onerror/propagate-to-window-onerror.html.ini
testing/web-platform/tests/tools/lint/lint.whitelist
toolkit/components/protobuf/google/protobuf/extension_set.cc
toolkit/components/protobuf/google/protobuf/extension_set.h
toolkit/components/protobuf/google/protobuf/generated_message_util.cc
toolkit/components/protobuf/google/protobuf/generated_message_util.h
toolkit/components/protobuf/google/protobuf/io/coded_stream.cc
toolkit/components/protobuf/google/protobuf/io/coded_stream.h
toolkit/components/protobuf/google/protobuf/io/coded_stream_inl.h
toolkit/components/protobuf/google/protobuf/io/package_info.h
toolkit/components/protobuf/google/protobuf/io/zero_copy_stream.cc
toolkit/components/protobuf/google/protobuf/io/zero_copy_stream.h
toolkit/components/protobuf/google/protobuf/io/zero_copy_stream_impl.h
toolkit/components/protobuf/google/protobuf/io/zero_copy_stream_impl_lite.cc
toolkit/components/protobuf/google/protobuf/io/zero_copy_stream_impl_lite.h
toolkit/components/protobuf/google/protobuf/message_lite.cc
toolkit/components/protobuf/google/protobuf/message_lite.h
toolkit/components/protobuf/google/protobuf/package_info.h
toolkit/components/protobuf/google/protobuf/repeated_field.cc
toolkit/components/protobuf/google/protobuf/repeated_field.h
toolkit/components/protobuf/google/protobuf/stubs/common.cc
toolkit/components/protobuf/google/protobuf/stubs/common.h
toolkit/components/protobuf/google/protobuf/stubs/hash.h
toolkit/components/protobuf/google/protobuf/stubs/map-util.h
toolkit/components/protobuf/google/protobuf/stubs/once.cc
toolkit/components/protobuf/google/protobuf/stubs/once.h
toolkit/components/protobuf/google/protobuf/stubs/stl_util-inl.h
toolkit/components/protobuf/google/protobuf/wire_format_lite.cc
toolkit/components/protobuf/google/protobuf/wire_format_lite.h
toolkit/components/protobuf/google/protobuf/wire_format_lite_inl.h
toolkit/components/protobuf/r512.patch
toolkit/components/protobuf/update.sh
toolkit/components/protobuf/vs2013.patch
widget/android/AndroidJavaWrappers.h
--- a/accessible/atk/InterfaceInitFuncs.h
+++ b/accessible/atk/InterfaceInitFuncs.h
@@ -12,18 +12,16 @@
 namespace mozilla {
 namespace a11y {
 
 class AccessibleWrap;
 
 } // namespace a11y
 } // namespace mozilla
 
-struct MaiUtilClass;
-
 extern "C" {
 void actionInterfaceInitCB(AtkActionIface* aIface);
 void componentInterfaceInitCB(AtkComponentIface* aIface);
 void documentInterfaceInitCB(AtkDocumentIface *aIface);
 void editableTextInterfaceInitCB(AtkEditableTextIface* aIface);
 void hyperlinkImplInterfaceInitCB(AtkHyperlinkImplIface *aIface);
 void hypertextInterfaceInitCB(AtkHypertextIface* aIface);
 void imageInterfaceInitCB(AtkImageIface* aIface);
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -18,33 +18,31 @@
 #include "nsRect.h"
 
 struct nsRoleMapEntry;
 
 struct nsRect;
 class nsIFrame;
 class nsIAtom;
 class nsIPersistentProperties;
-class nsView;
 
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class AccEvent;
 class AccGroupInfo;
 class ApplicationAccessible;
 class DocAccessible;
 class EmbeddedObjCollector;
 class HTMLImageMapAccessible;
 class HTMLLIAccessible;
 class HyperTextAccessible;
 class ImageAccessible;
 class KeyBinding;
-class MathMLAccessible;
 class ProxyAccessible;
 class Relation;
 class RootAccessible;
 class TableAccessible;
 class TableCellAccessible;
 class TextLeafAccessible;
 class XULLabelAccessible;
 class XULTreeAccessible;
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -18,18 +18,16 @@
 #include "nsIEditor.h"
 #include "nsIObserver.h"
 #include "nsIScrollPositionListener.h"
 #include "nsITimer.h"
 #include "nsIWeakReference.h"
 
 class nsAccessiblePivot;
 
-class nsIScrollableView;
-
 const uint32_t kDefaultCacheLength = 128;
 
 namespace mozilla {
 namespace a11y {
 
 class DocManager;
 class NotificationController;
 class DocAccessibleChild;
--- a/accessible/generic/ImageAccessible.h
+++ b/accessible/generic/ImageAccessible.h
@@ -3,18 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_ImageAccessible_h__
 #define mozilla_a11y_ImageAccessible_h__
 
 #include "BaseAccessibles.h"
 
-class nsGenericHTMLElement;
-
 namespace mozilla {
 namespace a11y {
 
 /* Accessible for supporting images
  * supports:
  * - gets name, role
  * - support basic state
  */
--- a/accessible/html/HTMLSelectAccessible.h
+++ b/accessible/html/HTMLSelectAccessible.h
@@ -3,18 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_HTMLSelectAccessible_h__
 #define mozilla_a11y_HTMLSelectAccessible_h__
 
 #include "HTMLFormControlAccessible.h"
 
-class nsIMutableArray;
-
 namespace mozilla {
 namespace a11y {
 
 /**
   *  Selects, Listboxes and Comboboxes, are made up of a number of different
   *  widgets, some of which are shared between the two. This file contains
   *  all of the widgets for both of the Selects, for HTML only.
   *
--- a/accessible/html/HTMLTableAccessible.h
+++ b/accessible/html/HTMLTableAccessible.h
@@ -5,17 +5,16 @@
 
 #ifndef mozilla_a11y_HTMLTableAccessible_h__
 #define mozilla_a11y_HTMLTableAccessible_h__
 
 #include "HyperTextAccessibleWrap.h"
 #include "TableAccessible.h"
 #include "TableCellAccessible.h"
 
-class nsITableLayout;
 class nsITableCellLayout;
 
 namespace mozilla {
 namespace a11y {
 
 /**
  * HTML table cell accessible (html:td).
  */
--- a/accessible/interfaces/nsIAccessibilityService.h
+++ b/accessible/interfaces/nsIAccessibilityService.h
@@ -15,21 +15,17 @@
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 
 } // namespace a11y
 } // namespace mozilla
 
-class nsINode;
-class nsIContent;
-class nsIFrame;
 class nsIPresShell;
-class nsPluginFrame;
 
 // 0e7e6879-854b-4260-bc6e-525b5fb5cf34
 #define NS_IACCESSIBILITYSERVICE_IID \
 { 0x0e7e6879, 0x854b, 0x4260, \
  { 0xbc, 0x6e, 0x52, 0x5b, 0x5f, 0xb5, 0xcf, 0x34 } }
 
 class nsIAccessibilityService : public nsIAccessibleRetrieval
 {
--- a/accessible/jsat/EventManager.jsm
+++ b/accessible/jsat/EventManager.jsm
@@ -186,16 +186,22 @@ this.EventManager.prototype = {
         }
         break;
       }
       case Events.NAME_CHANGE:
       {
         let acc = aEvent.accessible;
         if (acc === this.contentControl.vc.position) {
           this.present(Presentation.nameChanged(acc));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ['text', 'all']);
+          if (liveRegion) {
+            this.present(Presentation.nameChanged(acc, isPolite));
+          }
         }
         break;
       }
       case Events.SCROLLING_START:
       {
         this.contentControl.autoMove(aEvent.accessible);
         break;
       }
@@ -288,16 +294,22 @@ this.EventManager.prototype = {
       }
       case Events.VALUE_CHANGE:
       {
         let position = this.contentControl.vc.position;
         let target = aEvent.accessible;
         if (position === target ||
             Utils.getEmbeddedControl(position) === target) {
           this.present(Presentation.valueChanged(target));
+        } else {
+          let {liveRegion, isPolite} = this._handleLiveRegion(aEvent,
+            ['text', 'all']);
+          if (liveRegion) {
+            this.present(Presentation.valueChanged(target, isPolite));
+          }
         }
       }
     }
   },
 
   _setEditingMode: function _setEditingMode(aEvent, aCaretOffset) {
     let acc = aEvent.accessible;
     let accText, characterCount;
--- a/accessible/jsat/Presentation.jsm
+++ b/accessible/jsat/Presentation.jsm
@@ -518,39 +518,41 @@ B2GPresenter.prototype.pivotChanged =
           isUserInput: aIsUserInput,
           hints: aContext.interactionHints
         }
       }
     };
   };
 
 B2GPresenter.prototype.nameChanged =
-  function B2GPresenter_nameChanged(aAccessible) {
+  function B2GPresenter_nameChanged(aAccessible, aIsPolite = true) {
     return {
       type: this.type,
       details: {
         eventType: 'name-change',
-        data: aAccessible.name
+        data: aAccessible.name,
+        options: {enqueue: aIsPolite}
       }
     };
   };
 
 B2GPresenter.prototype.valueChanged =
-  function B2GPresenter_valueChanged(aAccessible) {
+  function B2GPresenter_valueChanged(aAccessible, aIsPolite = true) {
 
     // the editable value changes are handled in the text changed presenter
     if (Utils.getState(aAccessible).contains(States.EDITABLE)) {
       return null;
     }
 
     return {
       type: this.type,
       details: {
         eventType: 'value-change',
-        data: aAccessible.value
+        data: aAccessible.value,
+        options: {enqueue: aIsPolite}
       }
     };
   };
 
 B2GPresenter.prototype.textChanged = function B2GPresenter_textChanged(
   aAccessible, aIsInserted, aStart, aLength, aText, aModifiedText) {
     let echoSetting = this.keyboardEchoSetting.value;
     let text = '';
--- a/accessible/mac/RootAccessibleWrap.h
+++ b/accessible/mac/RootAccessibleWrap.h
@@ -10,18 +10,16 @@
 #ifndef mozilla_a11y_RootAccessibleWrap_h__
 #define mozilla_a11y_RootAccessibleWrap_h__
 
 #include "RootAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
-struct objc_class;
-
 class RootAccessibleWrap : public RootAccessible
 {
 public:
   RootAccessibleWrap(nsIDocument* aDocument, nsIContent* aRootContent,
                      nsIPresShell* aPresShell);
   virtual ~RootAccessibleWrap();
 
     Class GetNativeType ();
--- a/accessible/tests/mochitest/jsat/doc_content_integration.html
+++ b/accessible/tests/mochitest/jsat/doc_content_integration.html
@@ -39,16 +39,27 @@
     function ariaHideIframe() {
       document.getElementById('iframe').setAttribute('aria-hidden', true);
     }
 
     function renameFruit() {
       document.getElementById('fruit').setAttribute('aria-label', 'banana');
     }
 
+    function renameSlider() {
+      document.getElementById('slider').setAttribute(
+        'aria-label', 'mover');
+    }
+
+    function changeSliderValue() {
+      document.getElementById('slider').setAttribute('aria-valuenow', '5');
+      document.getElementById('slider').setAttribute(
+        'aria-valuetext', 'medium');
+    }
+
   </script>
   <style>
     #windows {
       position: relative;
       width: 320px;
       height: 480px;
     }
 
@@ -84,10 +95,14 @@
       <p>Do you agree?</p>
       <button onclick="setTimeout(hideAlert, 500)">Yes</button>
       <button onclick="hideAlert()">No</button>
     </div>
     <div id="appframe"></div>
   </div>
   <button id="home">Home</button>
   <button id="fruit" aria-label="apple"></button>
+  <div id="live" aria-live="polite" aria-label="live">
+    <div id="slider" role="slider" aria-label="slider" aria-valuemin="0"
+      aria-valuemax="10"  aria-valuenow="0"></div>
+  </div>
 </body>
 </html>
--- a/accessible/tests/mochitest/jsat/jsatcommon.js
+++ b/accessible/tests/mochitest/jsat/jsatcommon.js
@@ -624,17 +624,17 @@ function ExpectedNameChange(aName, aOpti
   }, null, aOptions);
 }
 
 ExpectedNameChange.prototype = Object.create(ExpectedPresent.prototype);
 
 function ExpectedValueChange(aValue, aOptions) {
   ExpectedPresent.call(this, {
     eventType: 'value-change',
-    data: [aValue]
+    data: aValue
   }, null, aOptions);
 }
 
 ExpectedValueChange.prototype = Object.create(ExpectedPresent.prototype);
 
 function ExpectedTextChanged(aValue, aOptions) {
   ExpectedPresent.call(this, {
     eventType: 'text-change',
--- a/accessible/tests/mochitest/jsat/test_content_integration.html
+++ b/accessible/tests/mochitest/jsat/test_content_integration.html
@@ -53,19 +53,23 @@
            new ExpectedCursorChange(['much range', {'string': 'label'}])],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['much range', '5', {'string': 'slider'}])],
           [ContentMessages.moveOrAdjustUp(), new ExpectedValueChange('6')],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+           [ContentMessages.simpleMoveNext,
+           new ExpectedCursorChange(['slider', '0', {'string': 'slider'}])],
 
           // Simple traversal backward
           [ContentMessages.simpleMovePrevious,
+           new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
+          [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['Home', {'string': 'pushbutton'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', '6', {'string': 'slider'}, 'such app'])],
           [ContentMessages.moveOrAdjustDown(), new ExpectedValueChange('5')],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['much range', {'string': 'label'}])],
           [ContentMessages.simpleMovePrevious,
            new ExpectedCursorChange(['many option', {'string': 'stateChecked'},
@@ -87,17 +91,17 @@
           // Moving to the absolute last item from an embedded document
           // fails. Bug 972035.
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(
             ['wow', {'string': 'headingLevel', 'args': [1]}, 'such app'])],
           // Move from an inner frame to the last element in the parent doc
           [ContentMessages.simpleMoveLast,
             new ExpectedCursorChange(
-              ['apple', {'string': 'pushbutton'}], { b2g_todo: true })],
+              ['slider', '0', {'string': 'slider'}], { b2g_todo: true })],
 
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])],
           [ContentMessages.moveOrAdjustDown('FormElement'),
            new ExpectedCursorChange(['Back', {"string": "pushbutton"}])],
           [ContentMessages.moveOrAdjustDown('FormElement'),
@@ -142,16 +146,22 @@
 
           // Current virtual cursor's position's name changes
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])],
           [ContentMessages.focusSelector('button#fruit', false),
            new ExpectedCursorChange(['apple', {'string': 'pushbutton'}])],
           [doc.defaultView.renameFruit, new ExpectedNameChange('banana')],
 
+          // Name and value changes inside a live-region (no cursor present)
+          [doc.defaultView.renameSlider,
+            new ExpectedNameChange('mover')],
+          [doc.defaultView.changeSliderValue,
+            new ExpectedValueChange('medium')],
+
           // Blur button and reset cursor
           [ContentMessages.focusSelector('button#fruit', true), null],
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Move cursor with focus in outside document
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])],
           [ContentMessages.focusSelector('button#home', false),
@@ -217,24 +227,24 @@
            new ExpectedCursorChange(
              ["wow", {"string": "headingLevel","args": [1]}, "such app"])],
           [doc.defaultView.ariaShowBack],
           [ContentMessages.focusSelector('button#back', true), null],
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Open dialog in outer doc, while cursor is also in outer doc
           [ContentMessages.simpleMoveLast,
-           new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])],
+           new ExpectedCursorChange(['mover'])],
           [doc.defaultView.showAlert,
             new ExpectedCursorChange(['This is an alert!',
               {'string': 'headingLevel', 'args': [1]},
               {'string': 'dialog'}])],
 
           [doc.defaultView.hideAlert,
-           new ExpectedCursorChange(['banana', {'string': 'pushbutton'}])],
+           new ExpectedCursorChange(['mover'])],
 
           [ContentMessages.clearCursor, 'AccessFu:CursorCleared'],
 
           // Open dialog in outer doc, while cursor is in inner frame
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(['Phone status bar', 'Traversal Rule test document'])],
           [ContentMessages.simpleMoveNext,
            new ExpectedCursorChange(["Back", {"string": "pushbutton"}])],
--- a/accessible/windows/msaa/nsWinUtils.h
+++ b/accessible/windows/msaa/nsWinUtils.h
@@ -9,17 +9,16 @@
 #define nsWinUtils_h_
 
 #include <windows.h>
 
 #include "nsIDOMCSSStyleDeclaration.h"
 #include "nsCOMPtr.h"
 #include "nsRefPtrHashtable.h"
 
-class nsIArray;
 class nsIContent;
 
 namespace mozilla {
 namespace a11y {
 
 class DocAccessible;
 
 const LPCWSTR kClassNameRoot = L"MozillaUIWindowClass";
--- a/accessible/xul/XULListboxAccessible.h
+++ b/accessible/xul/XULListboxAccessible.h
@@ -9,18 +9,16 @@
 #include "BaseAccessibles.h"
 #include "TableAccessible.h"
 #include "TableCellAccessible.h"
 #include "xpcAccessibleTable.h"
 #include "xpcAccessibleTableCell.h"
 #include "XULMenuAccessible.h"
 #include "XULSelectControlAccessible.h"
 
-class nsIWeakReference;
-
 namespace mozilla {
 namespace a11y {
 
 /**
  * XULColumAccessible are accessible for list and tree columns elements
  * (xul:treecols and xul:listcols).
  */
 class XULColumAccessible : public AccessibleWrap
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -318,17 +318,17 @@ pref("media.cache_readahead_limit", 30);
 // Enable/Disable Gonk Decoder Module
 pref("media.fragmented-mp4.gonk.enabled", true);
 #endif
 // The default number of decoded video frames that are enqueued in
 // MediaDecoderReader's mVideoQueue.
 pref("media.video-queue.default-size", 3);
 
 // optimize images' memory usage
-pref("image.decode-only-on-draw.enabled", true);
+pref("image.decode-only-on-draw.enabled", false);
 pref("image.mem.allow_locking_in_content_processes", true);
 // Limit the surface cache to 1/8 of main memory or 128MB, whichever is smaller.
 // Almost everything that was factored into 'max_decoded_image_kb' is now stored
 // in the surface cache.  1/8 of main memory is 32MB on a 256MB device, which is
 // about the same as the old 'max_decoded_image_kb'.
 pref("image.mem.surfacecache.max_size_kb", 131072);  // 128MB
 pref("image.mem.surfacecache.size_factor", 8);  // 1/8 of main memory
 pref("image.mem.surfacecache.discard_factor", 2);  // Discard 1/2 of the surface cache at a time.
@@ -1119,13 +1119,10 @@ pref("dom.mozSettings.SettingsService.ve
 // Controlling whether we want to allow forcing some Settings
 // IndexedDB transactions to be opened as readonly or keep everything as
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
 // RequestSync API is enabled by default on B2G.
 pref("dom.requestSync.enabled", true);
 
-// Use vsync aligned rendering
-pref("gfx.vsync.hw-vsync.enabled", true);
-pref("gfx.vsync.compositor", true);
+// Resample touch events on b2g
 pref("gfx.touch.resample", true);
-pref("gfx.vsync.refreshdriver", true);
--- a/b2g/chrome/content/content.css
+++ b/b2g/chrome/content/content.css
@@ -25,24 +25,29 @@ html xul|scrollbar[root="true"] {
 html xul|scrollbar {
   -moz-appearance: none !important;
   background-color: transparent !important;
   background-image: none !important;
   border: 0px solid transparent !important;
   pointer-events: none;
 }
 
-/* Scrollbar code will reset the margin to the correct side depending on
-   layout.scrollbar.side pref */
 xul|scrollbar[orient="vertical"] {
-  margin-left: -8px;
+  -moz-margin-start: -8px;
   min-width: 8px;
   max-width: 8px;
 }
 
+/* workaround for bug 1119057: as -moz-margin-start may not work as expected,
+ * force a right margin value in RTL mode.  */
+[dir="rtl"] xul|scrollbar[root="true"][orient="vertical"] {
+  -moz-margin-start: unset;
+  margin-right: -8px;
+}
+
 xul|scrollbar[orient="vertical"] xul|thumb {
   max-width: 6px !important;
   min-width: 6px !important;
 }
 
 xul|scrollbar[orient="horizontal"] {
   margin-top: -8px;
   min-height: 8px;
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -204,16 +204,17 @@
 #endif
 @RESPATH@/components/dom_notification.xpt
 @RESPATH@/components/dom_html.xpt
 @RESPATH@/components/dom_offline.xpt
 @RESPATH@/components/dom_payment.xpt
 @RESPATH@/components/dom_json.xpt
 @RESPATH@/components/dom_messages.xpt
 @RESPATH@/components/dom_power.xpt
+@RESPATH@/components/dom_push.xpt
 @RESPATH@/components/dom_quota.xpt
 @RESPATH@/components/dom_range.xpt
 @RESPATH@/components/dom_security.xpt
 @RESPATH@/components/dom_settings.xpt
 @RESPATH@/components/dom_permissionsettings.xpt
 @RESPATH@/components/dom_sidebar.xpt
 @RESPATH@/components/dom_cellbroadcast.xpt
 @RESPATH@/components/dom_icc.xpt
@@ -626,17 +627,17 @@
 @RESPATH@/components/XULStore.js
 @RESPATH@/components/XULStore.manifest
 @RESPATH@/components/Webapps.js
 @RESPATH@/components/Webapps.manifest
 @RESPATH@/components/AppsService.js
 @RESPATH@/components/AppsService.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
-@RESPATH@/components/PushServiceLauncher.js
+@RESPATH@/components/PushNotificationService.js
 
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
 @RESPATH@/components/InterAppMessagePort.js
 
 @RESPATH@/components/nsDOMIdentity.js
 @RESPATH@/components/nsIDService.js
--- a/b2g/locales/en-US/chrome/overrides/appstrings.properties
+++ b/b2g/locales/en-US/chrome/overrides/appstrings.properties
@@ -25,13 +25,14 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
+unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -369,16 +369,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
+        <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
         <h1 id="et_sslv3Used">&sslv3Used.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
@@ -396,16 +397,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
+        <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc;</div>
         <div id="learn_more_ssl3">&sslv3Used.learnMore;</div>
       </div>
     </div>
 
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -74,55 +74,106 @@
         // Handoff to the appropriate initializer, based on error code
         switch (getErrorCode()) {
           case "malwareBlocked" :
             initPage_malware();
             break;
           case "phishingBlocked" :
             initPage_phishing();
             break;
+          case "unwantedBlocked" :
+            initPage_unwanted();
+            break;
         }
       }        
       
       /**
        * Initialize custom strings and functionality for blocked malware case
        */
       function initPage_malware()
       {
-        // Remove phishing strings
+        // Remove phishing and unwanted strings
         var el = document.getElementById("errorTitleText_phishing");
         el.parentNode.removeChild(el);
 
         el = document.getElementById("errorShortDescText_phishing");
         el.parentNode.removeChild(el);
 
         el = document.getElementById("errorLongDescText_phishing");
         el.parentNode.removeChild(el);
 
+        el = document.getElementById("errorTitleText_unwanted");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_unwanted");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_unwanted");
+        el.parentNode.removeChild(el);
+
         // Set sitename
         document.getElementById("malware_sitename").textContent = getHostString();
         document.title = document.getElementById("errorTitleText_malware")
                                  .innerHTML;
       }
       
       /**
+       * Initialize custom strings and functionality for blocked malware case
+       */
+      function initPage_unwanted()
+      {
+        // Remove phishing and malware strings
+        var el = document.getElementById("errorTitleText_phishing");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_phishing");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_phishing");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorTitleText_malware");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_malware");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_malware");
+        el.parentNode.removeChild(el);
+
+        // Set sitename
+        document.getElementById("unwanted_sitename").textContent = getHostString();
+        document.title = document.getElementById("errorTitleText_unwanted")
+                                 .innerHTML;
+      }
+      
+      /**
        * Initialize custom strings and functionality for blocked phishing case
        */
       function initPage_phishing()
       {
-        // Remove malware strings
+        // Remove malware and unwanted strings
         var el = document.getElementById("errorTitleText_malware");
         el.parentNode.removeChild(el);
 
         el = document.getElementById("errorShortDescText_malware");
         el.parentNode.removeChild(el);
 
         el = document.getElementById("errorLongDescText_malware");
         el.parentNode.removeChild(el);
 
+        el = document.getElementById("errorTitleText_unwanted");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorShortDescText_unwanted");
+        el.parentNode.removeChild(el);
+
+        el = document.getElementById("errorLongDescText_unwanted");
+        el.parentNode.removeChild(el);
+
         // Set sitename
         document.getElementById("phishing_sitename").textContent = getHostString();
         document.title = document.getElementById("errorTitleText_phishing")
                                  .innerHTML;
       }
     ]]></script>
     <style type="text/css">
       /* Style warning button to look like a small text link in the
@@ -156,30 +207,33 @@
 
   <body dir="&locale.dir;">
     <div id="errorPageContainer">
     
       <!-- Error Title -->
       <div id="errorTitle">
         <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title;</h1>
         <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+        <h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
       </div>
       
       <div id="errorLongContent">
       
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc;</p>
           <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
+          <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
         </div>
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
+          <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
         
         <!-- Action buttons -->
         <div id="buttons">
           <!-- Commands handled in browser.js -->
           <button id="getMeOutButton">&safeb.palm.accept.label;</button>
           <button id="reportButton">&safeb.palm.reportPage.label;</button>
         </div>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2654,17 +2654,17 @@ let BrowserOnClick = {
   receiveMessage: function (msg) {
     switch (msg.name) {
       case "Browser:CertExceptionError":
         this.onAboutCertError(msg.target, msg.data.elementId,
                               msg.data.isTopFrame, msg.data.location,
                               msg.data.sslStatusAsString);
       break;
       case "Browser:SiteBlockedError":
-        this.onAboutBlocked(msg.data.elementId, msg.data.isMalware,
+        this.onAboutBlocked(msg.data.elementId, msg.data.reason,
                             msg.data.isTopFrame, msg.data.location);
       break;
       case "Browser:EnableOnlineMode":
         if (Services.io.offline) {
           // Reset network state and refresh the page.
           Services.io.offline = false;
           msg.target.reload();
         }
@@ -2838,57 +2838,48 @@ let BrowserOnClick = {
         if (isTopFrame) {
           secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_BAD_CERT_TOP_UNDERSTAND_RISKS);
         }
         break;
 
     }
   },
 
-  onAboutBlocked: function (elementId, isMalware, isTopFrame, location) {
-    // Depending on what page we are displaying here (malware/phishing)
+  onAboutBlocked: function (elementId, reason, isTopFrame, location) {
+    // Depending on what page we are displaying here (malware/phishing/unwanted)
     // use the right strings and links for each.
-    let bucketName = isMalware ? "WARNING_MALWARE_PAGE_":"WARNING_PHISHING_PAGE_";
+    let bucketName = "WARNING_PHISHING_PAGE_";
+    if (reason === 'malware') {
+      bucketName = "WARNING_MALWARE_PAGE_";
+    } else if (reason === 'unwanted') {
+      bucketName = "WARNING_UNWANTED_PAGE_";
+    }
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
     let nsISecTel = Ci.nsISecurityUITelemetry;
     bucketName += isTopFrame ? "TOP_" : "FRAME_";
     switch (elementId) {
       case "getMeOutButton":
         secHistogram.add(nsISecTel[bucketName + "GET_ME_OUT_OF_HERE"]);
         getMeOutOfHere();
         break;
 
       case "reportButton":
-        // This is the "Why is this site blocked" button.  For malware,
-        // we can fetch a site-specific report, for phishing, we redirect
-        // to the generic page describing phishing protection.
-
-        // We log even if malware/phishing info URL couldn't be found:
+        // This is the "Why is this site blocked" button. We redirect
+        // to the generic page describing phishing/malware protection.
+
+        // We log even if malware/phishing/unwanted info URL couldn't be found:
         // the measurement is for how many users clicked the WHY BLOCKED button
         secHistogram.add(nsISecTel[bucketName + "WHY_BLOCKED"]);
 
-        if (isMalware) {
-          // Get the stop badware "why is this blocked" report url,
-          // append the current url, and go there.
-          try {
-            let reportURL = formatURL("browser.safebrowsing.malware.reportURL", true);
-            reportURL += location;
-            gBrowser.loadURI(reportURL);
-          } catch (e) {
-            Components.utils.reportError("Couldn't get malware report URL: " + e);
-          }
-        }
-        else { // It's a phishing site, not malware
-          openHelpLink("phishing-malware", false, "current");
-        }
+        openHelpLink("phishing-malware", false, "current");
         break;
 
       case "ignoreWarningButton":
         secHistogram.add(nsISecTel[bucketName + "IGNORE_WARNING"]);
-        this.ignoreWarningButton(isMalware);
+        this.ignoreWarningButton(reason);
         break;
     }
   },
 
   /**
    * This functions prevents navigation from happening directly through the <a>
    * link in about:newtab (which is loaded in the parent and therefore would load
    * the next page also in the parent) and instructs the browser to open the url
@@ -2905,17 +2896,17 @@ let BrowserOnClick = {
     if (anchorTarget instanceof HTMLAnchorElement &&
         anchorTarget.classList.contains("newtab-link")) {
       event.preventDefault();
       let where = whereToOpenLink(event, false, false);
       openLinkIn(anchorTarget.href, where, { charset: ownerDoc.characterSet });
     }
   },
 
-  ignoreWarningButton: function (isMalware) {
+  ignoreWarningButton: function (reason) {
     // Allow users to override and continue through to the site,
     // but add a notify bar as a reminder, so that they don't lose
     // track after, e.g., tab switching.
     gBrowser.loadURIWithFlags(gBrowser.currentURI.spec,
                               nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                               null, null, null);
 
     Services.perms.add(gBrowser.currentURI, "safe-browsing",
@@ -2924,34 +2915,38 @@ let BrowserOnClick = {
 
     let buttons = [{
       label: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.label"),
       accessKey: gNavigatorBundle.getString("safebrowsing.getMeOutOfHereButton.accessKey"),
       callback: function() { getMeOutOfHere(); }
     }];
 
     let title;
-    if (isMalware) {
+    if (reason === 'malware') {
       title = gNavigatorBundle.getString("safebrowsing.reportedAttackSite");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAnAttackButton.accessKey"),
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('MalwareError'), 'tab');
         }
       };
-    } else {
+    } else if (reason === 'phishing') {
       title = gNavigatorBundle.getString("safebrowsing.reportedWebForgery");
       buttons[1] = {
         label: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.label"),
         accessKey: gNavigatorBundle.getString("safebrowsing.notAForgeryButton.accessKey"),
         callback: function() {
           openUILinkIn(gSafeBrowsing.getReportURL('Error'), 'tab');
         }
       };
+    } else if (reason === 'unwanted') {
+      title = gNavigatorBundle.getString("safebrowsing.reportedUnwantedSite");
+      // There is no button for reporting errors since Google doesn't currently
+      // provide a URL endpoint for these reports.
     }
 
     let notificationBox = gBrowser.getNotificationBox();
     let value = "blocked-badware-page";
 
     let previousNotification = notificationBox.getNotificationWithValue(value);
     if (previousNotification) {
       notificationBox.removeNotification(previousNotification);
@@ -3989,16 +3984,21 @@ var XULBrowserWindow = {
     if (gURLBar && gURLBar._mayTrimURLs /* corresponds to browser.urlbar.trimURLs */)
       url = trimURL(url);
 
     this.overLink = url;
     LinkTargetDisplay.update();
   },
 
   showTooltip: function (x, y, tooltip) {
+    if (Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService).
+        getCurrentSession()) {
+      return;
+    }
+
     // The x,y coordinates are relative to the <browser> element using
     // the chrome zoom level.
     let elt = document.getElementById("remoteBrowserTooltip");
     elt.label = tooltip;
 
     let anchor = gBrowser.selectedBrowser;
     elt.openPopupAtScreen(anchor.boxObject.screenX + x, anchor.boxObject.screenY + y, false, null);
   },
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -118,16 +118,23 @@ let handleContentContextMenu = function 
     let editFlags = SpellCheckHelper.isEditable(event.target, content);
     let spellInfo;
     if (editFlags &
         (SpellCheckHelper.EDITABLE | SpellCheckHelper.CONTENTEDITABLE)) {
       spellInfo =
         InlineSpellCheckerContent.initContextMenu(event, editFlags, this);
     }
 
+    // Set the event target first as the copy image command needs it to
+    // determine what was context-clicked on. Then, update the state of the
+    // commands on the context menu.
+    docShell.contentViewer.QueryInterface(Ci.nsIContentViewerEdit)
+            .setCommandNode(event.target);
+    event.target.ownerDocument.defaultView.updateCommands("contentcontextmenu");
+
     let customMenuItems = PageMenuChild.build(event.target);
     let principal = doc.nodePrincipal;
     sendSyncMessage("contextmenu",
                     { editFlags, spellInfo, customMenuItems, addonInfo,
                       principal, docLocation, charSet, baseURI, referrer,
                       referrerPolicy, contentType, contentDisposition,
                       frameOuterWindowID },
                     { event, popupNode: event.target });
@@ -372,19 +379,25 @@ let ClickEventHandler = {
       location: ownerDoc.location.href,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView),
       sslStatusAsString: serializedSSLStatus
     });
   },
 
   onAboutBlocked: function (targetElement, ownerDoc) {
+    var reason = 'phishing';
+    if (/e=malwareBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'malware';
+    } else if (/e=unwantedBlocked/.test(ownerDoc.documentURI)) {
+      reason = 'unwanted';
+    }
     sendAsyncMessage("Browser:SiteBlockedError", {
       location: ownerDoc.location.href,
-      isMalware: /e=malwareBlocked/.test(ownerDoc.documentURI),
+      reason: reason,
       elementId: targetElement.getAttribute("id"),
       isTopFrame: (ownerDoc.defaultView.parent === ownerDoc.defaultView)
     });
   },
 
   onAboutNetError: function (event, documentURI) {
     let elmId = event.originalTarget.getAttribute("id");
     if (elmId != "errorTryAgain" || !/e=netOffline/.test(documentURI)) {
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -623,17 +623,19 @@ nsContextMenu.prototype = {
                                         .outerWindowID;
     }
     this.onSocial = !!this.browser.getAttribute("origin");
 
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     this.inSyntheticDoc = ownerDoc.mozSyntheticDocument;
     // First, do checks for nodes that never have children.
     if (this.target.nodeType == Node.ELEMENT_NODE) {
-      // See if the user clicked on an image.
+      // See if the user clicked on an image. This check mirrors
+      // nsDocumentViewer::GetInImage. Make sure to update both if this is
+      // changed.
       if (this.target instanceof Ci.nsIImageLoadingContent &&
           this.target.currentURI) {
         this.onImage = true;
 
         var request =
           this.target.getRequest(Ci.nsIImageLoadingContent.CURRENT_REQUEST);
         if (request && (request.imageStatus & request.STATUS_SIZE_AVAILABLE))
           this.onLoadedImage = true;
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -481,16 +481,21 @@ Sanitizer.prototype = {
         }
 
         // Clear site security settings - no support for ranges in this
         // interface either, so we clearAll().
         var sss = Cc["@mozilla.org/ssservice;1"]
                     .getService(Ci.nsISiteSecurityService);
         sss.clearAll();
 
+        // Clear all push notification subscriptions
+        var push = Cc["@mozilla.org/push/NotificationService;1"]
+                    .getService(Ci.nsIPushNotificationService);
+        push.clearAll();
+
         TelemetryStopwatch.finish("FX_SANITIZE_SITESETTINGS");
       },
 
       get canClear()
       {
         return true;
       }
     },
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -139,17 +139,16 @@ skip-if = e10s # Bug 1101993 - times out
 [browser_backButtonFitts.js]
 skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
 [browser_beforeunload_duplicate_dialogs.js]
 skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
 [browser_blob-channelname.js]
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug304198.js]
-skip-if = e10s
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
 [browser_bug331772_xul_tooltiptext_in_html.js]
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
 skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
@@ -210,17 +209,16 @@ skip-if = e10s # Bug 1056146 - zoom test
 skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
 [browser_bug556061.js]
 [browser_bug559991.js]
 [browser_bug561623.js]
 [browser_bug561636.js]
 [browser_bug562649.js]
 [browser_bug563588.js]
 [browser_bug565575.js]
-skip-if = e10s
 [browser_bug565667.js]
 skip-if = toolkit != "cocoa"
 [browser_bug567306.js]
 [browser_bug575561.js]
 [browser_bug575830.js]
 skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug577121.js]
 [browser_bug578534.js]
@@ -245,17 +243,16 @@ skip-if = e10s # Bug 653065 - Make the l
 [browser_bug623155.js]
 skip-if = e10s && debug
 [browser_bug623893.js]
 [browser_bug624734.js]
 [browser_bug633691.js]
 [browser_bug647886.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093373 - Relies on browser.sessionHistory
 [browser_bug655584.js]
-skip-if = e10s
 [browser_bug664672.js]
 [browser_bug676619.js]
 skip-if = buildapp == 'mulet' || os == "mac" # mac: Intermittent failures, bug 925225
 [browser_bug678392.js]
 skip-if = e10s # bug 1102331 - does focus things on the content window which break in e10s mode
 [browser_bug710878.js]
 [browser_bug719271.js]
 skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
@@ -332,17 +329,16 @@ skip-if = e10s # Bug 921957 - remote web
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
 [browser_locationBarCommand.js]
 skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
 [browser_locationBarExternalLoad.js]
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
-skip-if = e10s # Bug 921952 - Content:Click event issues
 [browser_minimize.js]
 skip-if = e10s # Bug 1100664 - test directly access content docShells (TypeError: gBrowser.docShell is null)
 [browser_mixedcontent_securityflags.js]
 [browser_notification_tab_switching.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
 [browser_overflowScroll.js]
@@ -383,17 +379,17 @@ skip-if = buildapp == 'mulet'
 skip-if = buildapp == 'mulet'
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_link_when_window_navigates.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
-skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
+skip-if = buildapp == 'mulet'
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
 [browser_selectTabAtIndex.js]
@@ -460,17 +456,16 @@ skip-if = true # Bug 1005420 - fails int
 [browser_visibleTabs_bookmarkAllTabs.js]
 [browser_visibleTabs_contextMenu.js]
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug) || e10s # Bug 1007418
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 skip-if = buildapp == 'mulet'
 [browser_wyciwyg_urlbarCopying.js]
-skip-if = e10s # Bug 1100703 - test directly manipulates content (content.document.getElementById)
 [browser_zbug569342.js]
 skip-if = e10s # Bug 1094240 - has findbar-related failures
 [browser_registerProtocolHandler_notification.js]
 skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
 [browser_no_mcb_on_http_site.js]
 [browser_bug1003461-switchtab-override.js]
 [browser_bug1024133-switchtab-override-keynav.js]
 [browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
--- a/browser/base/content/test/general/browser_bug304198.js
+++ b/browser/base/content/test/general/browser_bug304198.js
@@ -1,124 +1,106 @@
 /* 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/. */
 
-function test() {
-  waitForExplicitFinish();
-
+add_task(function* () {
   let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
 
   charsToDelete = 5;
   deletedURLTab = gBrowser.addTab();
   fullURLTab = gBrowser.addTab();
   partialURLTab = gBrowser.addTab();
   testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
 
+  let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
+  let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
+  let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
+  deletedURLTab.linkedBrowser.loadURI(testURL);
+  fullURLTab.linkedBrowser.loadURI(testURL);
+  partialURLTab.linkedBrowser.loadURI(testURL);
+  yield Promise.all([loaded1, loaded2, loaded3]);
+
+  testURL = gURLBar.trimValue(testURL);
+  testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
+
   function cleanUp() {
     gBrowser.removeTab(fullURLTab);
     gBrowser.removeTab(partialURLTab);
     gBrowser.removeTab(deletedURLTab);
   }
 
-  function cycleTabs() {
-    gBrowser.selectedTab = fullURLTab;
+  function* cycleTabs() {
+    yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
     is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
 
-    gBrowser.selectedTab = partialURLTab;
+    yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
     is(gURLBar.textValue, testPartialURL, 'gURLBar.textValue should be testPartialURL after switching back to partialURLTab');
-    gBrowser.selectedTab = deletedURLTab;
+    yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
     is(gURLBar.textValue, '', 'gURLBar.textValue should be "" after switching back to deletedURLTab');
 
-    gBrowser.selectedTab = fullURLTab;
+    yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
     is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after switching back to fullURLTab');
   }
 
-  // function borrowed from browser_bug386835.js
-  function load(tab, url, cb) {
-    tab.linkedBrowser.addEventListener("load", function (event) {
-      event.currentTarget.removeEventListener("load", arguments.callee, true);
-      cb();
-    }, true);
-    tab.linkedBrowser.loadURI(url);
+  function urlbarBackspace() {
+    return new Promise((resolve, reject) => {
+      gBrowser.selectedBrowser.focus();
+      gURLBar.addEventListener("input", function () {
+        gURLBar.removeEventListener("input", arguments.callee, false);
+        resolve();
+      }, false);
+      gURLBar.focus();
+      EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+    });
   }
 
-  function urlbarBackspace(cb) {
-    gBrowser.selectedBrowser.focus();
-    gURLBar.addEventListener("focus", function () {
-      gURLBar.removeEventListener("focus", arguments.callee, false);
-      gURLBar.addEventListener("input", function () {
-        gURLBar.removeEventListener("input", arguments.callee, false);
-        cb();
-      }, false);
-      executeSoon(function () {
-        EventUtils.synthesizeKey("VK_BACK_SPACE", {});
-      });
-    }, false);
-    gURLBar.focus();
-  }
-
-  function prepareDeletedURLTab(cb) {
-    gBrowser.selectedTab = deletedURLTab;
+  function* prepareDeletedURLTab() {
+    yield BrowserTestUtils.switchTab(gBrowser, deletedURLTab);
     is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to deletedURLTab');
 
     // simulate the user removing the whole url from the location bar
     gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", true);
 
-    urlbarBackspace(function () {
-      is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
-      if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
-        gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
-      cb();
-    });
+    yield urlbarBackspace();
+    is(gURLBar.textValue, "", 'gURLBar.textValue should be "" (just set)');
+    if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+      gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+    }
   }
 
-  function prepareFullURLTab(cb) {
-    gBrowser.selectedTab = fullURLTab;
+  function* prepareFullURLTab() {
+    yield BrowserTestUtils.switchTab(gBrowser, fullURLTab);
     is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to fullURLTab');
-    cb();
   }
 
-  function preparePartialURLTab(cb) {
-    gBrowser.selectedTab = partialURLTab;
+  function* preparePartialURLTab() {
+    yield BrowserTestUtils.switchTab(gBrowser, partialURLTab);
     is(gURLBar.textValue, testURL, 'gURLBar.textValue should be testURL after initial switch to partialURLTab');
 
     // simulate the user removing part of the url from the location bar
     gPrefService.setBoolPref("browser.urlbar.clickSelectsAll", false);
 
-    var deleted = 0;
-    urlbarBackspace(function () {
+    let deleted = 0;
+    while (deleted < charsToDelete) {
+      yield urlbarBackspace(arguments.callee);
       deleted++;
-      if (deleted < charsToDelete) {
-        urlbarBackspace(arguments.callee);
-      } else {
-        is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
-        if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll"))
-          gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
-        cb();
-      }
-    });
+    }
+
+    is(gURLBar.textValue, testPartialURL, "gURLBar.textValue should be testPartialURL (just set)");
+    if (gPrefService.prefHasUserValue("browser.urlbar.clickSelectsAll")) {
+      gPrefService.clearUserPref("browser.urlbar.clickSelectsAll");
+    }
   }
 
-  function runTests() {
-    testURL = gURLBar.trimValue(testURL);
-    testPartialURL = testURL.substr(0, (testURL.length - charsToDelete));
+  // prepare the three tabs required by this test
+
+  // First tab
+  yield* prepareFullURLTab();
+  yield* preparePartialURLTab();
+  yield* prepareDeletedURLTab();
 
-    // prepare the three tabs required by this test
-    prepareFullURLTab(function () {
-      preparePartialURLTab(function () {
-        prepareDeletedURLTab(function () {
-          // now cycle the tabs and make sure everything looks good
-          cycleTabs();
-          cleanUp();
-          finish();
-        });
-      });
-    });
-  }
+  // now cycle the tabs and make sure everything looks good
+  yield* cycleTabs();
+  cleanUp();
+});
 
-  load(deletedURLTab, testURL, function() {
-    load(fullURLTab, testURL, function() {
-      load(partialURLTab, testURL, runTests);
-    });
-  });
-}
 
--- a/browser/base/content/test/general/browser_bug565575.js
+++ b/browser/base/content/test/general/browser_bug565575.js
@@ -1,13 +1,14 @@
-function test() {
+add_task(function* () {
   gBrowser.selectedBrowser.focus();
-  BrowserOpenTab();
+
+  yield BrowserTestUtils.openNewForegroundTab(gBrowser, () => BrowserOpenTab(), false);
   ok(gURLBar.focused, "location bar is focused for a new tab");
 
-  gBrowser.selectedTab = gBrowser.tabs[0];
+  yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[0]);
   ok(!gURLBar.focused, "location bar isn't focused for the previously selected tab");
 
-  gBrowser.selectedTab = gBrowser.tabs[1];
+  yield BrowserTestUtils.switchTab(gBrowser, gBrowser.tabs[1]);
   ok(gURLBar.focused, "location bar is re-focused when selecting the new tab");
 
   gBrowser.removeCurrentTab();
-}
+});
--- a/browser/base/content/test/general/browser_bug655584.js
+++ b/browser/base/content/test/general/browser_bug655584.js
@@ -1,23 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Bug 655584 - awesomebar suggestions don't update after tab is closed
 
-function test() {
+add_task(function* () {
   var tab1 = gBrowser.addTab();
   var tab2 = gBrowser.addTab();
 
   // When urlbar in a new tab is focused, and a tab switch occurs,
   // the urlbar popup should be closed
-  gBrowser.selectedTab = tab2;
+  yield BrowserTestUtils.switchTab(gBrowser, tab2);
   gURLBar.focus(); // focus the urlbar in the tab we will switch to
-  gBrowser.selectedTab = tab1;
+  yield BrowserTestUtils.switchTab(gBrowser, tab1);
   gURLBar.openPopup();
-  gBrowser.selectedTab = tab2;
+  yield BrowserTestUtils.switchTab(gBrowser, tab2);
   ok(!gURLBar.popupOpen, "urlbar focused in tab to switch to, close popup");
   
   // cleanup
   gBrowser.removeCurrentTab();
   gBrowser.removeCurrentTab();
-}
+});
--- a/browser/base/content/test/general/browser_clipboard.js
+++ b/browser/base/content/test/general/browser_clipboard.js
@@ -1,34 +1,38 @@
 // This test is used to check copy and paste in editable areas to ensure that non-text
 // types (html and images) are copied to and pasted from the clipboard properly.
 
-let testPage = "<div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>";
+let testPage = "<body style='margin: 0'><img id='img' tabindex='1' src='http://example.org/browser/browser/base/content/test/general/moz.png'>" +
+               "  <div id='main' contenteditable='true'>Test <b>Bold</b> After Text</div>" +
+               "</body>";
 
 add_task(function*() {
   let tab = gBrowser.addTab();
   let browser = gBrowser.getBrowserForTab(tab);
 
   gBrowser.selectedTab = tab;
 
   yield promiseTabLoadEvent(tab, "data:text/html," + escape(testPage));
   yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
 
-  let results = yield ContentTask.spawn(browser, {}, function* () {
+  const modifier = (content.navigator.platform.indexOf("Mac") >= 0) ?
+                   Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
+                   Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
+
+  let results = yield ContentTask.spawn(browser, { modifier: modifier },
+                                        function* (arg) {
     var doc = content.document;
     var main = doc.getElementById("main");
     main.focus();
 
     const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                          .getInterface(Components.interfaces.nsIDOMWindowUtils);
 
-    const modifier = (content.navigator.platform.indexOf("Mac") >= 0) ?
-                     Components.interfaces.nsIDOMWindowUtils.MODIFIER_META :
-                     Components.interfaces.nsIDOMWindowUtils.MODIFIER_CONTROL;
-
+    const modifier = arg.modifier;
     function sendKey(key)
     {
      if (utils.sendKeyEvent("keydown", key, 0, modifier)) {
        utils.sendKeyEvent("keypress", key, key.charCodeAt(0), modifier);
      }
      utils.sendKeyEvent("keyup", key, 0, modifier);
     }
 
@@ -41,30 +45,30 @@ add_task(function*() {
     let selection = doc.getSelection();
     selection.modify("move", "left", "line");
     selection.modify("move", "right", "character");
     selection.modify("move", "right", "character");
     selection.modify("move", "right", "character");
     selection.modify("extend", "right", "word");
     selection.modify("extend", "right", "word");
 
-    yield new content.Promise((resolve, reject) => {
+    yield new Promise((resolve, reject) => {
       addEventListener("copy", function copyEvent(event) {
         removeEventListener("copy", copyEvent, true);
         // The data is empty as the selection is copied during the event default phase.
         is(event.clipboardData.mozItemCount, 0, "Zero items on clipboard");
         resolve();
       }, true)
 
       sendKey("c");
     });
 
     selection.modify("move", "right", "line");
 
-    yield new content.Promise((resolve, reject) => {
+    yield new Promise((resolve, reject) => {
       addEventListener("paste", function copyEvent(event) {
         removeEventListener("paste", copyEvent, true);
         let clipboardData = event.clipboardData; 
         is(clipboardData.mozItemCount, 1, "One item on clipboard");
         is(clipboardData.types.length, 2, "Two types on clipboard");
         is(clipboardData.types[0], "text/html", "text/html on clipboard");
         is(clipboardData.types[1], "text/plain", "text/plain on clipboard");
         is(clipboardData.getData("text/html"), "t <b>Bold</b>", "text/html value");
@@ -75,49 +79,105 @@ add_task(function*() {
     });
 
     is(main.innerHTML, "Test <b>Bold</b> After Textt <b>Bold</b>", "Copy and paste html");
 
     selection.modify("extend", "left", "word");
     selection.modify("extend", "left", "word");
     selection.modify("extend", "left", "character");
 
-    yield new content.Promise((resolve, reject) => {
+    yield new Promise((resolve, reject) => {
       addEventListener("cut", function copyEvent(event) {
         removeEventListener("cut", copyEvent, true);
         event.clipboardData.setData("text/plain", "Some text");
         event.clipboardData.setData("text/html", "<i>Italic</i> ");
         selection.deleteFromDocument();
         event.preventDefault();
         resolve();
       }, true)
       sendKey("x");
     });
 
     selection.modify("move", "left", "line");
 
-    yield new content.Promise((resolve, reject) => {
+    yield new Promise((resolve, reject) => {
       addEventListener("paste", function copyEvent(event) {
         removeEventListener("paste", copyEvent, true);
         let clipboardData = event.clipboardData; 
         is(clipboardData.mozItemCount, 1, "One item on clipboard 2");
         is(clipboardData.types.length, 2, "Two types on clipboard 2");
         is(clipboardData.types[0], "text/html", "text/html on clipboard 2");
         is(clipboardData.types[1], "text/plain", "text/plain on clipboard 2");
         is(clipboardData.getData("text/html"), "<i>Italic</i> ", "text/html value 2");
         is(clipboardData.getData("text/plain"), "Some text", "text/plain value 2");
         resolve();
       }, true)
       sendKey("v");
     });
 
     is(main.innerHTML, "<i>Italic</i> Test <b>Bold</b> After<b></b>", "Copy and paste html 2");
+
     return results;
   });
 
   is(results.length, 15, "Correct number of results");
   for (var t = 0; t < results.length; t++) {
     ok(results[t].startsWith("PASSED"), results[t]);
   }
 
+  // Next, check that the Copy Image command works.
+
+  // The context menu needs to be opened to properly initialize for the copy
+  // image command to run.
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let contextMenuShown = promisePopupShown(contextMenu);
+  BrowserTestUtils.synthesizeMouseAtCenter("#img", { type: "contextmenu", button: 2 }, gBrowser.selectedBrowser);
+  yield contextMenuShown;
+
+  document.getElementById("context-copyimage-contents").doCommand();
+
+  contextMenu.hidePopup();
+  yield promisePopupHidden(contextMenu);
+
+  // Focus the content again
+  yield SimpleTest.promiseFocus(browser.contentWindowAsCPOW);
+
+  let expectedContent = yield ContentTask.spawn(browser, { modifier: modifier },
+                                                function* (arg) {
+    var doc = content.document;
+    var main = doc.getElementById("main");
+    main.focus();
+
+    yield new Promise((resolve, reject) => {
+      addEventListener("paste", function copyEvent(event) {
+        removeEventListener("paste", copyEvent, true);
+        let clipboardData = event.clipboardData;
+
+        // DataTransfer doesn't support the image types yet, so only text/html
+        // will be present.
+        if (clipboardData.getData("text/html") !=
+            '<img id="img" tabindex="1" src="http://example.org/browser/browser/base/content/test/general/moz.png">') {
+          reject();
+        }
+        resolve();
+      }, true)
+
+      const utils = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                           .getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+      const modifier = arg.modifier;
+      if (utils.sendKeyEvent("keydown", "v", 0, modifier)) {
+        utils.sendKeyEvent("keypress", "v", "v".charCodeAt(0), modifier);
+      }
+      utils.sendKeyEvent("keyup", "v", 0, modifier);
+    });
+
+    // Return the new content which should now include an image.
+    return main.innerHTML;
+  });
+
+  is(expectedContent, '<i>Italic</i> <img id="img" tabindex="1" ' +
+                      'src="http://example.org/browser/browser/base/content/test/general/moz.png">' +
+                      'Test <b>Bold</b> After<b></b>', "Paste after copy image");
+
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
+++ b/browser/base/content/test/general/browser_middleMouse_noJSPaste.js
@@ -1,53 +1,34 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const middleMousePastePref = "middlemouse.contentLoadURL";
 const autoScrollPref = "general.autoScroll";
-function test() {
-  waitForExplicitFinish();
+
+add_task(function* () {
+  yield pushPrefs([middleMousePastePref, true], [autoScrollPref, false]);
+
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser);
 
-  Services.prefs.setBoolPref(middleMousePastePref, true);
-  Services.prefs.setBoolPref(autoScrollPref, false);
-  let tab = gBrowser.selectedTab = gBrowser.addTab();
-
-  registerCleanupFunction(function () {
-    Services.prefs.clearUserPref(middleMousePastePref);
-    Services.prefs.clearUserPref(autoScrollPref);
-    gBrowser.removeTab(tab);
+  let url = "javascript:http://www.example.com/";
+  yield new Promise((resolve, reject) => {
+    SimpleTest.waitForClipboard(url, () => {
+      Components.classes["@mozilla.org/widget/clipboardhelper;1"]
+                .getService(Components.interfaces.nsIClipboardHelper)
+                .copyString(url, document);
+    }, resolve, () => {
+      ok(false, "Clipboard copy failed");
+      reject();
+    });
   });
 
-  addPageShowListener(function () {
-    let pagePrincipal = gBrowser.contentPrincipal;
+  let middlePagePromise = BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
-    // copy javascript URI to the clipboard
-    let url = "javascript:http://www.example.com/";
-    waitForClipboard(url,
-      function() {
-        Components.classes["@mozilla.org/widget/clipboardhelper;1"]
-                  .getService(Components.interfaces.nsIClipboardHelper)
-                  .copyString(url, document);
-      },
-      function () {
-        // Middle click on the content area
-        info("Middle clicking");
-        EventUtils.sendMouseEvent({type: "click", button: 1}, gBrowser);
-      },
-      function() {
-        ok(false, "Failed to copy URL to the clipboard");
-        finish();
-      }
-    );
+  // Middle click on the content area
+  info("Middle clicking");
+  yield BrowserTestUtils.synthesizeMouse(null, 10, 10, {button: 1}, gBrowser.selectedBrowser);
+  yield middlePagePromise;
 
-    addPageShowListener(function () {
-      is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
-      finish();
-    });
-  });
-}
+  is(gBrowser.currentURI.spec, url.replace(/^javascript:/, ""), "url loaded by middle click doesn't include JS");
 
-function addPageShowListener(func) {
-  gBrowser.selectedBrowser.addEventListener("pageshow", function loadListener() {
-    gBrowser.selectedBrowser.removeEventListener("pageshow", loadListener, false);
-    func();
-  });
-}
+  gBrowser.removeTab(tab);
+});
--- a/browser/base/content/test/general/browser_save_video.js
+++ b/browser/base/content/test/general/browser_save_video.js
@@ -3,80 +3,78 @@
 
 var MockFilePicker = SpecialPowers.MockFilePicker;
 MockFilePicker.init(window);
 
 /**
  * TestCase for bug 564387
  * <https://bugzilla.mozilla.org/show_bug.cgi?id=564387>
  */
-function test() {
-  waitForExplicitFinish();
+add_task(function* () {
   var fileName;
 
+  let loadPromise = BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
   gBrowser.loadURI("http://mochi.test:8888/browser/browser/base/content/test/general/web_video.html");
+  yield loadPromise;
+
+  let popupShownPromise = BrowserTestUtils.waitForEvent(document, "popupshown");
 
-  gBrowser.addEventListener("pageshow", function pageShown(event) {
-    if (event.target.location == "about:blank")
-      return;
-    gBrowser.removeEventListener("pageshow", pageShown);
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#video1",
+                                                 { type: "contextmenu", button: 2 },
+                                                 gBrowser.selectedBrowser);
+  info("context menu click on video1");
 
-    executeSoon(function () {
-      document.addEventListener("popupshown", contextMenuOpened);
+  yield popupShownPromise;
+
+  info("context menu opened on video1");
 
-      var video1 = gBrowser.contentDocument.getElementById("video1");
-      EventUtils.synthesizeMouseAtCenter(video1,
-                                         { type: "contextmenu", button: 2 },
-                                         gBrowser.contentWindow);
-      info("context menu click on video1");
-    });
-  });
-
-  function contextMenuOpened(event) {
-    event.currentTarget.removeEventListener("popupshown", contextMenuOpened);
-    info("context menu opened on video1");
+  // Create the folder the video will be saved into.
+  var destDir = createTemporarySaveDirectory();
+  var destFile = destDir.clone();
 
-    // Create the folder the video will be saved into.
-    var destDir = createTemporarySaveDirectory();
-    var destFile = destDir.clone();
+  MockFilePicker.displayDirectory = destDir;
+  MockFilePicker.showCallback = function(fp) {
+    fileName = fp.defaultString;
+    destFile.append(fileName);
+    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+  };
 
-    MockFilePicker.displayDirectory = destDir;
-    MockFilePicker.showCallback = function(fp) {
-      fileName = fp.defaultString;
-      destFile.append (fileName);
-      MockFilePicker.returnFiles = [destFile];
-      MockFilePicker.filterIndex = 1; // kSaveAsType_URL
-    };
+  let transferCompletePromise = new Promise((resolve) => {
+    function onTransferComplete(downloadSuccess) {
+      ok(downloadSuccess, "Video file should have been downloaded successfully");
+
+      is(fileName, "web-video1-expectedName.ogv",
+         "Video file name is correctly retrieved from Content-Disposition http header");
+      resolve();
+    }
 
     mockTransferCallback = onTransferComplete;
     mockTransferRegisterer.register();
+  });
 
-    registerCleanupFunction(function () {
-      mockTransferRegisterer.unregister();
-      MockFilePicker.cleanup();
-      destDir.remove(true);
-    });
-
-    // Select "Save Video As" option from context menu
-    var saveVideoCommand = document.getElementById("context-savevideo");
-    saveVideoCommand.doCommand();
-    info("context-savevideo command executed");
+  registerCleanupFunction(function () {
+    mockTransferRegisterer.unregister();
+    MockFilePicker.cleanup();
+    destDir.remove(true);
+  });
 
-    event.target.hidePopup();
-  }
-
-  function onTransferComplete(downloadSuccess) {
-    ok(downloadSuccess, "Video file should have been downloaded successfully");
+  // Select "Save Video As" option from context menu
+  var saveVideoCommand = document.getElementById("context-savevideo");
+  saveVideoCommand.doCommand();
+  info("context-savevideo command executed");
 
-    is(fileName, "web-video1-expectedName.ogv",
-       "Video file name is correctly retrieved from Content-Disposition http header");
+  let contextMenu = document.getElementById("contentAreaContextMenu");
+  let popupHiddenPromise = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
+  contextMenu.hidePopup();
+  yield popupHiddenPromise;
 
-    finish();
-  }
-}
+  yield transferCompletePromise;
+});
+
 
 Cc["@mozilla.org/moz/jssubscript-loader;1"]
   .getService(Ci.mozIJSSubScriptLoader)
   .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
                  this);
 
 function createTemporarySaveDirectory() {
   var saveDir = Cc["@mozilla.org/file/directory_service;1"]
--- a/browser/base/content/test/general/browser_wyciwyg_urlbarCopying.js
+++ b/browser/base/content/test/general/browser_wyciwyg_urlbarCopying.js
@@ -1,39 +1,31 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-function test() {
-  waitForExplicitFinish();
-
-  let url = "http://mochi.test:8888/browser/browser/base/content/test/general/test_wyciwyg_copying.html";
-  let tab = gBrowser.selectedTab = gBrowser.addTab(url);
-  tab.linkedBrowser.addEventListener("pageshow", function () {
-    let btn = content.document.getElementById("btn");
-    executeSoon(function () {
-      EventUtils.synthesizeMouseAtCenter(btn, {}, content);
-      let currentURL = gBrowser.currentURI.spec;
-      ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
-
-      executeSoon(function () {
-        testURLBarCopy(url, endTest);
-      });
-    });
-  }, false);
-
-  function endTest() {
-    while (gBrowser.tabs.length > 1)
-      gBrowser.removeCurrentTab();
-    finish();
-  }
-
-  function testURLBarCopy(targetValue, cb) {
+function testURLBarCopy(targetValue) {
+  return new Promise((resolve, reject) => {
     info("Expecting copy of: " + targetValue);
     waitForClipboard(targetValue, function () {
       gURLBar.focus();
       gURLBar.select();
 
       goDoCommand("cmd_copy");
-    }, cb, cb);
-  }
+    }, resolve, () => {
+      ok(false, "Clipboard copy failed");
+      reject();
+    });
+  });
 }
 
+add_task(function* () {
+  const url = "http://mochi.test:8888/browser/browser/base/content/test/general/test_wyciwyg_copying.html";
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
+  yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser);
+  let currentURL = gBrowser.currentURI.spec;
+  ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
+
+  yield testURLBarCopy(url);
+
+  while (gBrowser.tabs.length > 1)
+    gBrowser.removeCurrentTab();
+});
--- a/browser/components/safebrowsing/content/test/browser_bug400731.js
+++ b/browser/components/safebrowsing/content/test/browser_bug400731.js
@@ -29,16 +29,35 @@ function testMalware(event) {
 
   // Confirm that "Ignore this warning" is visible - bug 422410
   var el = content.document.getElementById("ignoreWarningButton");
   ok(el, "Ignore warning button should be present for malware");
   
   var style = content.getComputedStyle(el, null);
   is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for malware");
   
+  // Now launch the unwanted software test
+  window.addEventListener("DOMContentLoaded", testUnwanted, true);
+  content.location = "http://www.itisatrap.org/firefox/unwanted.html";
+}
+
+function testUnwanted(event) {
+  if (event.target != gBrowser.selectedBrowser.contentDocument) {
+    return;
+  }
+
+  window.removeEventListener("DOMContentLoaded", testUnwanted, true);
+
+  // Confirm that "Ignore this warning" is visible - bug 422410
+  var el = content.document.getElementById("ignoreWarningButton");
+  ok(el, "Ignore warning button should be present for unwanted software");
+
+  var style = content.getComputedStyle(el, null);
+  is(style.display, "inline-block", "Ignore Warning button should be display:inline-block for unwanted software");
+
   // Now launch the phishing test
   window.addEventListener("DOMContentLoaded", testPhishing, true);
   content.location = "http://www.itisatrap.org/firefox/its-a-trap.html";
 }
 
 function testPhishing(event) {
   if (event.target != gBrowser.selectedBrowser.contentDocument) {
     return;
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -1,5 +1,5 @@
 // Force SafeBrowsing to be initialized for the tests
-Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple");
+Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
 SafeBrowsing.init();
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -213,16 +213,17 @@
 @RESPATH@/components/dom_media.xpt
 @RESPATH@/components/dom_network.xpt
 @RESPATH@/components/dom_notification.xpt
 @RESPATH@/components/dom_html.xpt
 @RESPATH@/components/dom_icc.xpt
 @RESPATH@/components/dom_offline.xpt
 @RESPATH@/components/dom_json.xpt
 @RESPATH@/components/dom_power.xpt
+@RESPATH@/components/dom_push.xpt
 @RESPATH@/components/dom_quota.xpt
 @RESPATH@/components/dom_range.xpt
 @RESPATH@/components/dom_security.xpt
 @RESPATH@/components/dom_settings.xpt
 @RESPATH@/components/dom_permissionsettings.xpt
 @RESPATH@/components/dom_sidebar.xpt
 @RESPATH@/components/dom_cellbroadcast.xpt
 @RESPATH@/components/dom_mobilemessage.xpt
@@ -553,17 +554,17 @@
 @RESPATH@/components/PhoneNumberService.js
 @RESPATH@/components/PhoneNumberService.manifest
 @RESPATH@/components/NotificationStorage.js
 @RESPATH@/components/NotificationStorage.manifest
 @RESPATH@/components/AlarmsManager.js
 @RESPATH@/components/AlarmsManager.manifest
 @RESPATH@/components/Push.js
 @RESPATH@/components/Push.manifest
-@RESPATH@/components/PushServiceLauncher.js
+@RESPATH@/components/PushNotificationService.js
 
 @RESPATH@/components/SlowScriptDebug.manifest
 @RESPATH@/components/SlowScriptDebug.js
 
 #ifndef RELEASE_BUILD
 @RESPATH@/components/InterAppComm.manifest
 @RESPATH@/components/InterAppCommService.js
 @RESPATH@/components/InterAppConnection.js
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -393,16 +393,17 @@ pointerLock.autoLock.title2=%S will hide
 safebrowsing.getMeOutOfHereButton.label=Get me out of here!
 safebrowsing.getMeOutOfHereButton.accessKey=G
 safebrowsing.reportedWebForgery=Reported Web Forgery!
 safebrowsing.notAForgeryButton.label=This isn't a web forgery…
 safebrowsing.notAForgeryButton.accessKey=F
 safebrowsing.reportedAttackSite=Reported Attack Site!
 safebrowsing.notAnAttackButton.label=This isn't an attack site…
 safebrowsing.notAnAttackButton.accessKey=A
+safebrowsing.reportedUnwantedSite=Reported Unwanted Software Site!
 
 # Ctrl-Tab
 # LOCALIZATION NOTE (ctrlTab.listAllTabs.label): #1 represents the number
 # of tabs in the current browser window. It will always be 2 at least.
 # See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
 ctrlTab.listAllTabs.label=;List All #1 Tabs
 
 # LOCALIZATION NOTE (addKeywordTitleAutoFill): %S will be replaced by the page's title
--- a/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
+++ b/browser/locales/en-US/chrome/browser/safebrowsing/phishing-afterload-warning-message.dtd
@@ -7,12 +7,17 @@
 <!ENTITY safeb.palm.notforgery.label2 "This isn't a web forgery…">
 <!ENTITY safeb.palm.reportPage.label "Why was this page blocked?">
 
 <!ENTITY safeb.blocked.malwarePage.title "Reported Attack Page!">
 <!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="malware_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.malwarePage.shortDesc "This web page at <span id='malware_sitename'/> has been reported as an attack page and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.malwarePage.longDesc "<p>Attack pages try to install programs that steal private information, use your computer to attack others, or damage your system.</p><p>Some attack pages intentionally distribute harmful software, but many are compromised without the knowledge or permission of their owners.</p>">
 
+<!ENTITY safeb.blocked.unwantedPage.title "Reported Unwanted Software Page!">
+<!-- Localization note (safeb.blocked.malware.shortDesc) - Please don't translate the contents of the <span id="unwanted_sitename"/> tag.  It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
+<!ENTITY safeb.blocked.unwantedPage.shortDesc "This web page at <span id='unwanted_sitename'/> has been reported to contain unwanted software and has been blocked based on your security preferences.">
+<!ENTITY safeb.blocked.unwantedPage.longDesc "<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>">
+
 <!ENTITY safeb.blocked.phishingPage.title "Reported Web Forgery!">
 <!-- Localization note (safeb.blocked.phishing.shortDesc) - Please don't translate the contents of the <span id="phishing_sitename"/> tag. It will be replaced at runtime with a domain name (e.g. www.badsite.com) -->
 <!ENTITY safeb.blocked.phishingPage.shortDesc "This web page at <span id='phishing_sitename'/> has been reported as a web forgery and has been blocked based on your security preferences.">
 <!ENTITY safeb.blocked.phishingPage.longDesc "<p>Web forgeries are designed to trick you into revealing personal or financial information by imitating sources you may trust.</p><p>Entering any information on this web page may result in identity theft or other fraud.</p>">
--- a/browser/locales/en-US/chrome/overrides/appstrings.properties
+++ b/browser/locales/en-US/chrome/overrides/appstrings.properties
@@ -25,14 +25,15 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
+unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default in Firefox.
 ## LOCALIZATION NOTE (sslv3Used) - Do not translate "%S".
 sslv3Used=Firefox cannot guarantee the safety of your data on %S because it uses SSLv3, a broken security protocol.
--- a/browser/locales/en-US/chrome/overrides/netError.dtd
+++ b/browser/locales/en-US/chrome/overrides/netError.dtd
@@ -159,16 +159,21 @@ be temporary, and you can try again late
 ">
 
 <!ENTITY malwareBlocked.title "Suspected Attack Site!">
 <!ENTITY malwareBlocked.longDesc "
 <p>Attack sites try to install programs that steal private information, use your computer to attack others, or damage your system.</p>
 <p>Website owners who believe their site has been reported as an attack site in error may <a href='http://www.stopbadware.org/home/reviewinfo' >request a review</a>.</p>
 ">
 
+<!ENTITY unwantedBlocked.title "Suspected Unwanted Software Site!">
+<!ENTITY unwantedBlocked.longDesc "
+<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>
+">
+
 <!ENTITY phishingBlocked.title "Suspected Web Forgery!">
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>&brandShortName; prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
--- a/browser/metro/base/content/contenthandlers/Content.js
+++ b/browser/metro/base/content/contenthandlers/Content.js
@@ -336,30 +336,25 @@ let Content = {
         sendAsyncMessage("Browser:CertException",
                          { url: errorDoc.location.href, action: action });
       } else if (ot == errorDoc.getElementById("getMeOutOfHereButton")) {
         sendAsyncMessage("Browser:CertException",
                          { url: errorDoc.location.href, action: "leave" });
       }
     } else if (/^about:blocked/.test(errorDoc.documentURI)) {
       // The event came from a button on a malware/phishing block page
-      // First check whether it's malware or phishing, so that we can
-      // use the right strings/links.
-      let isMalware = /e=malwareBlocked/.test(errorDoc.documentURI);
     
       if (ot == errorDoc.getElementById("getMeOutButton")) {
         sendAsyncMessage("Browser:BlockedSite",
                          { url: errorDoc.location.href, action: "leave" });
       } else if (ot == errorDoc.getElementById("reportButton")) {
-        // This is the "Why is this site blocked" button.  For malware,
-        // we can fetch a site-specific report, for phishing, we redirect
-        // to the generic page describing phishing protection.
-        let action = isMalware ? "report-malware" : "report-phishing";
+        // This is the "Why is this site blocked" button. We redirect
+        // to the generic page describing phishing/malware protection.
         sendAsyncMessage("Browser:BlockedSite",
-                         { url: errorDoc.location.href, action: action });
+                         { url: errorDoc.location.href, action: "report-phishing" });
       } else if (ot == errorDoc.getElementById("ignoreWarningButton")) {
         // Allow users to override and continue through to the site,
         // but add a notify bar as a reminder, so that they don't lose
         // track after, e.g., tab switching.
         let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
         webNav.loadURI(content.location,
                        Ci.nsIWebNavigation.LOAD_FLAGS_BYPASS_CLASSIFIER,
                        null, null, null);
--- a/browser/themes/linux/devtools/floating-scrollbars.css
+++ b/browser/themes/linux/devtools/floating-scrollbars.css
@@ -4,20 +4,18 @@ scrollbar {
   -moz-appearance: none !important;
   position: relative;
   background-color: transparent;
   background-image: none;
   z-index: 2147483647;
   padding: 2px;
 }
 
-/* Scrollbar code will reset the margin to the correct side depending on
-   layout.scrollbar.side pref */
 scrollbar[orient="vertical"] {
-  margin-left: -10px;
+  -moz-margin-start: -10px;
   min-width: 10px;
   max-width: 10px;
 }
 
 scrollbar[orient="horizontal"] {
   margin-top: -10px;
   min-height: 10px;
   max-height: 10px;
--- a/browser/themes/osx/devtools/floating-scrollbars.css
+++ b/browser/themes/osx/devtools/floating-scrollbars.css
@@ -5,20 +5,18 @@ scrollbar {
   position: relative;
   background-color: transparent;
   background-image: none;
   border: 0px solid transparent;
   z-index: 2147483647;
   padding: 2px;
 }
 
-/* Scrollbar code will reset the margin to the correct side depending on
-   layout.scrollbar.side pref */
 scrollbar[orient="vertical"] {
-  margin-left: -8px;
+  -moz-margin-start: -8px;
   min-width: 8px;
   max-width: 8px;
 }
 
 scrollbar[orient="horizontal"] {
   margin-top: -8px;
   min-height: 8px;
   max-height: 8px;
--- a/browser/themes/windows/devtools/floating-scrollbars.css
+++ b/browser/themes/windows/devtools/floating-scrollbars.css
@@ -4,20 +4,18 @@ scrollbar {
   -moz-appearance: none !important;
   position: relative;
   background-color: transparent;
   background-image: none;
   z-index: 2147483647;
   padding: 2px;
 }
 
-/* Scrollbar code will reset the margin to the correct side depending on
-   layout.scrollbar.side pref */
 scrollbar[orient="vertical"] {
-  margin-left: -10px;
+  -moz-margin-start: -10px;
   min-width: 10px;
   max-width: 10px;
 }
 
 scrollbar[orient="horizontal"] {
   margin-top: -10px;
   min-height: 10px;
   max-height: 10px;
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -96,22 +96,25 @@ def dumpLeakLog(leakLogFile, filter = Fa
 
   # Simply copy the log.
   log.info(leakReport.rstrip("\n"))
 
 def processSingleLeakFile(leakLogFileName, processType, leakThreshold, ignoreMissingLeaks):
   """Process a single leak log.
   """
 
-  #                  Per-Inst  Leaked      Total  Rem ...
-  #   0 TOTAL              17     192  419115886    2 ...
-  # 833 nsTimerImpl        60     120      24726    2 ...
-  lineRe = re.compile(r"^\s*\d+\s+(?P<name>\S+)\s+"
-                      r"(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s+"
-                      r"-?\d+\s+(?P<numLeaked>-?\d+)")
+  #     |              |Per-Inst  Leaked|     Total  Rem|
+  #   0 |TOTAL         |      17     192| 419115886    2|
+  # 833 |nsTimerImpl   |      60     120|     24726    2|
+  # 930 |Foo<Bar, Bar> |      32       8|       100    1|
+  lineRe = re.compile(r"^\s*\d+ \|"
+                      r"(?P<name>[^|]+)\|"
+                      r"\s*(?P<size>-?\d+)\s+(?P<bytesLeaked>-?\d+)\s*\|"
+                      r"\s*-?\d+\s+(?P<numLeaked>-?\d+)")
+  # The class name can contain spaces. We remove trailing whitespace later.
 
   processString = "%s process:" % processType
   crashedOnPurpose = False
   totalBytesLeaked = None
   logAsWarning = False
   leakAnalysis = []
   leakedObjectAnalysis = []
   leakedObjectNames = []
@@ -120,17 +123,17 @@ def processSingleLeakFile(leakLogFileNam
     for line in leaks:
       if line.find("purposefully crash") > -1:
         crashedOnPurpose = True
       matches = lineRe.match(line)
       if not matches:
         # eg: the leak table header row
         log.info(line.rstrip())
         continue
-      name = matches.group("name")
+      name = matches.group("name").rstrip()
       size = int(matches.group("size"))
       bytesLeaked = int(matches.group("bytesLeaked"))
       numLeaked = int(matches.group("numLeaked"))
       # Output the raw line from the leak log table if it is the TOTAL row,
       # or is for an object row that has been leaked.
       if numLeaked != 0 or name == "TOTAL":
         log.info(line.rstrip())
       # Analyse the leak log, but output later or it will interrupt the leak table
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -79,62 +79,87 @@ private:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
   class RefCountedInsideLambdaChecker : public MatchFinder::MatchCallback {
   public:
     virtual void run(const MatchFinder::MatchResult &Result);
   };
 
+  class ExplicitOperatorBoolChecker : public MatchFinder::MatchCallback {
+  public:
+    virtual void run(const MatchFinder::MatchResult &Result);
+  };
+
   ScopeChecker stackClassChecker;
   ScopeChecker globalClassChecker;
   NonHeapClassChecker nonheapClassChecker;
   ArithmeticArgChecker arithmeticArgChecker;
   TrivialCtorDtorChecker trivialCtorDtorChecker;
   NaNExprChecker nanExprChecker;
   NoAddRefReleaseOnReturnChecker noAddRefReleaseOnReturnChecker;
   RefCountedInsideLambdaChecker refCountedInsideLambdaChecker;
+  ExplicitOperatorBoolChecker explicitOperatorBoolChecker;
   MatchFinder astMatcher;
 };
 
 namespace {
 
-bool isInIgnoredNamespace(const Decl *decl) {
+std::string getDeclarationNamespace(const Decl *decl) {
   const DeclContext *DC = decl->getDeclContext()->getEnclosingNamespaceContext();
   const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(DC);
   if (!ND) {
-    return false;
+    return "";
   }
 
   while (const DeclContext *ParentDC = ND->getParent()) {
     if (!isa<NamespaceDecl>(ParentDC)) {
       break;
     }
     ND = cast<NamespaceDecl>(ParentDC);
   }
 
   const auto& name = ND->getName();
+  return name;
+}
 
-  // namespace std and icu are ignored for now
+bool isInIgnoredNamespaceForImplicitCtor(const Decl *decl) {
+  std::string name = getDeclarationNamespace(decl);
+  if (name == "") {
+    return false;
+  }
+
   return name == "std" ||              // standard C++ lib
          name == "__gnu_cxx" ||        // gnu C++ lib
          name == "boost" ||            // boost
          name == "webrtc" ||           // upstream webrtc
          name == "icu_52" ||           // icu
          name == "google" ||           // protobuf
          name == "google_breakpad" ||  // breakpad
          name == "soundtouch" ||       // libsoundtouch
          name == "stagefright" ||      // libstagefright
          name == "MacFileUtilities" || // MacFileUtilities
          name == "dwarf2reader" ||     // dwarf2reader
          name == "arm_ex_to_module" || // arm_ex_to_module
          name == "testing";            // gtest
 }
 
-bool isIgnoredPath(const Decl *decl) {
+bool isInIgnoredNamespaceForImplicitConversion(const Decl *decl) {
+  std::string name = getDeclarationNamespace(decl);
+  if (name == "") {
+    return false;
+  }
+
+  return name == "std" ||              // standard C++ lib
+         name == "__gnu_cxx" ||        // gnu C++ lib
+         name == "google_breakpad" ||  // breakpad
+         name == "testing";            // gtest
+}
+
+bool isIgnoredPathForImplicitCtor(const Decl *decl) {
   decl = decl->getCanonicalDecl();
   SourceLocation Loc = decl->getLocation();
   const SourceManager &SM = decl->getASTContext().getSourceManager();
   SmallString<1024> FileName = SM.getFilename(Loc);
   llvm::sys::fs::make_absolute(FileName);
   llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
                                     end   = llvm::sys::path::rend(FileName);
   for (; begin != end; ++begin) {
@@ -145,19 +170,40 @@ bool isIgnoredPath(const Decl *decl) {
         begin->compare_lower(StringRef("scoped_ptr.h")) == 0 ||
         begin->compare_lower(StringRef("graphite2")) == 0) {
       return true;
     }
   }
   return false;
 }
 
-bool isInterestingDecl(const Decl *decl) {
-  return !isInIgnoredNamespace(decl) &&
-         !isIgnoredPath(decl);
+bool isIgnoredPathForImplicitConversion(const Decl *decl) {
+  decl = decl->getCanonicalDecl();
+  SourceLocation Loc = decl->getLocation();
+  const SourceManager &SM = decl->getASTContext().getSourceManager();
+  SmallString<1024> FileName = SM.getFilename(Loc);
+  llvm::sys::fs::make_absolute(FileName);
+  llvm::sys::path::reverse_iterator begin = llvm::sys::path::rbegin(FileName),
+                                    end   = llvm::sys::path::rend(FileName);
+  for (; begin != end; ++begin) {
+    if (begin->compare_lower(StringRef("graphite2")) == 0) {
+      return true;
+    }
+  }
+  return false;
+}
+
+bool isInterestingDeclForImplicitCtor(const Decl *decl) {
+  return !isInIgnoredNamespaceForImplicitCtor(decl) &&
+         !isIgnoredPathForImplicitCtor(decl);
+}
+
+bool isInterestingDeclForImplicitConversion(const Decl *decl) {
+  return !isInIgnoredNamespaceForImplicitConversion(decl) &&
+         !isIgnoredPathForImplicitConversion(decl);
 }
 
 }
 
 class MozChecker : public ASTConsumer, public RecursiveASTVisitor<MozChecker> {
   DiagnosticsEngine &Diag;
   const CompilerInstance &CI;
   DiagnosticsMatcher matcher;
@@ -227,17 +273,17 @@ public:
         unsigned overrideNote = Diag.getDiagnosticIDs()->getCustomDiagID(
             DiagnosticIDs::Note, "function to override is here");
         Diag.Report(d->getLocation(), overrideID) << d->getDeclName() <<
           (*it)->getDeclName();
         Diag.Report((*it)->getLocation(), overrideNote);
       }
     }
 
-    if (!d->isAbstract() && isInterestingDecl(d)) {
+    if (!d->isAbstract() && isInterestingDeclForImplicitCtor(d)) {
       for (CXXRecordDecl::ctor_iterator ctor = d->ctor_begin(),
            e = d->ctor_end(); ctor != e; ++ctor) {
         // Ignore non-converting ctors
         if (!ctor->isConvertingConstructor(false)) {
           continue;
         }
         // Ignore copy or move constructors
         if (ctor->isCopyOrMoveConstructor()) {
@@ -411,16 +457,26 @@ bool isClassRefCounted(const CXXRecordDe
 
 bool isClassRefCounted(QualType T) {
   while (const ArrayType *arrTy = T->getAsArrayTypeUnsafe())
     T = arrTy->getElementType();
   CXXRecordDecl *clazz = T->getAsCXXRecordDecl();
   return clazz ? isClassRefCounted(clazz) : RegularClass;
 }
 
+template<class T>
+bool IsInSystemHeader(const ASTContext &AC, const T &D) {
+  auto &SourceManager = AC.getSourceManager();
+  auto ExpansionLoc = SourceManager.getExpansionLoc(D.getLocStart());
+  if (ExpansionLoc.isInvalid()) {
+    return false;
+  }
+  return SourceManager.isInSystemHeader(ExpansionLoc);
+}
+
 }
 
 namespace clang {
 namespace ast_matchers {
 
 /// This matcher will match any class with the stack class assertion or an
 /// array of such classes.
 AST_MATCHER(QualType, stackClassAggregate) {
@@ -510,22 +566,17 @@ AST_MATCHER(BinaryOperator, binaryEquali
 AST_MATCHER(QualType, isFloat) {
   return Node->isRealFloatingType();
 }
 
 /// This matcher will match locations in system headers.  This is adopted from
 /// isExpansionInSystemHeader in newer clangs, but modified in order to work
 /// with old clangs that we use on infra.
 AST_MATCHER(BinaryOperator, isInSystemHeader) {
-  auto &SourceManager = Finder->getASTContext().getSourceManager();
-  auto ExpansionLoc = SourceManager.getExpansionLoc(Node.getLocStart());
-  if (ExpansionLoc.isInvalid()) {
-    return false;
-  }
-  return SourceManager.isInSystemHeader(ExpansionLoc);
+  return IsInSystemHeader(Finder->getASTContext(), Node);
 }
 
 /// This matcher will match locations in SkScalar.h.  This header contains a
 /// known NaN-testing expression which we would like to whitelist.
 AST_MATCHER(BinaryOperator, isInSkScalarDotH) {
   SourceLocation Loc = Node.getOperatorLoc();
   auto &SourceManager = Finder->getASTContext().getSourceManager();
   SmallString<1024> FileName = SourceManager.getFilename(Loc);
@@ -644,16 +695,23 @@ DiagnosticsMatcher::DiagnosticsMatcher()
                                                       hasParent(callExpr())).bind("member")
       )).bind("node"),
     &noAddRefReleaseOnReturnChecker);
 
   astMatcher.addMatcher(lambdaExpr(
             hasDescendant(declRefExpr(hasType(pointerType(pointee(isRefCounted())))).bind("node"))
         ),
     &refCountedInsideLambdaChecker);
+
+  // Older clang versions such as the ones used on the infra recognize these
+  // conversions as 'operator _Bool', but newer clang versions recognize these
+  // as 'operator bool'.
+  astMatcher.addMatcher(methodDecl(anyOf(hasName("operator bool"),
+                                         hasName("operator _Bool"))).bind("node"),
+    &explicitOperatorBoolChecker);
 }
 
 void DiagnosticsMatcher::ScopeChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
   unsigned globalID = Diag.getDiagnosticIDs()->getCustomDiagID(
@@ -856,16 +914,35 @@ void DiagnosticsMatcher::RefCountedInsid
       DiagnosticIDs::Note, "Please consider using a smart pointer");
   const DeclRefExpr *node = Result.Nodes.getNodeAs<DeclRefExpr>("node");
 
   Diag.Report(node->getLocStart(), errorID) << node->getFoundDecl() <<
     node->getType()->getPointeeType();
   Diag.Report(node->getLocStart(), noteID);
 }
 
+void DiagnosticsMatcher::ExplicitOperatorBoolChecker::run(
+    const MatchFinder::MatchResult &Result) {
+  DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
+  unsigned errorID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Error, "bad implicit conversion operator for %0");
+  unsigned noteID = Diag.getDiagnosticIDs()->getCustomDiagID(
+      DiagnosticIDs::Note, "consider adding the explicit keyword to %0");
+  const CXXConversionDecl *method = Result.Nodes.getNodeAs<CXXConversionDecl>("node");
+  const CXXRecordDecl *clazz = method->getParent();
+
+  if (!method->isExplicitSpecified() &&
+      !MozChecker::hasCustomAnnotation(method, "moz_implicit") &&
+      !IsInSystemHeader(method->getASTContext(), *method) &&
+      isInterestingDeclForImplicitConversion(method)) {
+    Diag.Report(method->getLocStart(), errorID) << clazz;
+    Diag.Report(method->getLocStart(), noteID) << "'operator bool'";
+  }
+}
+
 class MozCheckAction : public PluginASTAction {
 public:
   ASTConsumerPtr CreateASTConsumer(CompilerInstance &CI, StringRef fileName) override {
 #if CLANG_VERSION_FULL >= 306
     std::unique_ptr<MozChecker> checker(make_unique<MozChecker>(CI));
 
     std::vector<std::unique_ptr<ASTConsumer>> consumers;
     consumers.push_back(std::move(checker));
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestExplicitOperatorBool.cpp
@@ -0,0 +1,11 @@
+#define MOZ_IMPLICIT __attribute__((annotate("moz_implicit")))
+
+struct Bad {
+  operator bool(); // expected-error {{bad implicit conversion operator for 'Bad'}} expected-note {{consider adding the explicit keyword to 'operator bool'}}
+};
+struct Good {
+  explicit operator bool();
+};
+struct Okay {
+  MOZ_IMPLICIT operator bool();
+};
--- a/build/clang-plugin/tests/moz.build
+++ b/build/clang-plugin/tests/moz.build
@@ -2,16 +2,17 @@
 # vim: set filetype=python:
 # 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/.
 
 SOURCES += [
     'TestBadImplicitConversionCtor.cpp',
     'TestCustomHeap.cpp',
+    'TestExplicitOperatorBool.cpp',
     'TestGlobalClass.cpp',
     'TestMustOverride.cpp',
     'TestNANTestingExpr.cpp',
     'TestNANTestingExprC.c',
     'TestNoAddRefReleaseOnReturn.cpp',
     'TestNoArithmeticExprInArgument.cpp',
     'TestNonHeapClass.cpp',
     'TestNoRefcountedInsideLambdas.cpp',
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -175,17 +175,23 @@ class RemoteAutomation(Automation):
                 print "%s does not exist; tombstone check skipped" % remoteDir
         else:
             print "MOZ_UPLOAD_DIR not defined; tombstone check skipped"
 
     def checkForCrashes(self, directory, symbolsPath):
         self.checkForANRs()
         self.checkForTombstones()
 
-        logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
+        try:
+            logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
+        except DMError:
+            print "getLogcat threw DMError; re-trying just once..."
+            time.sleep(1)
+            logcat = self._devicemanager.getLogcat(filterOutRegexps=fennecLogcatFilters)
+
         javaException = mozcrash.check_for_java_exception(logcat)
         if javaException:
             return True
 
         # If crash reporting is disabled (MOZ_CRASHREPORTER!=1), we can't say
         # anything.
         if not self.CRASHREPORTER:
             return False
--- a/build/pgo/server-locations.txt
+++ b/build/pgo/server-locations.txt
@@ -3,17 +3,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/.
 
 #
 # This file defines the locations at which this HTTP server may be accessed.
 # It is referred to by the following page, so if this file moves, that page must
 # be modified accordingly:
 #
-# http://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
+# https://developer.mozilla.org/en/docs/Mochitest#How_do_I_test_issues_which_only_show_up_when_tests_are_run_across_domains.3F
 #
 # Empty lines and lines which begin with "#" are ignored and may be used for
 # storing comments.  All other lines consist of an origin followed by whitespace
 # and a comma-separated list of options (if indeed any options are needed).
 #
 # The format of an origin is, referring to RFC 2396, a scheme (either "http" or
 # "https"), followed by "://", followed by a host, followed by ":", followed by
 # a port number.  The colon and port number must be present even if the port
--- a/caps/DomainPolicy.h
+++ b/caps/DomainPolicy.h
@@ -8,20 +8,16 @@
 #define DomainPolicy_h__
 
 #include "nsIDomainPolicy.h"
 #include "nsTHashtable.h"
 #include "nsURIHashKey.h"
 
 namespace mozilla {
 
-namespace dom {
-class nsIContentParent;
-};
-
 namespace ipc {
 class URIParams;
 };
 
 enum DomainSetChangeType{
     ACTIVATE_POLICY,
     DEACTIVATE_POLICY,
     ADD_DOMAIN,
--- a/caps/nsJSPrincipals.h
+++ b/caps/nsJSPrincipals.h
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 /* describes principals by their orginating uris*/
 
 #ifndef nsJSPrincipals_h__
 #define nsJSPrincipals_h__
 #include "jsapi.h"
 #include "nsIPrincipal.h"
 
-class nsCString;
-
 struct nsJSPrincipals : nsIPrincipal, JSPrincipals
 {
   static bool Subsume(JSPrincipals *jsprin, JSPrincipals *other);
   static void Destroy(JSPrincipals *jsprin);
 
   /*
    * Get a weak reference to nsIPrincipal associated with the given JS
    * principal.
--- a/caps/nsPrincipal.h
+++ b/caps/nsPrincipal.h
@@ -10,19 +10,16 @@
 #include "nsCOMPtr.h"
 #include "nsJSPrincipals.h"
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
 #include "nsIProtocolHandler.h"
 #include "nsNetUtil.h"
 #include "nsScriptSecurityManager.h"
 
-class nsIObjectInputStream;
-class nsIObjectOutputStream;
-
 class nsBasePrincipal : public nsJSPrincipals
 {
 public:
   nsBasePrincipal();
 
 protected:
   virtual ~nsBasePrincipal();
 
--- a/caps/nsScriptSecurityManager.h
+++ b/caps/nsScriptSecurityManager.h
@@ -12,23 +12,20 @@
 #include "nsCOMPtr.h"
 #include "nsIChannelEventSink.h"
 #include "nsIObserver.h"
 #include "plstr.h"
 #include "js/TypeDecls.h"
 
 #include <stdint.h>
 
-class nsIDocShell;
 class nsCString;
-class nsIClassInfo;
 class nsIIOService;
 class nsIStringBundle;
 class nsSystemPrincipal;
-class ClassInfoData;
 
 /////////////////////////////
 // nsScriptSecurityManager //
 /////////////////////////////
 #define NS_SCRIPTSECURITYMANAGER_CID \
 { 0x7ee2a4c0, 0x4b93, 0x17d3, \
 { 0xba, 0x18, 0x00, 0x60, 0xb0, 0xf1, 0x99, 0xa2 }}
 
--- a/configure.in
+++ b/configure.in
@@ -3012,16 +3012,17 @@ then
             if test "$SOLARIS_SUNPRO_CC"; then
                 CFLAGS="$CFLAGS -mt"
                 CXXFLAGS="$CXXFLAGS -mt"
             fi
             ;;
     esac
     LDFLAGS="${_PTHREAD_LDFLAGS} ${LDFLAGS}"
     AC_SUBST(MOZ_USE_PTHREADS)
+    MOZ_CHECK_HEADERS(pthread.h)
 fi
 
 
 dnl Checks for library functions.
 dnl ========================================================
 AC_PROG_GCC_TRADITIONAL
 AC_FUNC_MEMCMP
 AC_CHECK_FUNCS(stat64 lstat64 truncate64 statvfs64 statvfs statfs64 statfs getpagesize gmtime_r localtime_r arc4random arc4random_buf)
--- a/docshell/base/IHistory.h
+++ b/docshell/base/IHistory.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_IHistory_h_
 #define mozilla_IHistory_h_
 
 #include "nsISupports.h"
 
 class nsIURI;
-class nsString;
 
 namespace mozilla {
 
 namespace dom {
 class Link;
 }
 
 // 0057c9d3-b98e-4933-bdc5-0275d06705e1
--- a/docshell/base/LoadContext.h
+++ b/docshell/base/LoadContext.h
@@ -9,18 +9,16 @@
 
 #include "SerializedLoadContext.h"
 #include "mozilla/Attributes.h"
 #include "nsIWeakReferenceUtils.h"
 #include "mozilla/dom/Element.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadContext.h"
 
-class mozIApplication;
-
 namespace mozilla {
 
 /**
  * Class that provides nsILoadContext info in Parent process.  Typically copied
  * from Child via SerializedLoadContext.
  *
  * Note: this is not the "normal" or "original" nsILoadContext.  That is
  * typically provided by nsDocShell.  This is only used when the original
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5082,17 +5082,18 @@ nsDocShell::DisplayLoadError(nsresult aE
           Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId);
         }
 
       } else {
         error.AssignLiteral("nssFailure2");
       }
     }
   } else if (NS_ERROR_PHISHING_URI == aError ||
-             NS_ERROR_MALWARE_URI == aError) {
+             NS_ERROR_MALWARE_URI == aError ||
+             NS_ERROR_UNWANTED_URI == aError) {
     nsAutoCString host;
     aURI->GetHost(host);
     CopyUTF8toUTF16(host, formatStrs[0]);
     formatStrCount = 1;
 
     // Malware and phishing detectors may want to use an alternate error
     // page, but if the pref's not set, we'll fall back on the standard page
     nsAdoptingCString alternateErrorPage =
@@ -5101,24 +5102,29 @@ nsDocShell::DisplayLoadError(nsresult aE
       errorPage.Assign(alternateErrorPage);
     }
 
     uint32_t bucketId;
     if (NS_ERROR_PHISHING_URI == aError) {
       error.AssignLiteral("phishingBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
-    } else {
+    } else if (NS_ERROR_MALWARE_URI == aError) {
       error.AssignLiteral("malwareBlocked");
       bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
                            : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
-    }
-
-    if (errorPage.EqualsIgnoreCase("blocked"))
+    } else {
+      error.AssignLiteral("unwantedBlocked");
+      bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME
+                           : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP;
+    }
+
+    if (errorPage.EqualsIgnoreCase("blocked")) {
       Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId);
+    }
 
     cssClass.AssignLiteral("blacklist");
   } else if (NS_ERROR_CONTENT_CRASHED == aError) {
     errorPage.AssignLiteral("tabcrashed");
     error.AssignLiteral("tabcrashed");
 
     nsCOMPtr<EventTarget> handler = mChromeEventHandler;
     if (handler) {
@@ -7819,16 +7825,17 @@ nsDocShell::EndPageLoad(nsIWebProgress* 
     else if (aStatus == NS_ERROR_NET_TIMEOUT ||
              aStatus == NS_ERROR_REDIRECT_LOOP ||
              aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE ||
              aStatus == NS_ERROR_NET_INTERRUPT ||
              aStatus == NS_ERROR_NET_RESET ||
              aStatus == NS_ERROR_OFFLINE ||
              aStatus == NS_ERROR_MALWARE_URI ||
              aStatus == NS_ERROR_PHISHING_URI ||
+             aStatus == NS_ERROR_UNWANTED_URI ||
              aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE ||
              aStatus == NS_ERROR_REMOTE_XUL ||
              aStatus == NS_ERROR_OFFLINE ||
              NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) {
       // Errors to be shown for any frame
       DisplayLoadError(aStatus, url, nullptr, aChannel);
     } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
       // Non-caching channels will simply return NS_ERROR_OFFLINE.
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -83,17 +83,16 @@ class nsIPrompt;
 class nsISHistory;
 class nsISecureBrowserUI;
 class nsIStringBundle;
 class nsISupportsArray;
 class nsIURIFixup;
 class nsIURILoader;
 class nsIWebBrowserFind;
 class nsIWidget;
-class ProfilerMarkerTracing;
 
 /* load commands were moved to nsIDocShell.h */
 /* load types were moved to nsDocShellLoadTypes.h */
 
 /* internally used ViewMode types */
 enum ViewMode
 {
   viewNormal = 0x0,
--- a/docshell/base/nsIContentViewerEdit.idl
+++ b/docshell/base/nsIContentViewerEdit.idl
@@ -1,17 +1,19 @@
 /* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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 "nsISupports.idl"
 
-[scriptable, uuid(AF13EA3A-D488-4308-B843-526E055AB943)]
+interface nsIDOMNode;
+
+[scriptable, uuid(35BE2D7E-F29B-48EC-BF7E-80A30A724DE3)]
 interface nsIContentViewerEdit : nsISupports
 {
 	void clearSelection();
 	void selectAll();
 
 	void copySelection();
 	readonly attribute boolean copyable;
 
@@ -22,9 +24,13 @@ interface nsIContentViewerEdit : nsISupp
 	const long COPY_IMAGE_HTML = 0x0002;
 	const long COPY_IMAGE_DATA = 0x0004;
 	const long COPY_IMAGE_ALL = -1;
 	void copyImage(in long aCopyFlags);
 	readonly attribute boolean inImage;
 
 	AString getContents(in string aMimeType, in boolean aSelectionOnly);
 	readonly attribute boolean canGetContents;
+
+	// Set the node that will be the subject of the editing commands above.
+	// Usually this will be the node that was context-clicked.
+	void setCommandNode(in nsIDOMNode aNode);
 };
--- a/docshell/base/nsILinkHandler.h
+++ b/docshell/base/nsILinkHandler.h
@@ -8,17 +8,16 @@
 
 #include "nsISupports.h"
 #include "mozilla/EventForwards.h"
 
 class nsIContent;
 class nsIDocShell;
 class nsIInputStream;
 class nsIRequest;
-class nsString;
 
 // Interface ID for nsILinkHandler
 #define NS_ILINKHANDLER_IID \
   { 0xceb9aade, 0x43da, 0x4f1a, \
     { 0xac, 0x8a, 0xc7, 0x09, 0xfb, 0x22, 0x46, 0x64 } }
 
 /**
  * Interface used for handling clicks on links
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -286,16 +286,17 @@
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
         <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
+        <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -312,16 +313,17 @@
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
         <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
+        <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -213,16 +213,42 @@ Animation::GetFinished(ErrorResult& aRv)
     aRv.Throw(NS_ERROR_FAILURE);
   } else if (IsFinished()) {
     mFinished->MaybeResolve(this);
   }
   return mFinished;
 }
 
 void
+Animation::Finish(ErrorResult& aRv)
+{
+  // https://w3c.github.io/web-animations/#finish-an-animation
+
+  if (mPlaybackRate == 0 ||
+      (mPlaybackRate > 0 && EffectEnd() == TimeDuration::Forever())) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return;
+  }
+
+  TimeDuration limit =
+    mPlaybackRate > 0 ? TimeDuration(EffectEnd()) : TimeDuration(0);
+
+  SetCurrentTime(limit);
+
+  if (mPendingState == PendingState::PlayPending) {
+    CancelPendingTasks();
+    if (mReady) {
+      mReady->MaybeResolve(this);
+    }
+  }
+  UpdateFinishedState(true);
+  PostUpdate();
+}
+
+void
 Animation::Play(LimitBehavior aLimitBehavior)
 {
   DoPlay(aLimitBehavior);
   PostUpdate();
 }
 
 void
 Animation::Pause()
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -89,16 +89,17 @@ public:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SetCurrentTime(const TimeDuration& aNewCurrentTime);
   double PlaybackRate() const { return mPlaybackRate; }
   void SetPlaybackRate(double aPlaybackRate);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   AnimationPlayState PlayState() const;
   virtual Promise* GetReady(ErrorResult& aRv);
   virtual Promise* GetFinished(ErrorResult& aRv);
+  virtual void Finish(ErrorResult& aRv);
   virtual void Play(LimitBehavior aLimitBehavior);
   virtual void Pause();
   bool IsRunningOnCompositor() const { return mIsRunningOnCompositor; }
 
   // Wrapper functions for Animation DOM methods when called
   // from script. We often use the same methods internally and from
   // script but when called from script we (or one of our subclasses) perform
   // extra steps such as flushing style or converting the return type.
--- a/dom/animation/test/css-animations/test_animation-currenttime.html
+++ b/dom/animation/test/css-animations/test_animation-currenttime.html
@@ -78,122 +78,16 @@ function currentTimeForEndOfActiveInterv
 // that. If that's happening then there are likely other issues that should be
 // fixed, so a failure to make us look into that seems like a good thing.
 const UNANIMATED_POSITION = 10;
 const INITIAL_POSITION = 100;
 const TEN_PCT_POSITION = 110;
 const FIFTY_PCT_POSITION = 150;
 const END_POSITION = 200;
 
-/**
- * CSS animation events fire asynchronously after we set 'startTime'. This
- * helper class allows us to handle such events using Promises.
- *
- * To use this class:
- *
- *   var eventWatcher = new EventWatcher(watchedNode, eventTypes);
- *   eventWatcher.waitForEvent(eventType).then(function() {
- *     // Promise fulfilled
- *     checkStuff();
- *     makeSomeChanges();
- *     return eventWatcher.waitForEvent(nextEventType);
- *   }).then(function() {
- *     // Promise fulfilled
- *     checkMoreStuff();
- *     eventWatcher.stopWatching(); // all done - stop listening for events
- *   });
- *
- * This class will assert_unreached() if an event occurs when there is no
- * Promise created by a waitForEvent() call waiting to be fulfilled, or if the
- * event is of a different type to the type passed to waitForEvent. This helps
- * provide test coverage to ensure that only events that are expected occur, in
- * the correct order and with the correct timing. It also helps vastly simplify
- * the already complex code below by avoiding lots of gnarly error handling
- * code.
- */
-function EventWatcher(watchedNode, eventTypes)
-{
-  if (typeof eventTypes == 'string') {
-    eventTypes = [eventTypes];
-  }
-
-  var waitingFor = null;
-
-  function eventHandler(evt) {
-    if (!waitingFor) {
-      assert_unreached('Not expecting event, but got: ' + evt.type +
-        ' targeting element #' + evt.target.getAttribute('id'));
-      return;
-    }
-    if (evt.type != waitingFor.types[0]) {
-      assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
-                        evt.type + ' event');
-      return;
-    }
-    if (waitingFor.types.length > 1) {
-      // Pop first event from array
-      waitingFor.types.shift();
-      return;
-    }
-    // We need to null out waitingFor before calling the resolve function since
-    // the Promise's resolve handlers may call waitForEvent() which will need
-    // to set waitingFor.
-    var resolveFunc = waitingFor.resolve;
-    waitingFor = null;
-    resolveFunc(evt);
-  }
-
-  for (var i = 0; i < eventTypes.length; i++) {
-    watchedNode.addEventListener(eventTypes[i], eventHandler);
-  }
-
-  this.waitForEvent = function(type) {
-    if (typeof type != 'string') {
-      return Promise.reject('Event type not a string');
-    }
-    return this.waitForEvents([type]);
-  };
-
-  /**
-   * This is useful when two events are expected to fire one immediately after
-   * the other. This happens when we skip over the entire active interval for
-   * instance. In this case an 'animationstart' and an 'animationend' are fired
-   * and due to the asynchronous nature of Promise callbacks this won't work:
-   *
-   *   eventWatcher.waitForEvent('animationstart').then(function() {
-   *     return waitForEvent('animationend');
-   *   }).then(...);
-   *
-   * It doesn't work because the 'animationend' listener is added too late,
-   * because the resolve handler for the first Promise is called asynchronously
-   * some time after the 'animationstart' event is called, rather than at the
-   * time the event reaches the watched element.
-   */
-  this.waitForEvents = function(types) {
-    if (waitingFor) {
-      return Promise.reject('Already waiting for an event');
-    }
-    return new Promise(function(resolve, reject) {
-      waitingFor = {
-        types: types,
-        resolve: resolve,
-        reject: reject
-      };
-    });
-  };
-
-  this.stopWatching = function() {
-    for (var i = 0; i < eventTypes.length; i++) {
-      watchedNode.removeEventListener(eventTypes[i], eventHandler);
-    }
-  };
-
-  return this;
-}
-
 // The terms used for the naming of the following helper functions refer to
 // terms used in the Web Animations specification for specific phases of an
 // animation. The terms can be found here:
 //
 //   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
 //
 // Note the distinction between the "animation start time" which occurs before
 // the start delay and the start of the active interval which occurs after it.
@@ -331,71 +225,69 @@ test(function(t)
 
   checkStateOnSettingCurrentTimeToZero(animation);
 }, 'Sanity test to check round-tripping assigning to new animation\'s ' +
    'currentTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
 
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
 
   animation.ready.then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
 
     animation.currentTime =
       currentTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.waitForEvent('animationstart');
+    return eventWatcher.wait_for('animationstart');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.currentTime =
       currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
     checkStateAtFiftyPctOfActiveInterval(animation);
 
     animation.currentTime =
       currentTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.waitForEvent('animationend');
+    return eventWatcher.wait_for('animationend');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalEndTime(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Skipping forward through animation');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
 
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
 
   // So that animation is running instead of paused when we set currentTime:
   animation.startTime = animation.timeline.currentTime;
 
   animation.currentTime = currentTimeForEndOfActiveInterval(animation.timeline);
 
   var previousTimelineTime = animation.timeline.currentTime;
 
   // Skipping over the active interval will dispatch an 'animationstart' then
   // an 'animationend' event. We need to wait for these events before we start
   // testing going backwards since EventWatcher will fail the test if it gets
   // an event that we haven't told it about.
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(t.step_func(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(t.step_func(function() {
     assert_true(document.timeline.currentTime - previousTimelineTime <
                   ANIM_DUR_MS,
                 'Sanity check that seeking worked rather than the events ' +
                 'firing after normal playback through the very long ' +
                 'animation duration');
 
     // Now we can start the tests for skipping backwards, but first we check
     // that after the events we're still in the same end time state:
@@ -405,35 +297,33 @@ async_test(function(t) {
       currentTimeForFiftyPercentThroughActiveInterval(animation.timeline);
 
     // Despite going backwards from after the end of the animation (to being
     // in the active interval), we now expect an 'animationstart' event
     // because the animation should go from being inactive to active.
     //
     // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
     // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call waitForEvent first
+    // be dispatched synchronously. We need to call wait_for first
     // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.waitForEvent('animationstart');
+    var promise = eventWatcher.wait_for('animationstart');
     checkStateAtFiftyPctOfActiveInterval(animation);
     return promise;
   })).then(t.step_func(function() {
     animation.currentTime =
       currentTimeForStartOfActiveInterval(animation.timeline);
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.currentTime = 0;
     // Despite going backwards from just after the active interval starts to
     // the animation start time, we now expect an animationend event
     // because we went from inside to outside the active interval.
-    return eventWatcher.waitForEvent('animationend');
+    return eventWatcher.wait_for('animationend');
   })).then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 
   // This must come after we've set up the Promise chain, since requesting
   // computed style will force events to be dispatched.
@@ -449,115 +339,109 @@ async_test(function(t) {
 // without waiting an infinitely long time. Obviously we don't want to do that
 // (block this test from finishing forever), so instead we just listen for
 // events until two animation frames (i.e. requestAnimationFrame callbacks)
 // have happened, then assume that no events will ever be dispatched for the
 // redundant changes if no events were detected in that time.
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
   animation.currentTime = currentTimeForBeforePhase(animation.timeline);
 
   waitForAnimationFrames(2).then(function() {
-    eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> active, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
   animation.currentTime = currentTimeForBeforePhase(animation.timeline);
 
   waitForAnimationFrames(2).then(function() {
-    eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvent('animationstart').then(function() {
+  eventWatcher.wait_for('animationstart').then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> before, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvent('animationstart').then(function() {
+  eventWatcher.wait_for('animationstart').then(function() {
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(function() {
     animation.currentTime = currentTimeForBeforePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> before, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(function() {
     animation.currentTime = currentTimeForActivePhase(animation.timeline);
     animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.currentTime = currentTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> active, then back');
 
 
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-animations/test_animation-finish.html
@@ -0,0 +1,160 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<div id="log"></div>
+<style>
+
+.animated-div {
+  margin-left: 10px;
+}
+
+@keyframes anim {
+  from { margin-left: 100px; }
+  to { margin-left: 200px; }
+}
+
+</style>
+<script>
+
+'use strict';
+
+const ANIM_PROP_VAL = 'anim 100s';
+const ANIM_DURATION = 100000; // ms
+
+test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.playbackRate = 0;
+
+  var threw = false;
+  try {
+    animation.finish();
+  } catch (e) {
+    threw = true;
+    assert_equals(e.name, 'InvalidStateError',
+                  'Exception should be an InvalidStateError exception when ' +
+                  'trying to finish an animation with playbackRate == 0');
+  }
+  assert_true(threw,
+              'Expect InvalidStateError exception trying to finish an ' +
+              'animation with playbackRate == 0');
+}, 'Test exceptions when finishing non-running animation');
+
+test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  div.style.animationIterationCount = 'infinite';
+  var animation = div.getAnimations()[0];
+
+  var threw = false;
+  try {
+    animation.finish();
+  } catch (e) {
+    threw = true;
+    assert_equals(e.name, 'InvalidStateError',
+                  'Exception should be an InvalidStateError exception when ' +
+                  'trying to finish an infinite animation');
+  }
+  assert_true(threw,
+              'Expect InvalidStateError exception trying to finish an ' +
+              'infinite animation');
+}, 'Test exceptions when finishing infinite animation');
+
+test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.finish();
+  assert_equals(animation.currentTime, ANIM_DURATION,
+                'After finishing, the currentTime should be set to the end ' +
+                'of the active duration');
+}, 'Test finishing of animation');
+
+test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.currentTime = ANIM_DURATION + 1000; // 1s past effect end
+
+  animation.finish();
+  assert_equals(animation.currentTime, ANIM_DURATION,
+                'After finishing, the currentTime should be set back to the ' +
+                'end of the active duration');
+}, 'Test finishing of animation with a current time past the effect end');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.currentTime = ANIM_DURATION;
+
+  animation.finished.then(t.step_func(function() {
+    animation.playbackRate = -1;
+    animation.finish();
+    assert_equals(animation.currentTime, 0,
+                  'After finishing a reversed animation the currentTime ' +
+                  'should be set to zero');
+    t.done();
+  }));
+}, 'Test finishing of reversed animation');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.currentTime = ANIM_DURATION;
+
+  animation.finished.then(t.step_func(function() {
+    animation.playbackRate = -1;
+
+    animation.currentTime = -1000;
+
+    animation.finish();
+    assert_equals(animation.currentTime, 0,
+                  'After finishing a reversed animation the currentTime ' +
+                  'should be set back to zero');
+    t.done();
+  }));
+}, 'Test finishing of reversed animation with with a current time less ' +
+   'than zero');
+
+async_test(function(t) {
+  var div = addDiv(t);
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.pause();
+
+  animation.ready.then(t.step_func(function() {
+    animation.finish();
+    assert_equals(animation.playState, 'paused',
+                  'The play state of a paused animation should remain ' +
+                  '"paused" even after finish() is called');
+    t.done();
+  }));
+}, 'Test paused state after finishing of animation');
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  div.style.animation = ANIM_PROP_VAL;
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(t.step_func(function() {
+    animation.finish();
+    var marginLeft = parseFloat(getComputedStyle(div).marginLeft);
+    assert_equals(marginLeft, 10,
+                  'The computed style should be reset when finish() is ' +
+                  'called');
+    t.done();
+  }));
+}, 'Test resetting of computed style');
+
+</script>
--- a/dom/animation/test/css-animations/test_animation-finished.html
+++ b/dom/animation/test/css-animations/test_animation-finished.html
@@ -72,25 +72,25 @@ async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = ANIM_PROP_VAL;
   var animation = div.getAnimations()[0];
 
   var previousFinishedPromise;
 
   animation.currentTime = ANIM_DURATION;
 
-  animation.finished.then(function() {
+  animation.finished.then(t.step_func(function() {
     previousFinishedPromise = animation.finished;
     animation.playbackRate = -1;
     assert_not_equals(animation.finished, previousFinishedPromise,
                       'Finished promise should be replaced when reversing a ' +
                       'finished promise');
     animation.currentTime = 0;
     return animation.finished;
-  }).then(t.step_func(function() {
+  })).then(t.step_func(function() {
     previousFinishedPromise = animation.finished;
     animation.play();
     assert_not_equals(animation.finished, previousFinishedPromise,
                       'Finished promise is replaced after play() call on ' +
                       'finished, reversed animation');
     t.done();
   }));
 }, 'Test restarting a reversed finished animation');
--- a/dom/animation/test/css-animations/test_animation-ready.html
+++ b/dom/animation/test/css-animations/test_animation-ready.html
@@ -39,51 +39,51 @@ async_test(function(t) {
 }, 'A new ready promise is created when play()/pause() is called');
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s paused';
   var animation = div.getAnimations()[0];
 
   var originalReadyPromise = animation.ready;
-  animation.ready.then(function() {
+  animation.ready.then(t.step_func(function() {
     div.style.animationPlayState = 'running';
     assert_not_equals(animation.ready, originalReadyPromise,
                       'After updating animation-play-state a new ready promise'
                       + ' object is created');
     t.done();
-  });
+  }));
 }, 'A new ready promise is created when setting animation-play-state: running');
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
-  animation.ready.then(function() {
+  animation.ready.then(t.step_func(function() {
     var promiseBeforeCallingPlay = animation.ready;
     animation.play();
     assert_equals(animation.ready, promiseBeforeCallingPlay,
                   'Ready promise has same object identity after redundant call'
                   + ' to play()');
     t.done();
-  });
+  }));
 }, 'Redundant calls to play() do not generate new ready promise objects');
 
 async_test(function(t) {
   var div = addDiv(t);
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
 
-  animation.ready.then(function(resolvedAnimation) {
+  animation.ready.then(t.step_func(function(resolvedAnimation) {
     assert_equals(resolvedAnimation, animation,
                   'Object identity of Animation passed to Promise callback'
                   + ' matches the Animation object owning the Promise');
     t.done();
-  });
+  }));
 }, 'The ready promise is fulfilled with its Animation');
 
 async_test(function(t) {
   var div = addDiv(t);
 
   // Set up pending animation
   div.style.animation = 'abc 100s';
   var animation = div.getAnimations()[0];
--- a/dom/animation/test/css-animations/test_animation-starttime.html
+++ b/dom/animation/test/css-animations/test_animation-starttime.html
@@ -78,122 +78,16 @@ function startTimeForEndOfActiveInterval
 // that. If that's happening then there are likely other issues that should be
 // fixed, so a failure to make us look into that seems like a good thing.
 const UNANIMATED_POSITION = 10;
 const INITIAL_POSITION = 100;
 const TEN_PCT_POSITION = 110;
 const FIFTY_PCT_POSITION = 150;
 const END_POSITION = 200;
 
-/**
- * CSS animation events fire asynchronously after we set 'startTime'. This
- * helper class allows us to handle such events using Promises.
- *
- * To use this class:
- *
- *   var eventWatcher = new EventWatcher(watchedNode, eventTypes);
- *   eventWatcher.waitForEvent(eventType).then(function() {
- *     // Promise fulfilled
- *     checkStuff();
- *     makeSomeChanges();
- *     return eventWatcher.waitForEvent(nextEventType);
- *   }).then(function() {
- *     // Promise fulfilled
- *     checkMoreStuff();
- *     eventWatcher.stopWatching(); // all done - stop listening for events
- *   });
- *
- * This class will assert_unreached() if an event occurs when there is no
- * Promise created by a waitForEvent() call waiting to be fulfilled, or if the
- * event is of a different type to the type passed to waitForEvent. This helps
- * provide test coverage to ensure that only events that are expected occur, in
- * the correct order and with the correct timing. It also helps vastly simplify
- * the already complex code below by avoiding lots of gnarly error handling
- * code.
- */
-function EventWatcher(watchedNode, eventTypes)
-{
-  if (typeof eventTypes == 'string') {
-    eventTypes = [eventTypes];
-  }
-
-  var waitingFor = null;
-
-  function eventHandler(evt) {
-    if (!waitingFor) {
-      assert_unreached('Not expecting event, but got: ' + evt.type +
-        ' targeting element #' + evt.target.getAttribute('id'));
-      return;
-    }
-    if (evt.type != waitingFor.types[0]) {
-      assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
-                        evt.type + ' event');
-      return;
-    }
-    if (waitingFor.types.length > 1) {
-      // Pop first event from array
-      waitingFor.types.shift();
-      return;
-    }
-    // We need to null out waitingFor before calling the resolve function since
-    // the Promise's resolve handlers may call waitForEvent() which will need
-    // to set waitingFor.
-    var resolveFunc = waitingFor.resolve;
-    waitingFor = null;
-    resolveFunc(evt);
-  }
-
-  for (var i = 0; i < eventTypes.length; i++) {
-    watchedNode.addEventListener(eventTypes[i], eventHandler);
-  }
-
-  this.waitForEvent = function(type) {
-    if (typeof type != 'string') {
-      return Promise.reject('Event type not a string');
-    }
-    return this.waitForEvents([type]);
-  };
-
-  /**
-   * This is useful when two events are expected to fire one immediately after
-   * the other. This happens when we skip over the entire active interval for
-   * instance. In this case an 'animationstart' and an 'animationend' are fired
-   * and due to the asynchronous nature of Promise callbacks this won't work:
-   *
-   *   eventWatcher.waitForEvent('animationstart').then(function() {
-   *     return waitForEvent('animationend');
-   *   }).then(...);
-   *
-   * It doesn't work because the 'animationend' listener is added too late,
-   * because the resolve handler for the first Promise is called asynchronously
-   * some time after the 'animationstart' event is called, rather than at the
-   * time the event reaches the watched element.
-   */
-  this.waitForEvents = function(types) {
-    if (waitingFor) {
-      return Promise.reject('Already waiting for an event');
-    }
-    return new Promise(function(resolve, reject) {
-      waitingFor = {
-        types: types,
-        resolve: resolve,
-        reject: reject
-      };
-    });
-  };
-
-  this.stopWatching = function() {
-    for (var i = 0; i < eventTypes.length; i++) {
-      watchedNode.removeEventListener(eventTypes[i], eventHandler);
-    }
-  };
-
-  return this;
-}
-
 // The terms used for the naming of the following helper functions refer to
 // terms used in the Web Animations specification for specific phases of an
 // animation. The terms can be found here:
 //
 //   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
 //
 // Note the distinction between the "animation start time" which occurs before
 // the start delay and the start of the active interval which occurs after it.
@@ -385,66 +279,64 @@ test(function(t)
   assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
     'Check setting of startTime actually works');
 }, 'Sanity test to check round-tripping assigning to a new animation\'s ' +
    'startTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
 
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
 
   animation.ready.then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
 
     animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
-    return eventWatcher.waitForEvent('animationstart');
+    return eventWatcher.wait_for('animationstart');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.startTime =
       startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
     checkStateAtFiftyPctOfActiveInterval(animation);
 
     animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.waitForEvent('animationend');
+    return eventWatcher.wait_for('animationend');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalEndTime(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Skipping forward through animation');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
 
   div.style.animation = ANIM_PROPERTY_VAL;
 
   var animation = div.getAnimations()[0];
 
   animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
 
   var previousTimelineTime = animation.timeline.currentTime;
 
   // Skipping over the active interval will dispatch an 'animationstart' then
   // an 'animationend' event. We need to wait for these events before we start
   // testing going backwards since EventWatcher will fail the test if it gets
   // an event that we haven't told it about.
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(t.step_func(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(t.step_func(function() {
     assert_true(document.timeline.currentTime - previousTimelineTime <
                   ANIM_DUR_MS,
                 'Sanity check that seeking worked rather than the events ' +
                 'firing after normal playback through the very long ' +
                 'animation duration');
 
     // Now we can start the tests for skipping backwards, but first we check
     // that after the events we're still in the same end time state:
@@ -454,34 +346,32 @@ async_test(function(t) {
       startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
 
     // Despite going backwards from after the end of the animation (to being
     // in the active interval), we now expect an 'animationstart' event
     // because the animation should go from being inactive to active.
     //
     // Calling checkStateAtFiftyPctOfActiveInterval will check computed style,
     // causing computed style to be updated and the 'animationstart' event to
-    // be dispatched synchronously. We need to call waitForEvent first
+    // be dispatched synchronously. We need to call wait_for first
     // otherwise eventWatcher will assert that the event was unexpected.
-    var promise = eventWatcher.waitForEvent('animationstart');
+    var promise = eventWatcher.wait_for('animationstart');
     checkStateAtFiftyPctOfActiveInterval(animation);
     return promise;
   })).then(t.step_func(function() {
     animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.startTime = animation.timeline.currentTime;
     // Despite going backwards from just after the active interval starts to
     // the animation start time, we now expect an animationend event
     // because we went from inside to outside the active interval.
-    return eventWatcher.waitForEvent('animationend');
+    return eventWatcher.wait_for('animationend');
   })).then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 
   // This must come after we've set up the Promise chain, since requesting
   // computed style will force events to be dispatched.
@@ -497,115 +387,109 @@ async_test(function(t) {
 // without waiting an infinitely long time. Obviously we don't want to do that
 // (block this test from finishing forever), so instead we just listen for
 // events until two animation frames (i.e. requestAnimationFrame callbacks)
 // have happened, then assume that no events will ever be dispatched for the
 // redundant changes if no events were detected in that time.
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.startTime = startTimeForActivePhase(animation.timeline);
   animation.startTime = startTimeForBeforePhase(animation.timeline);
 
   waitForAnimationFrames(2).then(function() {
-    eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> active, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
   animation.startTime = startTimeForAfterPhase(animation.timeline);
   animation.startTime = startTimeForBeforePhase(animation.timeline);
 
   waitForAnimationFrames(2).then(function() {
-    eventWatcher.stopWatching();
     t.done();
   });
 }, 'Redundant change, before -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvent('animationstart').then(function() {
+  eventWatcher.wait_for('animationstart').then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> before, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvent('animationstart').then(function() {
+  eventWatcher.wait_for('animationstart').then(function() {
     animation.startTime = startTimeForAfterPhase(animation.timeline);
     animation.startTime = startTimeForActivePhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForActivePhase(animation.timeline);
 }, 'Redundant change, active -> after, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(function() {
     animation.startTime = startTimeForBeforePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> before, then back');
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, CSS_ANIM_EVENTS);
+  var eventWatcher = new EventWatcher(t, div, CSS_ANIM_EVENTS);
   div.style.animation = ANIM_PROPERTY_VAL;
   var animation = div.getAnimations()[0];
 
-  eventWatcher.waitForEvents(['animationstart',
-                              'animationend']).then(function() {
+  eventWatcher.wait_for(['animationstart',
+                         'animationend']).then(function() {
     animation.startTime = startTimeForActivePhase(animation.timeline);
     animation.startTime = startTimeForAfterPhase(animation.timeline);
 
     waitForAnimationFrames(2).then(function() {
-      eventWatcher.stopWatching();
       t.done();
     });
   });
   // get us into the initial state:
   animation.startTime = startTimeForAfterPhase(animation.timeline);
 }, 'Redundant change, after -> active, then back');
 
 
@@ -618,21 +502,21 @@ async_test(function(t) {
   var storedCurrentTime;
 
   animation.ready.then(t.step_func(function() {
     storedCurrentTime = animation.currentTime;
     animation.startTime = null;
     return animation.ready;
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
-  })).then(function() {
+  })).then(t.step_func(function() {
     assert_equals(animation.currentTime, storedCurrentTime,
       'Test that hold time is correct');
     t.done();
-  });
+  }));
 }, 'Setting startTime to null');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
   div.style.animation = 'anim 100s';
 
   var animation = div.getAnimations()[0];
--- a/dom/animation/test/css-transitions/test_animation-currenttime.html
+++ b/dom/animation/test/css-transitions/test_animation-currenttime.html
@@ -68,121 +68,16 @@ function currentTimeForEndOfActiveInterv
 // active duration is 1000s long, a delay would need to be around 100s to cause
 // that. If that's happening then there are likely other issues that should be
 // fixed, so a failure to make us look into that seems like a good thing.
 const INITIAL_POSITION = 100;
 const TEN_PCT_POSITION = 110;
 const FIFTY_PCT_POSITION = 150;
 const END_POSITION = 200;
 
-/**
- * CSS animation events fire asynchronously after we set 'startTime'. This
- * helper class allows us to handle such events using Promises.
- *
- * To use this class:
- *
- *   var eventWatcher = new EventWatcher(watchedNode, eventTypes);
- *   eventWatcher.waitForEvent(eventType).then(function() {
- *     // Promise fulfilled
- *     checkStuff();
- *     makeSomeChanges();
- *     return eventWatcher.waitForEvent(nextEventType);
- *   }).then(function() {
- *     // Promise fulfilled
- *     checkMoreStuff();
- *     eventWatcher.stopWatching(); // all done - stop listening for events
- *   });
- *
- * This class will assert_unreached() if an event occurs when there is no
- * Promise created by a waitForEvent() call waiting to be fulfilled, or if the
- * event is of a different type to the type passed to waitForEvent. This helps
- * provide test coverage to ensure that only events that are expected occur, in
- * the correct order and with the correct timing. It also helps vastly simplify
- * the already complex code below by avoiding lots of gnarly error handling
- * code.
- */
-function EventWatcher(watchedNode, eventTypes)
-{
-  if (typeof eventTypes == 'string') {
-    eventTypes = [eventTypes];
-  }
-
-  var waitingFor = null;
-
-  function eventHandler(evt) {
-    if (!waitingFor) {
-      assert_unreached('Not expecting event, but got: ' + evt.type +
-        ' targeting element #' + evt.target.getAttribute('id'));
-      return;
-    }
-    if (evt.type != waitingFor.types[0]) {
-      assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
-                        evt.type + ' event');
-      return;
-    }
-    if (waitingFor.types.length > 1) {
-      // Pop first event from array
-      waitingFor.types.shift();
-      return;
-    }
-    // We need to null out waitingFor before calling the resolve function since
-    // the Promise's resolve handlers may call waitForEvent() which will need
-    // to set waitingFor.
-    var resolveFunc = waitingFor.resolve;
-    waitingFor = null;
-    resolveFunc(evt);
-  }
-
-  for (var i = 0; i < eventTypes.length; i++) {
-    watchedNode.addEventListener(eventTypes[i], eventHandler);
-  }
-
-  this.waitForEvent = function(type) {
-    if (typeof type != 'string') {
-      return Promise.reject('Event type not a string');
-    }
-    return this.waitForEvents([type]);
-  };
-
-  /**
-   * This is useful when two events are expected to fire one immediately after
-   * the other. This happens when we skip over the entire active interval for
-   * instance. In this case an 'animationstart' and an 'animationend' are fired
-   * and due to the asynchronous nature of Promise callbacks this won't work:
-   *
-   *   eventWatcher.waitForEvent('animationstart').then(function() {
-   *     return waitForEvent('animationend');
-   *   }).then(...);
-   *
-   * It doesn't work because the 'animationend' listener is added too late,
-   * because the resolve handler for the first Promise is called asynchronously
-   * some time after the 'animationstart' event is called, rather than at the
-   * time the event reaches the watched element.
-   */
-  this.waitForEvents = function(types) {
-    if (waitingFor) {
-      return Promise.reject('Already waiting for an event');
-    }
-    return new Promise(function(resolve, reject) {
-      waitingFor = {
-        types: types,
-        resolve: resolve,
-        reject: reject
-      };
-    });
-  };
-
-  this.stopWatching = function() {
-    for (var i = 0; i < eventTypes.length; i++) {
-      watchedNode.removeEventListener(eventTypes[i], eventHandler);
-    }
-  };
-
-  return this;
-}
 
 // The terms used for the naming of the following helper functions refer to
 // terms used in the Web Animations specification for specific phases of an
 // animation. The terms can be found here:
 //
 //   http://w3c.github.io/web-animations/#animation-effect-phases-and-states
 
 // Called when currentTime is set to zero (the beginning of the start delay).
@@ -295,49 +190,47 @@ test(function(t)
   assert_equals(animation.currentTime, 10,
     'Check setting of currentTime actually works');
 }, 'Sanity test to check round-tripping assigning to new animation\'s ' +
    'currentTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, 'transitionend');
+  var eventWatcher = new EventWatcher(t, div, 'transitionend');
 
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
 
   var animation = div.getAnimations()[0];
 
   animation.ready.then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
 
     animation.currentTime = currentTimeForStartOfActiveInterval();
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.currentTime = currentTimeForFiftyPercentThroughActiveInterval();
     checkStateAtFiftyPctOfActiveInterval(animation);
 
     animation.currentTime = currentTimeForEndOfActiveInterval();
-    return eventWatcher.waitForEvent('transitionend');
+    return eventWatcher.wait_for('transitionend');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalEndTime(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Skipping forward through transition');
 
 
 test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, 'transitionend');
+  var eventWatcher = new EventWatcher(t, div, 'transitionend');
 
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
 
   var animation = div.getAnimations()[0];
 
   // Unlike in the case of CSS animations, we cannot skip to the end and skip
   // backwards since when we reach the end the transition effect is removed and
@@ -351,20 +244,19 @@ test(function(t) {
     animation.currentTime = currentTimeForStartOfActiveInterval();
 
     // Despite going backwards from being in the active interval to being
     // before it, we now expect a 'transitionend' event because the transition
     // should go from being active to inactive.
     //
     // Calling checkStateAtActiveIntervalStartTime will check computed style,
     // causing computed style to be updated and the 'transitionend' event to
-    // be dispatched synchronously. We need to call waitForEvent first
+    // be dispatched synchronously. We need to call wait_for first
     // otherwise eventWatcher will assert that the event was unexpected.
-    eventWatcher.waitForEvent('transitionend').then(function() {
-      eventWatcher.stopWatching();
+    eventWatcher.wait_for('transitionend').then(function() {
       t.done();
     });
     checkStateAtActiveIntervalStartTime(animation);
   }));
 }, 'Skipping backwards through transition');
 
 
 async_test(function(t) {
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/css-transitions/test_animation-finished.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<meta charset=utf-8>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="../testcommon.js"></script>
+<div id="log"></div>
+<style>
+
+.animated-div {
+  margin-left: 100px;
+  transition: margin-left 1000s linear 1000s;
+}
+
+</style>
+<script>
+
+'use strict';
+
+const ANIM_DELAY_MS = 1000000; // 1000s
+const ANIM_DUR_MS = 1000000; // 1000s
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  animation.finish();
+
+  animation.finished.then(t.step_func(function() {
+    animation.play();
+    assert_equals(animation.currentTime, 0,
+                  'Replaying a finished transition should reset its ' +
+                  'currentTime');
+    t.done();
+  }));
+}, 'Test restarting a finished transition');
+
+async_test(function(t) {
+  var div = addDiv(t, {'class': 'animated-div'});
+  flushComputedStyle(div);
+  div.style.marginLeft = '200px'; // initiate transition
+
+  var animation = div.getAnimations()[0];
+
+  animation.ready.then(function() {
+    animation.playbackRate = -1;
+    return animation.finished;
+  }).then(t.step_func(function() {
+    animation.play();
+    // FIXME: once animation.effect.computedTiming.endTime is available (bug
+    // 1108055) we should use that here.
+    assert_equals(animation.currentTime, ANIM_DELAY_MS + ANIM_DUR_MS,
+                  'Replaying a finished reversed transition should reset ' +
+                  'its currentTime to the end of the effect');
+    t.done();
+  }));
+}, 'Test restarting a reversed finished transition');
+
+</script>
--- a/dom/animation/test/css-transitions/test_animation-starttime.html
+++ b/dom/animation/test/css-transitions/test_animation-starttime.html
@@ -68,122 +68,16 @@ function startTimeForEndOfActiveInterval
 // active duration is 1000s long, a delay would need to be around 100s to cause
 // that. If that's happening then there are likely other issues that should be
 // fixed, so a failure to make us look into that seems like a good thing.
 const INITIAL_POSITION = 100;
 const TEN_PCT_POSITION = 110;
 const FIFTY_PCT_POSITION = 150;
 const END_POSITION = 200;
 
-/**
- * CSS animation events fire asynchronously after we set 'startTime'. This
- * helper class allows us to handle such events using Promises.
- *
- * To use this class:
- *
- *   var eventWatcher = new EventWatcher(watchedNode, eventTypes);
- *   eventWatcher.waitForEvent(eventType).then(function() {
- *     // Promise fulfilled
- *     checkStuff();
- *     makeSomeChanges();
- *     return eventWatcher.waitForEvent(nextEventType);
- *   }).then(function() {
- *     // Promise fulfilled
- *     checkMoreStuff();
- *     eventWatcher.stopWatching(); // all done - stop listening for events
- *   });
- *
- * This class will assert_unreached() if an event occurs when there is no
- * Promise created by a waitForEvent() call waiting to be fulfilled, or if the
- * event is of a different type to the type passed to waitForEvent. This helps
- * provide test coverage to ensure that only events that are expected occur, in
- * the correct order and with the correct timing. It also helps vastly simplify
- * the already complex code below by avoiding lots of gnarly error handling
- * code.
- */
-function EventWatcher(watchedNode, eventTypes)
-{
-  if (typeof eventTypes == 'string') {
-    eventTypes = [eventTypes];
-  }
-
-  var waitingFor = null;
-
-  function eventHandler(evt) {
-    if (!waitingFor) {
-      assert_unreached('Not expecting event, but got: ' + evt.type +
-        ' targeting element #' + evt.target.getAttribute('id'));
-      return;
-    }
-    if (evt.type != waitingFor.types[0]) {
-      assert_unreached('Expected ' + waitingFor.types[0] + ' event but got ' +
-                        evt.type + ' event');
-      return;
-    }
-    if (waitingFor.types.length > 1) {
-      // Pop first event from array
-      waitingFor.types.shift();
-      return;
-    }
-    // We need to null out waitingFor before calling the resolve function since
-    // the Promise's resolve handlers may call waitForEvent() which will need
-    // to set waitingFor.
-    var resolveFunc = waitingFor.resolve;
-    waitingFor = null;
-    resolveFunc(evt);
-  }
-
-  for (var i = 0; i < eventTypes.length; i++) {
-    watchedNode.addEventListener(eventTypes[i], eventHandler);
-  }
-
-  this.waitForEvent = function(type) {
-    if (typeof type != 'string') {
-      return Promise.reject('Event type not a string');
-    }
-    return this.waitForEvents([type]);
-  };
-
-  /**
-   * This is useful when two events are expected to fire one immediately after
-   * the other. This happens when we skip over the entire active interval for
-   * instance. In this case an 'animationstart' and an 'animationend' are fired
-   * and due to the asynchronous nature of Promise callbacks this won't work:
-   *
-   *   eventWatcher.waitForEvent('animationstart').then(function() {
-   *     return waitForEvent('animationend');
-   *   }).then(...);
-   *
-   * It doesn't work because the 'animationend' listener is added too late,
-   * because the resolve handler for the first Promise is called asynchronously
-   * some time after the 'animationstart' event is called, rather than at the
-   * time the event reaches the watched element.
-   */
-  this.waitForEvents = function(types) {
-    if (waitingFor) {
-      return Promise.reject('Already waiting for an event');
-    }
-    return new Promise(function(resolve, reject) {
-      waitingFor = {
-        types: types,
-        resolve: resolve,
-        reject: reject
-      };
-    });
-  };
-
-  this.stopWatching = function() {
-    for (var i = 0; i < eventTypes.length; i++) {
-      watchedNode.removeEventListener(eventTypes[i], eventHandler);
-    }
-  };
-
-  return this;
-}
-
 // The terms used for the naming of the following helper functions refer to
 // terms used in the Web Animations specification for specific phases of an
 // animation. The terms can be found here:
 //
 //   https://w3c.github.io/web-animations/#animation-effect-phases-and-states
 //
 // Note the distinction between the "animation start time" which occurs before
 // the start delay and the start of the active interval which occurs after it.
@@ -272,17 +166,17 @@ test(function(t)
   assert_approx_equals(animation.startTime, currentTime, 0.0001, // rounding error
     'Check setting of startTime actually works');
 }, 'Sanity test to check round-tripping assigning to new animation\'s ' +
    'startTime');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, 'transitionend');
+  var eventWatcher = new EventWatcher(t, div, 'transitionend');
 
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
 
   var animation = div.getAnimations()[0];
 
   animation.ready.then(t.step_func(function() {
     checkStateOnReadyPromiseResolved(animation);
@@ -290,32 +184,30 @@ async_test(function(t) {
     animation.startTime = startTimeForStartOfActiveInterval(animation.timeline);
     checkStateAtActiveIntervalStartTime(animation);
 
     animation.startTime =
       startTimeForFiftyPercentThroughActiveInterval(animation.timeline);
     checkStateAtFiftyPctOfActiveInterval(animation);
 
     animation.startTime = startTimeForEndOfActiveInterval(animation.timeline);
-    return eventWatcher.waitForEvent('transitionend');
+    return eventWatcher.wait_for('transitionend');
   })).then(t.step_func(function() {
     checkStateAtActiveIntervalEndTime(animation);
-
-    eventWatcher.stopWatching();
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
   })).then(function() {
     t.done();
   });
 }, 'Skipping forward through animation');
 
 
 test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
-  var eventWatcher = new EventWatcher(div, 'transitionend');
+  var eventWatcher = new EventWatcher(t, div, 'transitionend');
 
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
 
   var animation = div.getAnimations()[0];
 
   // Unlike in the case of CSS animations, we cannot skip to the end and skip
   // backwards since when we reach the end the transition effect is removed and
@@ -331,18 +223,17 @@ test(function(t) {
   // Despite going backwards from being in the active interval to being before
   // it, we now expect an 'animationend' event because the animation should go
   // from being active to inactive.
   //
   // Calling checkStateAtActiveIntervalStartTime will check computed style,
   // causing computed style to be updated and the 'transitionend' event to
   // be dispatched synchronously. We need to call waitForEvent first
   // otherwise eventWatcher will assert that the event was unexpected.
-  eventWatcher.waitForEvent('transitionend').then(function() {
-    eventWatcher.stopWatching();
+  eventWatcher.wait_for('transitionend').then(function() {
     t.done();
   });
   checkStateAtActiveIntervalStartTime(animation);
 }, 'Skipping backwards through transition');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
@@ -355,21 +246,21 @@ async_test(function(t) {
   var storedCurrentTime;
 
   animation.ready.then(t.step_func(function() {
     storedCurrentTime = animation.currentTime;
     animation.startTime = null;
     return animation.ready;
   })).catch(t.step_func(function(reason) {
     assert_unreached(reason);
-  })).then(function() {
+  })).then(t.step_func(function() {
     assert_equals(animation.currentTime, storedCurrentTime,
       'Test that hold time is correct');
     t.done();
-  });
+  }));
 }, 'Setting startTime to null');
 
 
 async_test(function(t) {
   var div = addDiv(t, {'class': 'animated-div'});
 
   flushComputedStyle(div);
   div.style.marginLeft = '200px'; // initiate transition
--- a/dom/animation/test/mochitest.ini
+++ b/dom/animation/test/mochitest.ini
@@ -1,25 +1,27 @@
 [DEFAULT]
 support-files =
   testcommon.js
 
 [css-animations/test_animations-dynamic-changes.html]
+[css-animations/test_animation-currenttime.html]
+[css-animations/test_animation-finish.html]
+[css-animations/test_animation-finished.html]
 [css-animations/test_animation-pausing.html]
-[css-animations/test_animation-currenttime.html]
-[css-animations/test_animation-finished.html]
 [css-animations/test_animation-playstate.html]
 [css-animations/test_animation-ready.html]
 [css-animations/test_animation-starttime.html]
 [css-animations/test_effect-name.html]
 [css-animations/test_effect-target.html]
 [css-animations/test_element-get-animations.html]
 skip-if = buildapp == 'mulet'
+[css-transitions/test_animation-currenttime.html]
+[css-transitions/test_animation-finished.html]
 [css-transitions/test_animation-pausing.html]
-[css-transitions/test_animation-currenttime.html]
 [css-transitions/test_animation-ready.html]
 [css-transitions/test_animation-starttime.html]
 [css-transitions/test_effect-name.html]
 [css-transitions/test_effect-target.html]
 [css-transitions/test_element-get-animations.html]
 skip-if = buildapp == 'mulet'
 [document-timeline/test_document-timeline.html]
 [document-timeline/test_request_animation_frame.html]
--- a/dom/base/DirectionalityUtils.h
+++ b/dom/base/DirectionalityUtils.h
@@ -5,18 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef DirectionalityUtils_h___
 #define DirectionalityUtils_h___
 
 #include "nscore.h"
 
 class nsIContent;
-class nsIDocument;
-class nsINode;
 class nsAString;
 class nsAttrValue;
 class nsTextNode;
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
--- a/dom/base/Element.h
+++ b/dom/base/Element.h
@@ -32,26 +32,21 @@
 #include "mozilla/dom/DOMRect.h"
 #include "nsAttrValue.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "Units.h"
 
-class nsIDOMEventListener;
 class nsIFrame;
 class nsIDOMMozNamedAttrMap;
-class nsIDOMCSSStyleDeclaration;
 class nsIURI;
-class nsIControllers;
-class nsEventChainVisitor;
 class nsIScrollableFrame;
 class nsAttrValueOrString;
-class ContentUnbinder;
 class nsContentList;
 class nsDOMSettableTokenList;
 class nsDOMTokenList;
 struct nsRect;
 class nsFocusManager;
 class nsGlobalWindow;
 class nsICSSDeclaration;
 class nsISMILAttr;
--- a/dom/base/File.h
+++ b/dom/base/File.h
@@ -24,20 +24,18 @@
 #include "nsIFile.h"
 #include "nsIMutable.h"
 #include "nsIXMLHttpRequest.h"
 #include "nsString.h"
 #include "nsTemporaryFileInputStream.h"
 #include "nsWrapperCache.h"
 #include "nsWeakReference.h"
 
-class nsDOMMultipartFile;
 class nsIFile;
 class nsIInputStream;
-class nsIClassInfo;
 
 #define FILEIMPL_IID \
   { 0xbccb3275, 0x6778, 0x4ac5, \
     { 0xaf, 0x03, 0x90, 0xed, 0x37, 0xad, 0xdf, 0x5d } }
 
 namespace mozilla {
 namespace dom {
 
--- a/dom/base/ImportManager.h
+++ b/dom/base/ImportManager.h
@@ -44,17 +44,16 @@
 #include "nsIDOMEventListener.h"
 #include "nsIStreamListener.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsRefPtrHashtable.h"
 #include "nsScriptLoader.h"
 #include "nsURIHashKey.h"
 
 class nsIDocument;
-class nsIChannel;
 class nsIPrincipal;
 class nsINode;
 class AutoError;
 
 namespace mozilla {
 namespace dom {
 
 class ImportManager;
--- a/dom/base/ShadowRoot.h
+++ b/dom/base/ShadowRoot.h
@@ -10,20 +10,17 @@
 #include "mozilla/dom/StyleSheetList.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTHashtable.h"
 #include "nsDocument.h"
 
 class nsIAtom;
 class nsIContent;
-class nsIDocument;
-class nsPIDOMWindow;
 class nsXBLPrototypeBinding;
-class nsTagNameMapEntry;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
 class HTMLContentElement;
 class HTMLShadowElement;
 class ShadowRootStyleSheetList;
--- a/dom/base/ThirdPartyUtil.h
+++ b/dom/base/ThirdPartyUtil.h
@@ -7,18 +7,16 @@
 
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsIEffectiveTLDService.h"
 #include "mozilla/Attributes.h"
 
 class nsIURI;
-class nsIChannel;
-class nsIDOMWindow;
 
 class ThirdPartyUtil final : public mozIThirdPartyUtil
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_MOZITHIRDPARTYUTIL
 
   nsresult Init();
--- a/dom/base/URL.h
+++ b/dom/base/URL.h
@@ -6,18 +6,16 @@
 #define URL_h___
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsString.h"
 
-class nsIDOMBlob;
-class nsIPrincipal;
 class nsISupports;
 class nsIURI;
 
 namespace mozilla {
 
 class ErrorResult;
 class DOMMediaStream;
 
--- a/dom/base/WebSocket.h
+++ b/dom/base/WebSocket.h
@@ -23,20 +23,16 @@
 #define DEFAULT_WS_SCHEME_PORT  80
 #define DEFAULT_WSS_SCHEME_PORT 443
 
 class nsIInputStream;
 
 namespace mozilla {
 namespace dom {
 
-namespace workers {
-class WorkerPrivate;
-}
-
 class File;
 
 class WebSocketImpl;
 
 class WebSocket final : public DOMEventTargetHelper
 {
   friend class WebSocketImpl;
 
--- a/dom/base/nsContentAreaDragDrop.h
+++ b/dom/base/nsContentAreaDragDrop.h
@@ -7,25 +7,21 @@
 #define nsContentAreaDragDrop_h__
 
 
 #include "nsCOMPtr.h"
 
 #include "nsIDOMEventListener.h"
 #include "nsITransferable.h"
 
-class nsIDOMNode;
 class nsPIDOMWindow;
-class nsIDOMDragEvent;
 class nsISelection;
 class nsITransferable;
 class nsIContent;
-class nsIURI;
 class nsIFile;
-class nsISimpleEnumerator;
 
 namespace mozilla {
 namespace dom {
 class DataTransfer;
 }
 }
 
 //
--- a/dom/base/nsContentCreatorFunctions.h
+++ b/dom/base/nsContentCreatorFunctions.h
@@ -10,20 +10,18 @@
 #include "nsCOMPtr.h"
 #include "mozilla/dom/FromParser.h"
 
 /**
  * Functions to create content, to be used only inside Gecko
  * (mozilla/content and mozilla/layout).
  */
 
-class nsAString;
 class nsIContent;
 class imgRequestProxy;
-class nsNodeInfoManager;
 class nsGenericHTMLElement;
 
 namespace mozilla {
 namespace dom {
 class Element;
 class NodeInfo;
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsContentSink.h
+++ b/dom/base/nsContentSink.h
@@ -26,21 +26,19 @@
 #include "prlog.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsThreadUtils.h"
 
 class nsIDocument;
 class nsIURI;
 class nsIChannel;
 class nsIDocShell;
-class nsIParser;
 class nsIAtom;
 class nsIChannel;
 class nsIContent;
-class nsViewManager;
 class nsNodeInfoManager;
 class nsScriptLoader;
 class nsIApplicationCache;
 
 namespace mozilla {
 namespace css {
 class Loader;
 }
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7298,34 +7298,35 @@ nsContentUtils::TransferableToIPCTransfe
           }
 
           // Images to be placed on the clipboard are imgIContainers.
           nsCOMPtr<imgIContainer> image(do_QueryInterface(data));
           if (image) {
             RefPtr<mozilla::gfx::SourceSurface> surface =
               image->GetFrame(imgIContainer::FRAME_CURRENT,
                               imgIContainer::FLAG_SYNC_DECODE);
-
-            mozilla::RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
-              surface->GetDataSurface();
-            size_t length;
-            int32_t stride;
-            mozilla::UniquePtr<char[]> surfaceData =
-              nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
-            
-            IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
-            item->flavor() = nsCString(flavorStr);
-            item->data() = nsCString(surfaceData.get(), length);
-
-            IPCDataTransferImage& imageDetails = item->imageDetails();
-            mozilla::gfx::IntSize size = dataSurface->GetSize();
-            imageDetails.width() = size.width;
-            imageDetails.height() = size.height;
-            imageDetails.stride() = stride;
-            imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
+            if (surface) {
+              mozilla::RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
+                surface->GetDataSurface();
+              size_t length;
+              int32_t stride;
+              mozilla::UniquePtr<char[]> surfaceData =
+                nsContentUtils::GetSurfaceData(dataSurface, &length, &stride);
+              
+              IPCDataTransferItem* item = aIPCDataTransfer->items().AppendElement();
+              item->flavor() = nsCString(flavorStr);
+              item->data() = nsCString(surfaceData.get(), length);
+
+              IPCDataTransferImage& imageDetails = item->imageDetails();
+              mozilla::gfx::IntSize size = dataSurface->GetSize();
+              imageDetails.width() = size.width;
+              imageDetails.height() = size.height;
+              imageDetails.stride() = stride;
+              imageDetails.format() = static_cast<uint8_t>(dataSurface->GetFormat());
+            }
 
             continue;
           }
 
           // Otherwise, handle this as a file.
           nsCOMPtr<FileImpl> fileImpl;
           nsCOMPtr<nsIFile> file = do_QueryInterface(data);
           if (file) {
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -46,62 +46,55 @@ class imgLoader;
 class imgRequestProxy;
 class nsAutoScriptBlockerSuppressNodeRemoved;
 class nsHtml5StringParser;
 class nsIChannel;
 class nsIConsoleService;
 class nsIContent;
 class nsIContentPolicy;
 class nsIContentSecurityPolicy;
-class nsIDocShell;
 class nsIDocument;
 class nsIDocumentLoaderFactory;
-class nsIDocumentObserver;
 class nsIDOMDocument;
 class nsIDOMDocumentFragment;
 class nsIDOMEvent;
-class nsIDOMHTMLFormElement;
 class nsIDOMHTMLInputElement;
 class nsIDOMKeyEvent;
 class nsIDOMNode;
-class nsIDOMScriptObjectFactory;
 class nsIDOMWindow;
 class nsIDragSession;
 class nsIEditor;
 class nsIFragmentContentSink;
 class nsIFrame;
 class nsIImageLoadingContent;
 class nsIInterfaceRequestor;
 class nsIIOService;
-class nsIJSRuntimeService;
 class nsILineBreaker;
 class nsIMessageBroadcaster;
 class nsNameSpaceManager;
 class nsIObserver;
 class nsIParser;
 class nsIParserService;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIRequest;
 class nsIRunnable;
 class nsIScriptContext;
-class nsIScriptGlobalObject;
 class nsIScriptSecurityManager;
 class nsIStringBundle;
 class nsIStringBundleService;
 class nsISupportsArray;
 class nsISupportsHashKey;
 class nsIURI;
 class nsIWidget;
 class nsIWordBreaker;
 class nsIXPConnect;
 class nsNodeInfoManager;
 class nsPIDOMWindow;
 class nsPresContext;
-class nsScriptObjectTracer;
 class nsStringBuffer;
 class nsStringHashKey;
 class nsTextFragment;
 class nsView;
 class nsViewportInfo;
 class nsWrapperCache;
 class nsAttrValue;
 class nsITransferable;
--- a/dom/base/nsDOMClassInfo.h
+++ b/dom/base/nsDOMClassInfo.h
@@ -13,21 +13,18 @@
 #include "nsIDOMScriptObjectFactory.h"
 #include "js/Id.h"
 #include "nsIXPConnect.h"
 
 #ifdef XP_WIN
 #undef GetClassName
 #endif
 
-class nsContentList;
-class nsDocument;
 struct nsGlobalNameStruct;
 class nsGlobalWindow;
-class nsIScriptSecurityManager;
 
 struct nsDOMClassInfoData;
 
 typedef nsIClassInfo* (*nsDOMClassInfoConstructorFnc)
   (nsDOMClassInfoData* aData);
 
 typedef nsresult (*nsDOMConstructorFunc)(nsISupports** aNewObject);
 
--- a/dom/base/nsDOMMutationObserver.cpp
+++ b/dom/base/nsDOMMutationObserver.cpp
@@ -59,16 +59,23 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ns
                                       mPreviousSibling, mNextSibling,
                                       mAddedNodes, mRemovedNodes,
                                       mAddedAnimations, mRemovedAnimations,
                                       mChangedAnimations,
                                       mNext, mOwner)
 
 // Observer
 
+bool
+nsMutationReceiverBase::IsObservable(nsIContent* aContent)
+{
+  return !aContent->ChromeOnlyAccess() &&
+    (Observer()->IsChrome() || !aContent->IsInAnonymousSubtree());
+}
+
 NS_IMPL_ADDREF(nsMutationReceiver)
 NS_IMPL_RELEASE(nsMutationReceiver)
 
 NS_INTERFACE_MAP_BEGIN(nsMutationReceiver)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
 NS_INTERFACE_MAP_END
 
@@ -106,18 +113,17 @@ nsMutationReceiver::Disconnect(bool aRem
 void
 nsMutationReceiver::AttributeWillChange(nsIDocument* aDocument,
                                         mozilla::dom::Element* aElement,
                                         int32_t aNameSpaceID,
                                         nsIAtom* aAttribute,
                                         int32_t aModType)
 {
   if (nsAutoMutationBatch::IsBatching() ||
-      !ObservesAttr(aElement, aNameSpaceID, aAttribute) ||
-      aElement->ChromeOnlyAccess()) {
+      !ObservesAttr(RegisterTarget(), aElement, aNameSpaceID, aAttribute)) {
     return;
   }
 
   nsDOMMutationRecord* m =
     Observer()->CurrentRecord(nsGkAtoms::attributes);
 
   NS_ASSERTION(!m->mTarget || m->mTarget == aElement,
                "Wrong target!");
@@ -142,24 +148,26 @@ nsMutationReceiver::AttributeWillChange(
 }
 
 void
 nsMutationReceiver::CharacterDataWillChange(nsIDocument *aDocument,
                                             nsIContent* aContent,
                                             CharacterDataChangeInfo* aInfo)
 {
   if (nsAutoMutationBatch::IsBatching() ||
-      !CharacterData() || !(Subtree() || aContent == Target()) ||
-      aContent->ChromeOnlyAccess()) {
+      !CharacterData() ||
+      (!Subtree() && aContent != Target()) ||
+      (Subtree() && RegisterTarget()->SubtreeRoot() != aContent->SubtreeRoot()) ||
+      !IsObservable(aContent)) {
     return;
   }
-  
+
   nsDOMMutationRecord* m =
     Observer()->CurrentRecord(nsGkAtoms::characterData);
- 
+
   NS_ASSERTION(!m->mTarget || m->mTarget == aContent,
                "Wrong target!");
 
   if (!m->mTarget) {
     m->mTarget = aContent;
   }
   if (CharacterDataOldValue() && m->mPrevValue.IsVoid()) { 
     aContent->GetText()->AppendTo(m->mPrevValue);
@@ -168,18 +176,21 @@ nsMutationReceiver::CharacterDataWillCha
 
 void
 nsMutationReceiver::ContentAppended(nsIDocument* aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aFirstNewContent,
                                     int32_t aNewIndexInContainer)
 {
   nsINode* parent = NODE_FROM(aContainer, aDocument);
-  bool wantsChildList = ChildList() && (Subtree() || parent == Target());
-  if (!wantsChildList || aFirstNewContent->ChromeOnlyAccess()) {
+  bool wantsChildList =
+    ChildList() &&
+    ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) ||
+     parent == Target());
+  if (!wantsChildList || !IsObservable(aFirstNewContent)) {
     return;
   }
 
   if (nsAutoMutationBatch::IsBatching()) {
     if (parent == nsAutoMutationBatch::GetBatchTarget()) {
       nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
     }
     return;
@@ -206,18 +217,21 @@ nsMutationReceiver::ContentAppended(nsID
 
 void
 nsMutationReceiver::ContentInserted(nsIDocument* aDocument,
                                     nsIContent* aContainer,
                                     nsIContent* aChild,
                                     int32_t aIndexInContainer)
 {
   nsINode* parent = NODE_FROM(aContainer, aDocument);
-  bool wantsChildList = ChildList() && (Subtree() || parent == Target());
-  if (!wantsChildList || aChild->ChromeOnlyAccess()) {
+  bool wantsChildList =
+    ChildList() &&
+    ((Subtree() && RegisterTarget()->SubtreeRoot() == parent->SubtreeRoot()) ||
+     parent == Target());
+  if (!wantsChildList || !IsObservable(aChild)) {
     return;
   }
 
   if (nsAutoMutationBatch::IsBatching()) {
     if (parent == nsAutoMutationBatch::GetBatchTarget()) {
       nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
     }
     return;
@@ -238,21 +252,24 @@ nsMutationReceiver::ContentInserted(nsID
 
 void
 nsMutationReceiver::ContentRemoved(nsIDocument* aDocument,
                                    nsIContent* aContainer,
                                    nsIContent* aChild,
                                    int32_t aIndexInContainer,
                                    nsIContent* aPreviousSibling)
 {
-  if (aChild->ChromeOnlyAccess()) {
+  if (!IsObservable(aChild)) {
     return;
   }
 
   nsINode* parent = NODE_FROM(aContainer, aDocument);
+  if (Subtree() && parent->SubtreeRoot() != RegisterTarget()->SubtreeRoot()) {
+    return;
+  }
   if (nsAutoMutationBatch::IsBatching()) {
     if (nsAutoMutationBatch::IsRemovalDone()) {
       // This can happen for example if HTML parser parses to
       // context node, but needs to move elements around.
       return;
     }
     if (nsAutoMutationBatch::GetBatchTarget() != parent) {
       return;
@@ -260,17 +277,17 @@ nsMutationReceiver::ContentRemoved(nsIDo
 
     bool wantsChildList = ChildList() && (Subtree() || parent == Target());
     if (wantsChildList || Subtree()) {
       nsAutoMutationBatch::NodeRemoved(aChild);
       nsAutoMutationBatch::UpdateObserver(Observer(), wantsChildList);
     }
 
     return;
-  }                                                                   
+  }
 
   if (Subtree()) {
     // Try to avoid creating transient observer if the node
     // already has an observer observing the same set of nodes.
     nsMutationReceiver* orig = GetParent() ? GetParent() : this;
     if (Observer()->GetReceiverFor(aChild, false, false) != orig) {
       bool transientExists = false;
       nsCOMArray<nsMutationReceiver>* transientReceivers = nullptr;
@@ -709,18 +726,19 @@ nsDOMMutationObserver::Constructor(const
                                    mozilla::ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   MOZ_ASSERT(window->IsInnerWindow());
+  bool isChrome = nsContentUtils::IsChromeDoc(window->GetExtantDoc());
   nsRefPtr<nsDOMMutationObserver> observer =
-    new nsDOMMutationObserver(window.forget(), aCb);
+    new nsDOMMutationObserver(window.forget(), aCb, isChrome);
   return observer.forget();
 }
 
 void
 nsDOMMutationObserver::HandleMutation()
 {
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "Whaat!");
   NS_ASSERTION(mCurrentMutations.IsEmpty(),
--- a/dom/base/nsDOMMutationObserver.h
+++ b/dom/base/nsDOMMutationObserver.h
@@ -243,24 +243,30 @@ protected:
 
   void AddObserver()
   {
     AddMutationObserver();
     mRegisterTarget->SetMayHaveDOMMutationObserver();
     mRegisterTarget->OwnerDoc()->SetMayHaveDOMMutationObservers();
   }
 
-  bool ObservesAttr(mozilla::dom::Element* aElement,
+  bool IsObservable(nsIContent* aContent);
+
+  bool ObservesAttr(nsINode* aRegisterTarget,
+                    mozilla::dom::Element* aElement,
                     int32_t aNameSpaceID,
                     nsIAtom* aAttr)
   {
     if (mParent) {
-      return mParent->ObservesAttr(aElement, aNameSpaceID, aAttr);
+      return mParent->ObservesAttr(aRegisterTarget, aElement, aNameSpaceID, aAttr);
     }
-    if (!Attributes() || (!Subtree() && aElement != Target())) {
+    if (!Attributes() ||
+        (!Subtree() && aElement != Target()) ||
+        (Subtree() && aRegisterTarget->SubtreeRoot() != aElement->SubtreeRoot()) ||
+        !IsObservable(aElement)) {
       return false;
     }
     if (AllAttributes()) {
       return true;
     }
 
     if (aNameSpaceID != kNameSpaceID_None) {
       return false;
@@ -442,19 +448,20 @@ private:
 { 0x0c3b91f8, 0xcc3b, 0x4b08, \
   { 0x9e, 0xab, 0x07, 0x47, 0xa9, 0xe4, 0x65, 0xb4 } }
 
 class nsDOMMutationObserver final : public nsISupports,
                                     public nsWrapperCache
 {
 public:
   nsDOMMutationObserver(already_AddRefed<nsPIDOMWindow>&& aOwner,
-                        mozilla::dom::MutationCallback& aCb)
+                        mozilla::dom::MutationCallback& aCb,
+                        bool aChrome)
   : mOwner(aOwner), mLastPendingMutation(nullptr), mPendingMutationCount(0),
-    mCallback(&aCb), mWaitingForRun(false), mId(++sCount)
+    mCallback(&aCb), mWaitingForRun(false), mIsChrome(aChrome), mId(++sCount)
   {
   }
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_DOM_MUTATION_OBSERVER_IID)
 
   static already_AddRefed<nsDOMMutationObserver>
   Constructor(const mozilla::dom::GlobalObject& aGlobal,
@@ -466,16 +473,21 @@ public:
     return mozilla::dom::MutationObserverBinding::Wrap(aCx, this, aGivenProto);
   }
 
   nsISupports* GetParentObject() const
   {
     return mOwner;
   }
 
+  bool IsChrome()
+  {
+    return mIsChrome;
+  }
+
   void Observe(nsINode& aTarget,
                const mozilla::dom::MutationObserverInit& aOptions,
                mozilla::ErrorResult& aRv);
 
   void Disconnect();
 
   void TakeRecords(nsTArray<nsRefPtr<nsDOMMutationRecord> >& aRetVal);
 
@@ -566,16 +578,17 @@ protected:
   // the microtask.
   nsRefPtr<nsDOMMutationRecord>                      mFirstPendingMutation;
   nsDOMMutationRecord*                               mLastPendingMutation;
   uint32_t                                           mPendingMutationCount;
 
   nsRefPtr<mozilla::dom::MutationCallback>           mCallback;
 
   bool                                               mWaitingForRun;
+  bool                                               mIsChrome;
 
   uint64_t                                           mId;
 
   static uint64_t                                    sCount;
   static nsAutoTArray<nsRefPtr<nsDOMMutationObserver>, 4>* sScheduledMutationObservers;
   static nsDOMMutationObserver*                      sCurrentObserver;
 
   static uint32_t                                    sMutationLevel;
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2349,22 +2349,25 @@ nsDOMWindowUtils::AdvanceTimeAndRefresh(
   nsCOMPtr<nsIDocument> doc = GetDocument();
   if (doc) {
     PendingAnimationTracker* tracker = doc->GetPendingAnimationTracker();
     if (tracker) {
       tracker->TriggerPendingAnimationsNow();
     }
   }
 
-  nsRefreshDriver* driver = GetPresContext()->RefreshDriver();
-  driver->AdvanceTimeAndRefresh(aMilliseconds);
-
-  RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
-  if (transaction && transaction->IPCOpen()) {
-    transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
+  nsPresContext* presContext = GetPresContext();
+  if (presContext) {
+    nsRefreshDriver* driver = presContext->RefreshDriver();
+    driver->AdvanceTimeAndRefresh(aMilliseconds);
+
+    RefPtr<LayerTransactionChild> transaction = GetLayerTransaction();
+    if (transaction && transaction->IPCOpen()) {
+      transaction->SendSetTestSampleTime(driver->MostRecentRefresh());
+    }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::RestoreNormalRefresh()
 {
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -71,27 +71,22 @@
 
 #define XML_DECLARATION_BITS_DECLARATION_EXISTS   (1 << 0)
 #define XML_DECLARATION_BITS_ENCODING_EXISTS      (1 << 1)
 #define XML_DECLARATION_BITS_STANDALONE_EXISTS    (1 << 2)
 #define XML_DECLARATION_BITS_STANDALONE_YES       (1 << 3)
 
 
 class nsDOMStyleSheetSetList;
-class nsIOutputStream;
 class nsDocument;
-class nsIDTD;
 class nsIRadioVisitor;
 class nsIFormControl;
 struct nsRadioGroupStruct;
 class nsOnloadBlocker;
 class nsUnblockOnloadEvent;
-class nsChildContentList;
-class nsHTMLStyleSheet;
-class nsHTMLCSSStyleSheet;
 class nsDOMNavigationTiming;
 class nsWindowSizes;
 class nsHtml5TreeOpExecutor;
 class nsDocumentOnStack;
 class nsPointerLockPermissionRequest;
 class nsISecurityConsoleMessage;
 class nsPIBoxObject;
 
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -45,19 +45,16 @@ struct StructuredCloneData;
 namespace layout {
 class RenderFrameParent;
 }
 }
 
 #if defined(MOZ_WIDGET_GTK)
 typedef struct _GtkWidget GtkWidget;
 #endif
-#ifdef MOZ_WIDGET_QT
-class QX11EmbedContainer;
-#endif
 
 class nsFrameLoader final : public nsIFrameLoader,
                             public nsStubMutationObserver,
                             public mozilla::dom::ipc::MessageManagerCallback
 {
   friend class AutoResetInShow;
   typedef mozilla::dom::PBrowserParent PBrowserParent;
   typedef mozilla::dom::TabParent TabParent;
--- a/dom/base/nsFrameMessageManager.h
+++ b/dom/base/nsFrameMessageManager.h
@@ -119,18 +119,16 @@ protected:
 
 StructuredCloneData UnpackClonedMessageDataForParent(const ClonedMessageData& aData);
 StructuredCloneData UnpackClonedMessageDataForChild(const ClonedMessageData& aData);
 
 } // namespace ipc
 } // namespace dom
 } // namespace mozilla
 
-class nsAXPCNativeCallContext;
-
 struct nsMessageListenerInfo
 {
   bool operator==(const nsMessageListenerInfo& aOther) const
   {
     return &aOther == this;
   }
 
   // Exactly one of mStrongListener and mWeakListener must be non-null.
--- a/dom/base/nsGenericDOMDataNode.h
+++ b/dom/base/nsGenericDOMDataNode.h
@@ -18,22 +18,17 @@
 #include "nsError.h"
 #include "mozilla/dom/Element.h"
 #include "nsCycleCollectionParticipant.h"
 
 #include "nsISMILAttr.h"
 #include "mozilla/dom/ShadowRoot.h"
 
 class nsIDocument;
-class nsIDOMAttr;
-class nsIDOMEventListener;
-class nsIDOMNodeList;
-class nsIFrame;
 class nsIDOMText;
-class nsURI;
 
 #define DATA_NODE_FLAG_BIT(n_) NODE_FLAG_BIT(NODE_TYPE_SPECIFIC_BITS_OFFSET + (n_))
 
 // Data node specific flags
 enum {
   // This bit is set to indicate that if the text node changes to
   // non-whitespace, we may need to create a frame for it. This bit must
   // not be set on nodes that already have a frame.
--- a/dom/base/nsHostObjectProtocolHandler.h
+++ b/dom/base/nsHostObjectProtocolHandler.h
@@ -12,17 +12,16 @@
 #include "nsIInputStream.h"
 
 #define BLOBURI_SCHEME "blob"
 #define MEDIASTREAMURI_SCHEME "mediastream"
 #define MEDIASOURCEURI_SCHEME "mediasource"
 #define FONTTABLEURI_SCHEME "moz-fonttable"
 #define RTSPURI_SCHEME "rtsp"
 
-class nsIDOMBlob;
 class nsIPrincipal;
 
 namespace mozilla {
 class DOMMediaStream;
 namespace dom {
 class FileImpl;
 class MediaSource;
 }
--- a/dom/base/nsIAttribute.h
+++ b/dom/base/nsIAttribute.h
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIAttribute_h___
 #define nsIAttribute_h___
 
 #include "nsINode.h"
 
 class nsDOMAttributeMap;
-class nsIContent;
 
 #define NS_IATTRIBUTE_IID  \
 { 0x233a9c4d, 0xb27f, 0x4662, \
     { 0xbd, 0x90, 0xba, 0xd6, 0x2e, 0x76, 0xc8, 0xe1 } }
 
 class nsIAttribute : public nsINode
 {
 public:
--- a/dom/base/nsIDOMScriptObjectFactory.h
+++ b/dom/base/nsIDOMScriptObjectFactory.h
@@ -9,20 +9,16 @@
 #include "nsISupports.h"
 #include "nsIDOMClassInfo.h"
 #include "nsString.h"
 
 #define NS_IDOM_SCRIPT_OBJECT_FACTORY_IID \
 { 0x2a50e17c, 0x46ff, 0x4150, \
   { 0xbb, 0x46, 0xd8, 0x07, 0xb3, 0x36, 0xde, 0xab } }
 
-class nsIScriptContext;
-class nsIScriptGlobalObject;
-class nsIDOMEventListener;
-
 typedef nsXPCClassInfo* (*nsDOMClassInfoExternalConstructorFnc)
   (const char* aName);
 
 class nsIDOMScriptObjectFactory : public nsISupports {
 public:  
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOM_SCRIPT_OBJECT_FACTORY_IID)
 
   NS_IMETHOD_(nsISupports *) GetClassInfoInstance(nsDOMClassInfoID aID) = 0;
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -44,17 +44,16 @@ class nsIAtom;
 class nsIBFCacheEntry;
 class nsIChannel;
 class nsIContent;
 class nsIContentSink;
 class nsIDocShell;
 class nsIDocumentEncoder;
 class nsIDocumentObserver;
 class nsIDOMDocument;
-class nsIDOMDocumentFragment;
 class nsIDOMDocumentType;
 class nsIDOMElement;
 class nsIDOMNodeFilter;
 class nsIDOMNodeList;
 class nsIHTMLCollection;
 class nsILayoutHistoryState;
 class nsILoadContext;
 class nsIObjectLoadingContent;
@@ -114,17 +113,16 @@ class DOMImplementation;
 class DOMStringList;
 class Element;
 struct ElementRegistrationOptions;
 class Event;
 class EventTarget;
 class FontFaceSet;
 class FrameRequestCallback;
 class ImportManager;
-class OverfillCallback;
 class HTMLBodyElement;
 struct LifecycleCallbackArgs;
 class Link;
 class MediaQueryList;
 class GlobalObject;
 class NodeFilter;
 class NodeIterator;
 class ProcessingInstruction;
--- a/dom/base/nsIDocumentObserver.h
+++ b/dom/base/nsIDocumentObserver.h
@@ -4,21 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIDocumentObserver_h___
 #define nsIDocumentObserver_h___
 
 #include "mozilla/EventStates.h"
 #include "nsISupports.h"
 #include "nsIMutationObserver.h"
 
-class nsIAtom;
 class nsIContent;
 class nsIStyleSheet;
 class nsIStyleRule;
-class nsString;
 class nsIDocument;
 
 #define NS_IDOCUMENT_OBSERVER_IID \
 { 0x900bc4bc, 0x8b6c, 0x4cba, \
  { 0x82, 0xfa, 0x56, 0x8a, 0x80, 0xff, 0xfd, 0x3e } }
 
 typedef uint32_t nsUpdateType;
 
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -43,17 +43,16 @@ class nsIEditor;
 class nsIFrame;
 class nsIMutationObserver;
 class nsINode;
 class nsIPresShell;
 class nsIPrincipal;
 class nsIURI;
 class nsNodeSupportsWeakRefTearoff;
 class nsNodeWeakReference;
-class nsXPCClassInfo;
 class nsDOMMutationObserver;
 
 namespace mozilla {
 class EventListenerManager;
 namespace dom {
 /**
  * @return true if aChar is what the DOM spec defines as 'space character'.
  * http://dom.spec.whatwg.org/#space-character
@@ -68,17 +67,16 @@ inline bool IsSpaceCharacter(char aChar)
 }
 struct BoxQuadOptions;
 struct ConvertCoordinateOptions;
 class DOMPoint;
 class DOMQuad;
 class DOMRectReadOnly;
 class Element;
 class EventHandlerNonNull;
-class OnErrorEventHandlerNonNull;
 template<typename T> class Optional;
 class Text;
 class TextOrElementOrDocument;
 struct DOMPointInit;
 } // namespace dom
 } // namespace mozilla
 
 #define NODE_FLAG_BIT(n_) \
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -9,26 +9,16 @@
 #include "nscore.h"
 #include "nsStringGlue.h"
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "jspubtd.h"
 #include "js/GCAPI.h"
 
 class nsIScriptGlobalObject;
-class nsIScriptSecurityManager;
-class nsIPrincipal;
-class nsIAtom;
-class nsIArray;
-class nsIVariant;
-class nsIObjectInputStream;
-class nsIObjectOutputStream;
-class nsIScriptObjectPrincipal;
-class nsIDOMWindow;
-class nsIURI;
 
 #define NS_ISCRIPTCONTEXT_IID \
 { 0x901f0d5e, 0x217a, 0x45fa, \
   { 0x9a, 0xca, 0x45, 0x0f, 0xe7, 0x2f, 0x10, 0x9a } }
 
 class nsIOffThreadScriptReceiver;
 
 /**
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -22,18 +22,16 @@
 #include "nsIRequest.h"
 #include "mozilla/ErrorResult.h"
 #include "nsAutoPtr.h"
 #include "nsIContentPolicy.h"
 #include "mozilla/dom/BindingDeclarations.h"
 
 class nsIURI;
 class nsIDocument;
-class imgILoader;
-class nsIIOService;
 class nsPresContext;
 class nsIContent;
 class imgRequestProxy;
 
 #ifdef LoadImage
 // Undefine LoadImage to prevent naming conflict with Windows.
 #undef LoadImage
 #endif
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -14,19 +14,17 @@
 #include "nsIXPConnect.h"
 #include "nsIArray.h"
 #include "mozilla/Attributes.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "xpcpublic.h"
 
 class nsICycleCollectorListener;
-class nsIXPConnectJSObjectHolder;
 class nsScriptNameSpaceManager;
-class nsCycleCollectionNoteRootCallback;
 
 namespace JS {
 class AutoValueVector;
 }
 
 namespace mozilla {
 template <class> class Maybe;
 struct CycleCollectorResults;
@@ -168,18 +166,16 @@ private:
   // context does. It is eventually collected by the cycle collector.
   nsCOMPtr<nsIScriptGlobalObject> mGlobalObjectRef;
 
   static void JSOptionChangedCallback(const char *pref, void *data);
 
   static bool DOMOperationCallback(JSContext *cx);
 };
 
-class nsIJSRuntimeService;
-class nsIPrincipal;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 void StartupJSEnvironment();
 void ShutdownJSEnvironment();
 
--- a/dom/base/nsMappedAttributes.h
+++ b/dom/base/nsMappedAttributes.h
@@ -14,17 +14,16 @@
 #include "nsAttrAndChildArray.h"
 #include "nsMappedAttributeElement.h"
 #include "nsIStyleRule.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 
 class nsIAtom;
 class nsHTMLStyleSheet;
-class nsRuleWalker;
 
 class nsMappedAttributes final : public nsIStyleRule
 {
 public:
   nsMappedAttributes(nsHTMLStyleSheet* aSheet,
                      nsMapRuleToAttributesFunc aMapRuleFunc);
 
   // Do not return null.
--- a/dom/base/nsNameSpaceManager.h
+++ b/dom/base/nsNameSpaceManager.h
@@ -6,17 +6,16 @@
 #ifndef nsNameSpaceManager_h___
 #define nsNameSpaceManager_h___
 
 #include "nsDataHashtable.h"
 #include "nsTArray.h"
 
 #include "mozilla/StaticPtr.h"
 
-class nsIAtom;
 class nsAString;
 
 class nsNameSpaceKey : public PLDHashEntryHdr
 {
 public:
   typedef const nsAString* KeyType;
   typedef const nsAString* KeyTypePointer;
 
--- a/dom/base/nsNodeUtils.h
+++ b/dom/base/nsNodeUtils.h
@@ -7,18 +7,16 @@
 #define nsNodeUtils_h___
 
 #include "nsIContent.h"          // for use in inline function (ParentChainChanged)
 #include "nsIMutationObserver.h" // for use in inline function (ParentChainChanged)
 #include "js/TypeDecls.h"
 #include "nsCOMArray.h"
 
 struct CharacterDataChangeInfo;
-class nsIVariant;
-class nsIDOMNode;
 template<class E> class nsCOMArray;
 class nsCycleCollectionTraversalCallback;
 namespace mozilla {
 namespace dom {
 class Animation;
 }
 }
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -20,17 +20,16 @@
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
 
 class nsIArray;
 class nsIContent;
 class nsIDocShell;
 class nsIDocument;
 class nsIIdleObserver;
-class nsIPrincipal;
 class nsIScriptTimeoutHandler;
 class nsIURI;
 class nsPerformance;
 class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 struct nsTimeout;
 
 namespace mozilla {
--- a/dom/base/nsReferencedElement.h
+++ b/dom/base/nsReferencedElement.h
@@ -10,17 +10,16 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Element.h"
 #include "nsIAtom.h"
 #include "nsIDocument.h"
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 
 class nsIURI;
-class nsCycleCollectionCallback;
 
 /**
  * Class to track what element is referenced by a given ID.
  * 
  * To use it, call Reset() to set it up to watch a given URI. Call get()
  * anytime to determine the referenced element (which may be null if
  * the element isn't found). When the element changes, ElementChanged
  * will be called, so subclass this class if you want to receive that
--- a/dom/base/nsScriptNameSpaceManager.h
+++ b/dom/base/nsScriptNameSpaceManager.h
@@ -74,21 +74,17 @@ struct nsGlobalNameStruct
   union {
     mozilla::dom::DefineInterface mDefineDOMInterface; // for window
     mozilla::dom::ConstructNavigatorProperty mConstructNavigatorProperty; // for navigator
   };
   // May be null if enabled unconditionally
   mozilla::dom::ConstructorEnabled* mConstructorEnabled;
 };
 
-
-class nsIScriptContext;
 class nsICategoryManager;
-class GlobalNameMapEntry;
-
 
 class nsScriptNameSpaceManager : public nsIObserver,
                                  public nsSupportsWeakReference,
                                  public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
--- a/dom/base/nsStructuredCloneContainer.cpp
+++ b/dom/base/nsStructuredCloneContainer.cpp
@@ -43,26 +43,19 @@ nsStructuredCloneContainer::~nsStructure
 
 nsresult
 nsStructuredCloneContainer::InitFromJSVal(JS::Handle<JS::Value> aData,
                                           JSContext* aCx)
 {
   NS_ENSURE_STATE(!mData);
 
   uint64_t* jsBytes = nullptr;
-  bool success = false;
-  if (aData.isPrimitive()) {
-    success = JS_WriteStructuredClone(aCx, aData, &jsBytes, &mSize,
-                                      nullptr, nullptr,
-                                      JS::UndefinedHandleValue);
-  } else {
-    success = JS_WriteStructuredClone(aCx, aData, &jsBytes, &mSize,
-                                      nullptr, nullptr,
-                                      JS::UndefinedHandleValue);
-  }
+  bool success = JS_WriteStructuredClone(aCx, aData, &jsBytes, &mSize,
+                                           nullptr, nullptr,
+                                           JS::UndefinedHandleValue);
   NS_ENSURE_STATE(success);
   NS_ENSURE_STATE(jsBytes);
 
   // Copy jsBytes into our own buffer.
   mData = (uint64_t*) malloc(mSize);
   if (!mData) {
     mSize = 0;
     mVersion = 0;
--- a/dom/base/nsTextFragment.h
+++ b/dom/base/nsTextFragment.h
@@ -14,17 +14,16 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/MemoryReporting.h"
 
 #include "nsString.h"
 #include "nsReadableUtils.h"
 #include "nsISupportsImpl.h"
 
 class nsString;
-class nsCString;
 
 // XXX should this normalize the code to keep a \u0000 at the end?
 
 // XXX nsTextFragmentPool?
 
 /**
  * A fragment of text. If mIs2b is 1 then the m2b pointer is valid
  * otherwise the m1b pointer is valid. If m1b is used then each byte
--- a/dom/base/nsWindowRoot.h
+++ b/dom/base/nsWindowRoot.h
@@ -3,25 +3,19 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsWindowRoot_h__
 #define nsWindowRoot_h__
 
 class nsPIDOMWindow;
-class nsIDOMEventListener;
 class nsIDOMEvent;
 class nsIGlobalObject;
 
-namespace mozilla {
-class EventChainPostVisitor;
-class EventChainPreVisitor;
-} // namespace mozilla
-
 #include "mozilla/Attributes.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIDOMEventTarget.h"
 #include "nsPIWindowRoot.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
--- a/dom/base/nsWrapperCache.h
+++ b/dom/base/nsWrapperCache.h
@@ -18,17 +18,16 @@ namespace mozilla {
 namespace dom {
 class TabChildGlobal;
 class ProcessGlobal;
 } // namespace dom
 } // namespace mozilla
 class SandboxPrivate;
 class nsInProcessTabChildGlobal;
 class nsWindowRoot;
-class XPCWrappedNativeScope;
 
 #define NS_WRAPPERCACHE_IID \
 { 0x6f3179a1, 0x36f7, 0x4a5c, \
   { 0x8c, 0xf1, 0xad, 0xc8, 0x7c, 0xde, 0x3e, 0x87 } }
 
 /**
  * Class to store the wrapper for an object. This can only be used with objects
  * that only have one non-security wrapper at a time (for an XPCWrappedNative
--- a/dom/base/nsXMLContentSerializer.h
+++ b/dom/base/nsXMLContentSerializer.h
@@ -18,17 +18,16 @@
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "nsString.h"
 
 #define kIndentStr NS_LITERAL_STRING("  ")
 #define kEndTag NS_LITERAL_STRING("</")
 
 class nsIAtom;
-class nsIDOMNode;
 class nsINode;
 
 class nsXMLContentSerializer : public nsIContentSerializer {
  public:
   nsXMLContentSerializer();
 
   NS_DECL_ISUPPORTS
 
--- a/dom/base/test/test_mutationobservers.html
+++ b/dom/base/test/test_mutationobservers.html
@@ -15,16 +15,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
                                 
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 641821 **/
 
+SimpleTest.requestFlakyTimeout("requestFlakyTimeout is silly. (But make sure marquee has time to initialize itself.)");
+
 var div = document.createElement("div");
 
 var M;
 if ("MozMutationObserver" in window) {
   M = window.MozMutationObserver;
 } else if ("WebKitMutationObserver" in window) {
   M = window.WebKitMutationObserver;
 } else {
@@ -575,32 +577,100 @@ function testTakeRecords() {
   div.setAttribute("foo", "bar");
   is(mutationEventCount, 1, "Should have got only one mutation event!");
 }
 
 function testExpandos() {
   var m2 = new M(function(records, observer) {
     is(observer.expandoProperty, true);
     observer.disconnect();
-    then(testStyleCreate);
+    then(testOutsideShadowDOM);
   });
   m2.expandoProperty = true;
   m2.observe(div, { attributes: true });
   m2 = null;
   if (SpecialPowers) {
     // Run GC several times to see if the expando property disappears.
 
     SpecialPowers.gc();
     SpecialPowers.gc();
     SpecialPowers.gc();
     SpecialPowers.gc();
   }
   div.setAttribute("foo", "bar2");
 }
 
+function testOutsideShadowDOM() {
+  var m = new M(function(records, observer) {
+    is(records.length, 1);
+    is(records[0].type, "attributes", "Should have got attributes");
+    observer.disconnect();
+    then(testInsideShadowDOM);
+  });
+  m.observe(div, {
+      attributes: true,
+      childList: true,
+      characterData: true,
+      subtree: true
+    })
+  var sr = div.createShadowRoot();
+  sr.innerHTML = "<div" + ">text</" + "div>";
+  sr.firstChild.setAttribute("foo", "bar");
+  sr.firstChild.firstChild.data = "text2";
+  sr.firstChild.appendChild(document.createElement("div"));
+  div.setAttribute("foo", "bar");
+}
+
+function testInsideShadowDOM() {
+  var m = new M(function(records, observer) {
+    is(records.length, 4);
+    is(records[0].type, "childList");
+    is(records[1].type, "attributes");
+    is(records[2].type, "characterData");
+    is(records[3].type, "childList");
+    observer.disconnect();
+    then(testMarquee);
+  });
+  var sr = div.createShadowRoot();
+  m.observe(sr, {
+      attributes: true,
+      childList: true,
+      characterData: true,
+      subtree: true
+    });
+
+  sr.innerHTML = "<div" + ">text</" + "div>";
+  sr.firstChild.setAttribute("foo", "bar");
+  sr.firstChild.firstChild.data = "text2";
+  sr.firstChild.appendChild(document.createElement("div"));
+  div.setAttribute("foo", "bar2");
+
+}
+
+function testMarquee() {
+  var m = new M(function(records, observer) {
+    is(records.length, 1);
+    is(records[0].type, "attributes");
+    is(records[0].attributeName, "ok");
+    is(records[0].oldValue, null);
+    observer.disconnect();
+    then(testStyleCreate);
+  });
+  var marquee = document.createElement("marquee");
+  m.observe(marquee, {
+    attributes: true,
+    attributeOldValue: true,
+    childList: true,
+    characterData: true,
+    subtree: true
+  });
+  document.body.appendChild(marquee);
+  setTimeout(function() {marquee.setAttribute("ok", "ok")}, 500);
+}
+
 function testStyleCreate() {
   m = new M(function(records, observer) {
     is(records.length, 1, "number of records");
     is(records[0].type, "attributes", "record.type");
     is(records[0].attributeName, "style", "record.attributeName");
     is(records[0].oldValue, null, "record.oldValue");
     isnot(div.getAttribute("style"), null, "style attribute after creation");
     observer.disconnect();
--- a/dom/battery/BatteryManager.h
+++ b/dom/battery/BatteryManager.h
@@ -7,17 +7,16 @@
 #define mozilla_dom_battery_BatteryManager_h
 
 #include "Types.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/Observer.h"
 #include "nsCycleCollectionParticipant.h"
 
 class nsPIDOMWindow;
-class nsIScriptContext;
 
 namespace mozilla {
 
 namespace hal {
 class BatteryInformation;
 } // namespace hal
 
 namespace dom {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -257,16 +257,24 @@ DOMInterfaces = {
     'nativeType': 'nsDOMCaretPosition',
 },
 
 'CharacterData': {
     'nativeType': 'nsGenericDOMDataNode',
     'concrete': False
 },
 
+'ChromeUtils': {
+    # The codegen is dumb, and doesn't understand that this interface is only a
+    # collection of static methods, so we have this `concrete: False` hack.
+    'concrete': False,
+    'nativeType': 'mozilla::devtools::ChromeUtils',
+    'implicitJSContext': ['readHeapSnapshot', 'saveHeapSnapshot']
+},
+
 'ChromeWindow': {
     'concrete': False,
 },
 
 'ChromeWorker': {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
     'nativeType': 'mozilla::dom::workers::ChromeWorkerPrivate',
 },
@@ -488,16 +496,20 @@ DOMInterfaces = {
 'FormData': {
     'nativeType': 'nsFormData'
 },
 
 'Geolocation': {
     'headerFile': 'nsGeolocation.h'
 },
 
+'HeapSnapshot': {
+    'nativeType': 'mozilla::devtools::HeapSnapshot'
+},
+
 'History': {
     'headerFile': 'nsHistory.h',
     'nativeType': 'nsHistory'
 },
 
 'HTMLAppletElement': {
     'nativeType': 'mozilla::dom::HTMLSharedObjectElement'
 },
@@ -654,21 +666,16 @@ DOMInterfaces = {
     'wrapperCache': False,
 },
 
 'InputStream': {
     'nativeType': 'nsIInputStream',
     'notflattened': True
 },
 
-'InstallEvent': {
-    'headerFile': 'ServiceWorkerEvents.h',
-    'nativeType': 'mozilla::dom::workers::InstallEvent',
-},
-
 'KeyEvent': {
     'concrete': False
 },
 
 'LocalMediaStream': {
     'headerFile': 'DOMMediaStream.h',
     'nativeType': 'mozilla::DOMLocalMediaStream'
 },
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -300,17 +300,17 @@ public:
   }
 
   nsISupports* GetISupports() const
   {
     return reinterpret_cast<nsISupports*>(mPtrBits & ~XPCOMCallbackFlag);
   }
 
   // Boolean conversion operator so people can use this in boolean tests
-  operator bool() const
+  explicit operator bool() const
   {
     return GetISupports();
   }
 
   // Even if HasWebIDLCallback returns true, GetWebIDLCallback() might still
   // return null.
   bool HasWebIDLCallback() const
   {
--- a/dom/bindings/test/TestCImplementedInterface.h
+++ b/dom/bindings/test/TestCImplementedInterface.h
@@ -4,18 +4,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 #ifndef TestCImplementedInterface_h
 #define TestCImplementedInterface_h
 
 #include "../TestJSImplGenBinding.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 namespace dom {
 
 class TestCImplementedInterface : public TestJSImplInterface
 {
 public:
   TestCImplementedInterface(JS::Handle<JSObject*> aJSImpl,
                             nsIGlobalObject* aParent)
--- a/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
+++ b/dom/bluetooth/bluedroid/BluetoothDaemonSocketInterface.h
@@ -10,18 +10,16 @@
 #include "BluetoothDaemonHelpers.h"
 #include "BluetoothInterface.h"
 #include "BluetoothInterfaceHelpers.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 using namespace mozilla::ipc;
 
-class BlutoothDaemonInterface;
-
 class BluetoothDaemonSocketModule
 {
 public:
   static const int MAX_NUM_CLIENTS;
 
   virtual nsresult Send(BluetoothDaemonPDU* aPDU, void* aUserData) = 0;
 
   // Commands
--- a/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
+++ b/dom/bluetooth/bluedroid/hfp/BluetoothHfpManager.h
@@ -12,17 +12,16 @@
 #include "BluetoothHfpManagerBase.h"
 #include "BluetoothRilListener.h"
 #include "BluetoothSocketObserver.h"
 #include "mozilla/ipc/SocketBase.h"
 #include "mozilla/Hal.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothReplyRunnable;
 class BluetoothSocket;
 class Call;
 
 /**
  * These costants are defined in 4.33.2 "AT Capabilities Re-Used from GSM 07.07
  * and 3GPP 27.007" in Bluetooth hands-free profile 1.6
  */
 enum BluetoothCmeError {
--- a/dom/bluetooth/bluetooth1/BluetoothDevice.h
+++ b/dom/bluetooth/bluetooth1/BluetoothDevice.h
@@ -13,17 +13,16 @@
 #include "BluetoothPropertyContainer.h"
 #include "nsString.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothSignal;
-class BluetoothSocket;
 
 class BluetoothDevice : public DOMEventTargetHelper
                       , public BluetoothSignalObserver
                       , public BluetoothPropertyContainer
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
--- a/dom/bluetooth/bluetooth1/BluetoothPropertyContainer.h
+++ b/dom/bluetooth/bluetooth1/BluetoothPropertyContainer.h
@@ -5,17 +5,16 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothpropertyobject_h__
 #define mozilla_dom_bluetooth_bluetoothpropertyobject_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothReplyRunnable.h"
 
-class nsIDOMDOMRequest;
 class nsPIDOMWindow;
 
 namespace mozilla {
 class ErrorResult;
 namespace dom {
 class DOMRequest;
 }
 }
--- a/dom/bluetooth/bluetooth1/BluetoothService.h
+++ b/dom/bluetooth/bluetooth1/BluetoothService.h
@@ -22,17 +22,16 @@ namespace mozilla {
 namespace dom {
 class BlobChild;
 class BlobParent;
 }
 }
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothManager;
 class BluetoothNamedValue;
 class BluetoothReplyRunnable;
 class BluetoothSignal;
 
 typedef mozilla::ObserverList<BluetoothSignal> BluetoothSignalObserverList;
 
 class BluetoothService : public nsIObserver
                        , public BluetoothSignalObserver
--- a/dom/bluetooth/bluetooth2/BluetoothDevice.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDevice.h
@@ -22,17 +22,16 @@ namespace dom {
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothClassOfDevice;
 class BluetoothGatt;
 class BluetoothNamedValue;
 class BluetoothValue;
 class BluetoothSignal;
-class BluetoothSocket;
 
 class BluetoothDevice final : public DOMEventTargetHelper
                             , public BluetoothSignalObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BluetoothDevice,
                                            DOMEventTargetHelper)
--- a/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
+++ b/dom/bluetooth/bluetooth2/BluetoothDiscoveryHandle.h
@@ -11,17 +11,16 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsISupportsImpl.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothDevice;
-class BluetoothValue;
 
 class BluetoothDiscoveryHandle final : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   static already_AddRefed<BluetoothDiscoveryHandle>
     Create(nsPIDOMWindow* aWindow);
--- a/dom/bluetooth/bluetooth2/BluetoothGatt.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGatt.h
@@ -17,18 +17,16 @@
 namespace mozilla {
 namespace dom {
 class Promise;
 }
 }
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothReplyRunnable;
-class BluetoothService;
 class BluetoothSignal;
 class BluetoothValue;
 
 class BluetoothGatt final : public DOMEventTargetHelper
                           , public BluetoothSignalObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h
+++ b/dom/bluetooth/bluetooth2/BluetoothGattDescriptor.h
@@ -15,17 +15,16 @@
 #include "nsCOMPtr.h"
 #include "nsWrapperCache.h"
 #include "nsPIDOMWindow.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothGattCharacteristic;
 class BluetoothSignal;
-class BluetoothValue;
 
 class BluetoothGattDescriptor final : public nsISupports
                                     , public nsWrapperCache
                                     , public BluetoothSignalObserver
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothGattDescriptor)
--- a/dom/bluetooth/bluetooth2/BluetoothPairingHandle.h
+++ b/dom/bluetooth/bluetooth2/BluetoothPairingHandle.h
@@ -14,18 +14,16 @@ namespace mozilla {
 class ErrorResult;
 namespace dom {
 class Promise;
 }
 }
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothDevice;
-
 class BluetoothPairingHandle final : public nsISupports
                                    , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(BluetoothPairingHandle)
 
   static already_AddRefed<BluetoothPairingHandle>
--- a/dom/bluetooth/bluetooth2/BluetoothPairingListener.h
+++ b/dom/bluetooth/bluetooth2/BluetoothPairingListener.h
@@ -8,17 +8,16 @@
 #define mozilla_dom_bluetooth_bluetoothpairinglistener_h
 
 #include "BluetoothCommon.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
-class BluetoothDevice;
 class BluetoothSignal;
 
 class BluetoothPairingListener final : public DOMEventTargetHelper
                                      , public BluetoothSignalObserver
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
--- a/dom/browser-element/BrowserElementChildPreload.js
+++ b/dom/browser-element/BrowserElementChildPreload.js
@@ -1287,16 +1287,19 @@ BrowserElementChild.prototype = {
             sendAsyncMsg('error', { type: 'cspBlocked' });
             return;
           case Cr.NS_ERROR_PHISHING_URI :
             sendAsyncMsg('error', { type: 'phishingBlocked' });
             return;
           case Cr.NS_ERROR_MALWARE_URI :
             sendAsyncMsg('error', { type: 'malwareBlocked' });
             return;
+          case Cr.NS_ERROR_UNWANTED_URI :
+            sendAsyncMsg('error', { type: 'unwantedBlocked' });
+            return;
 
           case Cr.NS_ERROR_OFFLINE :
             sendAsyncMsg('error', { type: 'offline' });
             return;
           case Cr.NS_ERROR_MALFORMED_URI :
             sendAsyncMsg('error', { type: 'malformedURI' });
             return;
           case Cr.NS_ERROR_REDIRECT_LOOP :
--- a/dom/cache/OfflineStorage.h
+++ b/dom/cache/OfflineStorage.h
@@ -8,18 +8,16 @@
 #define mozilla_dom_cache_QuotaOfflineStorage_h
 
 #include "nsISupportsImpl.h"
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/dom/cache/Context.h"
 #include "nsIOfflineStorage.h"
 #include "nsTArray.h"
 
-class nsIThread;
-
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class OfflineStorage final : public nsIOfflineStorage
 {
 public:
   static already_AddRefed<OfflineStorage>
--- a/dom/cache/ReadStream.h
+++ b/dom/cache/ReadStream.h
@@ -10,18 +10,16 @@
 #include "mozilla/ipc/FileDescriptor.h"
 #include "nsCOMPtr.h"
 #include "nsID.h"
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 #include "nsRefPtr.h"
 #include "nsTArrayForwardDeclare.h"
 
-class nsIThread;
-
 namespace mozilla {
 namespace dom {
 namespace cache {
 
 class CacheReadStream;
 class CacheReadStreamOrVoid;
 class PCacheStreamControlParent;
 
--- a/dom/camera/GonkCameraControl.h
+++ b/dom/camera/GonkCameraControl.h
@@ -44,19 +44,16 @@ namespace android {
 
 namespace mozilla {
 
 namespace layers {
   class TextureClient;
   class ImageContainer;
 }
 
-class GonkRecorderProfile;
-class GonkRecorderProfileManager;
-
 class nsGonkCameraControl : public CameraControlImpl
 {
 public:
   nsGonkCameraControl(uint32_t aCameraId);
 
   void OnAutoFocusMoving(bool aIsMoving);
   void OnAutoFocusComplete(bool aSuccess, bool aExpired);
   void OnFacesDetected(camera_frame_metadata_t* aMetaData);
--- a/dom/camera/ICameraControl.h
+++ b/dom/camera/ICameraControl.h
@@ -8,18 +8,16 @@
 #include "nsCOMPtr.h"
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "nsISupportsImpl.h"
 #include "base/basictypes.h"
 
 struct DeviceStorageFileDescriptor;
 
-class nsIFile;
-
 namespace mozilla {
 
 class CameraControlListener;
 
 // XXXmikeh - In some future patch this should move into the ICameraControl
 //  class as well, and the names updated to the 'k'-style;
 //  e.g. kParamPreviewSize, etc.
 enum {
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1329,17 +1329,18 @@ CanvasRenderingContext2D::EnsureTarget(R
   RenderingMode mode = (aRenderingMode == RenderingMode::DefaultBackendMode) ? mRenderingMode : aRenderingMode;
 
   if (mTarget && mode == mRenderingMode) {
     return mRenderingMode;
   }
 
    // Check that the dimensions are sane
   IntSize size(mWidth, mHeight);
-  if (size.width <= 0xFFFF && size.height <= 0xFFFF &&
+  if (size.width <= gfxPrefs::MaxCanvasSize() &&
+      size.height <= gfxPrefs::MaxCanvasSize() &&
       size.width >= 0 && size.height >= 0) {
     SurfaceFormat format = GetSurfaceFormat();
     nsIDocument* ownerDoc = nullptr;
     if (mCanvasElement) {
       ownerDoc = mCanvasElement->OwnerDoc();
     }
 
     nsRefPtr<LayerManager> layerManager = nullptr;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -37,17 +37,16 @@ class SourceSurface;
 }
 
 namespace dom {
 class HTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement;
 class ImageData;
 class StringOrCanvasGradientOrCanvasPattern;
 class OwningStringOrCanvasGradientOrCanvasPattern;
 class TextMetrics;
-class SVGMatrix;
 class CanvasFilterChainObserver;
 class CanvasPath;
 
 extern const mozilla::gfx::Float SIGMA_MAX;
 
 template<typename T> class Optional;
 
 struct CanvasBidiProcessor;
--- a/dom/canvas/CanvasUtils.h
+++ b/dom/canvas/CanvasUtils.h
@@ -10,20 +10,16 @@
 #include "mozilla/dom/ToJSValue.h"
 #include "jsapi.h"
 #include "mozilla/FloatingPoint.h"
 
 class nsIPrincipal;
 
 namespace mozilla {
 
-namespace gfx {
-class Matrix;
-}
-
 namespace dom {
 class HTMLCanvasElement;
 }
 
 namespace CanvasUtils {
 
 
 // Check that the rectangle [x,y,w,h] is a subrectangle of [0,0,realWidth,realHeight]
--- a/dom/canvas/WebGL1Context.cpp
+++ b/dom/canvas/WebGL1Context.cpp
@@ -26,16 +26,23 @@ WebGL1Context::~WebGL1Context()
 }
 
 JSObject*
 WebGL1Context::WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto)
 {
     return dom::WebGLRenderingContextBinding::Wrap(cx, this, aGivenProto);
 }
 
+bool
+WebGL1Context::ValidateQueryTarget(GLenum target, const char* info)
+{
+    // TODO: Implement this for EXT_disjoint_timer
+    return false;
+}
+
 } // namespace mozilla
 
 nsresult
 NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** out_result)
 {
     mozilla::Telemetry::Accumulate(mozilla::Telemetry::CANVAS_WEBGL_USED, 1);
 
     nsIDOMWebGLRenderingContext* ctx = mozilla::WebGL1Context::Create();
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -30,15 +30,15 @@ public:
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) override;
 
 private:
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override;
     virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
-    
+    virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif // WEBGL_1_CONTEXT_H_
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -39,18 +39,19 @@ public:
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> aGivenProto) 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);
+    void GetBufferSubData(GLenum target, GLintptr offset,
+                          const dom::Nullable<dom::ArrayBuffer>& maybeData);
+
 
     // -------------------------------------------------------------------------
     // Framebuffer objects - WebGL2ContextFramebuffers.cpp
 
     void BlitFramebuffer(GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1,
                          GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1,
                          GLbitfield mask, GLenum filter);
     void FramebufferTextureLayer(GLenum target, GLenum attachment, GLuint texture, GLint level, GLint layer);
@@ -350,25 +351,27 @@ public:
     void BindVertexArray(WebGLVertexArrayObject* vertexArray);
 */
 
 private:
     WebGL2Context();
 
     JS::Value GetTexParameterInternal(const TexTarget& target, GLenum pname) override;
 
+    void UpdateBoundQuery(GLenum target, WebGLQuery* query);
+
     bool ValidateSizedInternalFormat(GLenum internalFormat, const char* info);
     bool ValidateTexStorage(GLenum target, GLsizei levels, GLenum internalformat,
                                 GLsizei width, GLsizei height, GLsizei depth,
                                 const char* info);
 
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) override;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) override;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) override;
     virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) override;
-
+    virtual bool ValidateQueryTarget(GLenum target, const char* info) override;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) override;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/canvas/WebGL2ContextBuffers.cpp
+++ b/dom/canvas/WebGL2ContextBuffers.cpp
@@ -146,19 +146,92 @@ WebGL2Context::CopyBufferSubData(GLenum 
     }
 
     WebGLContextUnchecked::CopyBufferSubData(readTarget, writeTarget, readOffset,
                                              writeOffset, size);
 }
 
 void
 WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
-                                const dom::ArrayBuffer& returnedData)
+                                const dom::Nullable<dom::ArrayBuffer>& maybeData)
 {
-    MOZ_CRASH("Not Implemented.");
-}
+    if (IsContextLost())
+        return;
+    
+    // For the WebGLBuffer bound to the passed target, read
+    // returnedData.byteLength bytes from the buffer starting at byte
+    // offset offset and write them to returnedData.
+
+    // If zero is bound to target, an INVALID_OPERATION error is
+    // generated.
+    if (!ValidateBufferTarget(target, "getBufferSubData"))
+        return;
+
+    // If offset is less than zero, an INVALID_VALUE error is
+    // generated.
+    if (offset < 0)
+        return ErrorInvalidValue("getBufferSubData: negative offset"); 
+
+    // If returnedData is null then an INVALID_VALUE error is
+    // generated.
+    if (maybeData.IsNull())
+        return ErrorInvalidValue("getBufferSubData: returnedData is null");
+
+    WebGLRefPtr<WebGLBuffer>& bufferSlot = GetBufferSlotByTarget(target);
+    WebGLBuffer* boundBuffer = bufferSlot.get();
+    if (!boundBuffer)
+        return ErrorInvalidOperation("getBufferSubData: no buffer bound");
+    
+    // If offset + returnedData.byteLength would extend beyond the end
+    // of the buffer an INVALID_VALUE error is generated.
+    const dom::ArrayBuffer& data = maybeData.Value();
+    data.ComputeLengthAndData();
+
+    CheckedInt<WebGLsizeiptr> neededByteLength = CheckedInt<WebGLsizeiptr>(offset) + data.Length();
+    if (!neededByteLength.isValid()) {
+        ErrorInvalidValue("getBufferSubData: Integer overflow computing the needed"
+                          " byte length.");
+        return;
+    }
 
-void
-WebGL2Context::GetBufferSubData(GLenum target, GLintptr offset,
-                                const dom::ArrayBufferView& returnedData)
-{
-    MOZ_CRASH("Not Implemented.");
+    if (neededByteLength.value() > boundBuffer->ByteLength()) {
+        ErrorInvalidValue("getBufferSubData: Not enough data. Operation requires"
+                          " %d bytes, but buffer only has %d bytes.",
+                          neededByteLength.value(), boundBuffer->ByteLength());
+        return;
+    }
+
+    // If target is TRANSFORM_FEEDBACK_BUFFER, and any transform
+    // feedback object is currently active, an INVALID_OPERATION error
+    // is generated.
+    WebGLTransformFeedback* currentTF = mBoundTransformFeedback;
+    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
+        if (currentTF->mIsActive)
+            return ErrorInvalidOperation("getBufferSubData: Currently bound transform"
+                                         " feedback is active");
+
+        // https://github.com/NVIDIA/WebGL/commit/63aff5e58c1d79825a596f0f4aa46174b9a5f72c
+        // Performing reads and writes on a buffer that is currently
+        // bound for transform feedback causes undefined results in
+        // GLES3.0 and OpenGL 4.5. In practice results of reads and
+        // writes might be consistent as long as transform feedback
+        // objects are not active, but neither GLES3.0 nor OpenGL 4.5
+        // spec guarantees this - just being bound for transform
+        // feedback is sufficient to cause undefined results.
+
+        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
+    }
+
+    /* If the buffer is written and read sequentially by other
+     * operations and getBufferSubData, it is the responsibility of
+     * the WebGL API to ensure that data are access
+     * consistently. This applies even if the buffer is currently
+     * bound to a transform feedback binding point.
+     */
+
+    void* ptr = gl->fMapBufferRange(target, offset, data.Length(), LOCAL_GL_MAP_READ_BIT);
+    memcpy(data.Data(), ptr, data.Length());
+    gl->fUnmapBuffer(target);
+
+    if (target == LOCAL_GL_TRANSFORM_FEEDBACK_BUFFER && currentTF) {
+        BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, currentTF);
+    }
 }
--- a/dom/canvas/WebGL2ContextQueries.cpp
+++ b/dom/canvas/WebGL2ContextQueries.cpp
@@ -2,18 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
 #include "GLContext.h"
 #include "WebGLQuery.h"
 
-using namespace mozilla;
-using namespace mozilla::dom;
+namespace mozilla {
 
 /*
  * We fake ANY_SAMPLES_PASSED and ANY_SAMPLES_PASSED_CONSERVATIVE with
  * SAMPLES_PASSED on desktop.
  *
  * OpenGL ES 3.0 spec 4.1.6:
  *     If the target of the query is ANY_SAMPLES_PASSED_CONSERVATIVE, an
  *     implementation may choose to use a less precise version of the test which
@@ -21,24 +20,24 @@ using namespace mozilla::dom;
  *     implementation-dependent cases.
  */
 
 static const char*
 GetQueryTargetEnumString(GLenum target)
 {
     switch (target)
     {
-        case LOCAL_GL_ANY_SAMPLES_PASSED:
-            return "ANY_SAMPLES_PASSED";
-        case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
-            return "ANY_SAMPLES_PASSED_CONSERVATIVE";
-        case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
-            return "TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN";
-        default:
-            break;
+    case LOCAL_GL_ANY_SAMPLES_PASSED:
+        return "ANY_SAMPLES_PASSED";
+    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+        return "ANY_SAMPLES_PASSED_CONSERVATIVE";
+    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
+        return "TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN";
+    default:
+        break;
     }
 
     MOZ_ASSERT(false, "Unknown query `target`.");
     return "UNKNOWN_QUERY_TARGET";
 }
 
 static inline GLenum
 SimulateOcclusionQueryTarget(const gl::GLContext* gl, GLenum target)
@@ -51,29 +50,33 @@ 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;
 }
 
-WebGLRefPtr<WebGLQuery>*
-WebGLContext::GetQueryTargetSlot(GLenum target)
+WebGLRefPtr<WebGLQuery>&
+WebGLContext::GetQuerySlotByTarget(GLenum target)
 {
+    /* This function assumes that target has been validated for either
+     * WebGL1 or WebGL2.
+     */
     switch (target) {
-        case LOCAL_GL_ANY_SAMPLES_PASSED:
-        case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
-            return &mActiveOcclusionQuery;
+    case LOCAL_GL_ANY_SAMPLES_PASSED:
+    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+        return mActiveOcclusionQuery;
 
-        case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
-            return &mActiveTransformFeedbackQuery;
+    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
+        return mActiveTransformFeedbackQuery;
+
+    default:
+        MOZ_CRASH("Should not get here.");
     }
-
-    return nullptr;
 }
 
 
 // -------------------------------------------------------------------------
 // Query Objects
 
 already_AddRefed<WebGLQuery>
 WebGL2Context::CreateQuery()
@@ -147,21 +150,18 @@ WebGL2Context::IsQuery(WebGLQuery* query
 }
 
 void
 WebGL2Context::BeginQuery(GLenum target, WebGLQuery* query)
 {
     if (IsContextLost())
         return;
 
-    WebGLRefPtr<WebGLQuery>* targetSlot = GetQueryTargetSlot(target);
-    if (!targetSlot) {
-        ErrorInvalidEnum("beginQuery: unknown query target");
+    if (!ValidateQueryTarget(target, "beginQuery"))
         return;
-    }
 
     if (!query) {
         /* From GLES's EXT_occlusion_query_boolean:
          *     BeginQueryEXT sets the active query object name for the query
          *     type given by <target> to <id>. If BeginQueryEXT is called with
          *     an <id> of zero, if the active query object name for <target> is
          *     non-zero (for the targets ANY_SAMPLES_PASSED_EXT and
          *     ANY_SAMPLES_PASSED_CONSERVATIVE_EXT, if the active query for
@@ -188,51 +188,50 @@ WebGL2Context::BeginQuery(GLenum target,
     if (query->HasEverBeenActive() &&
         query->mType != target)
     {
         ErrorInvalidOperation("beginQuery: Target doesn't match with the query"
                               " type.");
         return;
     }
 
-    if (*targetSlot) {
-        ErrorInvalidOperation("beginQuery: An other query already active.");
-        return;
-    }
+    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
+    WebGLQuery* activeQuery = querySlot.get();
+    if (activeQuery)
+        return ErrorInvalidOperation("beginQuery: An other query already active.");
 
     if (!query->HasEverBeenActive())
         query->mType = target;
 
     MakeContextCurrent();
 
     if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
         gl->fBeginQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN,
                         query->mGLName);
     } else {
         gl->fBeginQuery(SimulateOcclusionQueryTarget(gl, target),
                         query->mGLName);
     }
 
-    *targetSlot = query;
+    UpdateBoundQuery(target, query);
 }
 
 void
 WebGL2Context::EndQuery(GLenum target)
 {
     if (IsContextLost())
         return;
 
-    WebGLRefPtr<WebGLQuery>* targetSlot = GetQueryTargetSlot(target);
-    if (!targetSlot) {
-        ErrorInvalidEnum("endQuery: unknown query target");
+    if (!ValidateQueryTarget(target, "endQuery"))
         return;
-    }
 
-    if (!*targetSlot ||
-        target != (*targetSlot)->mType)
+    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
+    WebGLQuery* activeQuery = querySlot.get();
+
+    if (!activeQuery || target != activeQuery->mType)
     {
         /* From GLES's EXT_occlusion_query_boolean:
          *     marks the end of the sequence of commands to be tracked for the
          *     query type given by <target>. The active query object for
          *     <target> is updated to indicate that query results are not
          *     available, and the active query object name for <target> is reset
          *     to zero. When the commands issued prior to EndQueryEXT have
          *     completed and a final query result is available, the query object
@@ -250,40 +249,39 @@ WebGL2Context::EndQuery(GLenum target)
     MakeContextCurrent();
 
     if (target == LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN) {
         gl->fEndQuery(LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN);
     } else {
         gl->fEndQuery(SimulateOcclusionQueryTarget(gl, target));
     }
 
-    *targetSlot = nullptr;
+    UpdateBoundQuery(target, nullptr);
 }
 
 already_AddRefed<WebGLQuery>
 WebGL2Context::GetQuery(GLenum target, GLenum pname)
 {
     if (IsContextLost())
         return nullptr;
 
-    WebGLRefPtr<WebGLQuery>* targetSlot = GetQueryTargetSlot(target);
-    if (!targetSlot) {
-        ErrorInvalidEnum("getQuery: unknown query target");
+    if (!ValidateQueryTarget(target, "getQuery"))
         return nullptr;
-    }
 
     if (pname != LOCAL_GL_CURRENT_QUERY) {
         /* OpenGL ES 3.0 spec 6.1.7:
          *     pname must be CURRENT_QUERY.
          */
         ErrorInvalidEnum("getQuery: `pname` must be CURRENT_QUERY.");
         return nullptr;
     }
 
-    nsRefPtr<WebGLQuery> tmp = targetSlot->get();
+    WebGLRefPtr<WebGLQuery>& targetSlot = GetQuerySlotByTarget(target);
+
+    nsRefPtr<WebGLQuery> tmp = targetSlot.get();
     return tmp.forget();
 }
 
 void
 WebGL2Context::GetQueryParameter(JSContext*, WebGLQuery* query, GLenum pname,
                                  JS::MutableHandleValue retval)
 {
     retval.set(JS::NullValue());
@@ -348,8 +346,32 @@ WebGL2Context::GetQueryParameter(JSConte
         return;
 
     default:
         break;
     }
 
     ErrorInvalidEnum("getQueryObject: `pname` must be QUERY_RESULT{_AVAILABLE}.");
 }
+
+void
+WebGL2Context::UpdateBoundQuery(GLenum target, WebGLQuery* query)
+{
+    WebGLRefPtr<WebGLQuery>& querySlot = GetQuerySlotByTarget(target);
+    querySlot = query;
+}
+
+bool
+WebGL2Context::ValidateQueryTarget(GLenum target, const char* info)
+{
+    switch (target) {
+    case LOCAL_GL_ANY_SAMPLES_PASSED:
+    case LOCAL_GL_ANY_SAMPLES_PASSED_CONSERVATIVE:
+    case LOCAL_GL_TRANSFORM_FEEDBACK_PRIMITIVES_WRITTEN:
+        return true;
+
+    default:
+        ErrorInvalidEnumInfo(info, target);
+        return false;
+    }
+}
+
+} // namespace mozilla
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -63,33 +63,31 @@ class nsIDocShell;
 #define MINVALUE_GL_MAX_TEXTURE_IMAGE_UNITS           8     // Page 164
 #define MINVALUE_GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS    0     // Page 164
 #define MINVALUE_GL_MAX_RENDERBUFFER_SIZE             1024  // Different from the spec, which sets it to 1 on page 164
 #define MINVALUE_GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS  8     // Page 164
 
 namespace mozilla {
 
 class WebGLActiveInfo;
-class WebGLContextBoundObject;
 class WebGLContextLossHandler;
 class WebGLBuffer;
 class WebGLExtensionBase;
 class WebGLFramebuffer;
 class WebGLObserver;
 class WebGLProgram;
 class WebGLQuery;
 class WebGLRenderbuffer;
 class WebGLSampler;
 class WebGLShader;
 class WebGLShaderPrecisionFormat;
 class WebGLTexture;
 class WebGLTransformFeedback;
 class WebGLUniformLocation;
 class WebGLVertexArray;
-struct WebGLVertexAttribData;
 
 namespace dom {
 class Element;
 class ImageData;
 struct WebGLContextAttributes;
 template<typename> struct Nullable;
 }
 
@@ -931,17 +929,17 @@ protected:
 
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTarget(GLenum target);
     WebGLRefPtr<WebGLBuffer>& GetBufferSlotByTargetIndexed(GLenum target,
                                                            GLuint index);
 
 // -----------------------------------------------------------------------------
 // Queries (WebGL2ContextQueries.cpp)
 protected:
-    WebGLRefPtr<WebGLQuery>* GetQueryTargetSlot(GLenum target);
+    WebGLRefPtr<WebGLQuery>& GetQuerySlotByTarget(GLenum target);
 
     WebGLRefPtr<WebGLQuery> mActiveOcclusionQuery;
     WebGLRefPtr<WebGLQuery> mActiveTransformFeedbackQuery;
 
 // -----------------------------------------------------------------------------
 // State and State Requests (WebGLContextState.cpp)
 public:
     void Disable(GLenum cap);
@@ -1384,16 +1382,17 @@ private:
 private:
     // -------------------------------------------------------------------------
     // Context customization points
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, GLsizei* alignment, const char* info) = 0;
     virtual bool ValidateBufferTarget(GLenum target, const char* info) = 0;
     virtual bool ValidateBufferIndexedTarget(GLenum target, const char* info) = 0;
     virtual bool ValidateBufferForTarget(GLenum target, WebGLBuffer* buffer, const char* info) = 0;
     virtual bool ValidateBufferUsageEnum(GLenum usage, const char* info) = 0;
+    virtual bool ValidateQueryTarget(GLenum usage, const char* info) = 0;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0;
 
 protected:
     int32_t MaxTextureSizeForTarget(TexTarget target) const {
         return (target == LOCAL_GL_TEXTURE_2D) ? mGLMaxTextureSize
                                                : mGLMaxCubeMapTextureSize;
     }
 
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -1944,16 +1944,31 @@ WebGLContext::InitAndValidateGL()
     for (int32_t index = 0; index < mGLMaxVertexAttribs; ++index) {
         VertexAttrib4f(index, 0, 0, 0, 1);
     }
 
     mDefaultVertexArray = WebGLVertexArray::Create(this);
     mDefaultVertexArray->mAttribs.SetLength(mGLMaxVertexAttribs);
     mBoundVertexArray = mDefaultVertexArray;
 
+    // OpenGL core profiles remove the default VAO object from version
+    // 4.0.0. We create a default VAO for all core profiles,
+    // regardless of version.
+    //
+    // GL Spec 4.0.0:
+    // (https://www.opengl.org/registry/doc/glspec40.core.20100311.pdf)
+    // in Section E.2.2 "Removed Features", pg 397: "[...] The default
+    // vertex array object (the name zero) is also deprecated. [...]"
+
+    if (gl->IsCoreProfile()) {
+        MakeContextCurrent();
+        mDefaultVertexArray->GenVertexArray();
+        mDefaultVertexArray->BindVertexArray();
+    }
+
     if (mLoseContextOnMemoryPressure)
         mContextObserver->RegisterMemoryPressureEvent();
 
     return true;
 }
 
 bool
 WebGLContext::ValidateFramebufferTarget(GLenum target,
--- a/dom/canvas/WebGLFramebuffer.h
+++ b/dom/canvas/WebGLFramebuffer.h
@@ -9,17 +9,16 @@
 #include "mozilla/LinkedList.h"
 #include "nsWrapperCache.h"
 #include "WebGLBindableName.h"
 #include "WebGLObjectModel.h"
 #include "WebGLStrongTypes.h"
 
 namespace mozilla {
 
-class WebGLFramebufferAttachable;
 class WebGLRenderbuffer;
 class WebGLTexture;
 
 namespace gl {
     class GLContext;
 }
 
 class WebGLFramebuffer final
--- a/dom/canvas/WebGLObjectModel.h
+++ b/dom/canvas/WebGLObjectModel.h
@@ -7,17 +7,16 @@
 #define WEBGLOBJECTMODEL_H_
 
 #include "nsCycleCollectionNoteChild.h"
 #include "nsICanvasRenderingContextInternal.h"
 #include "WebGLTypes.h"
 
 namespace mozilla {
 
-class WebGLBuffer;
 class WebGLContext;
 
 /* Each WebGL object class WebGLFoo wants to:
  *  - inherit WebGLRefCountedObject<WebGLFoo>
  *  - implement a Delete() method
  *  - have its destructor call DeleteOnce()
  *
  * This base class provides two features to WebGL object types:
--- a/dom/canvas/WebGLQuery.cpp
+++ b/dom/canvas/WebGLQuery.cpp
@@ -35,20 +35,19 @@ WebGLQuery::Delete()
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteQueries(1, &mGLName);
     LinkedListElement<WebGLQuery>::removeFrom(mContext->mQueries);
 }
 
 bool
 WebGLQuery::IsActive() const
 {
-    WebGLRefPtr<WebGLQuery>* targetSlot = mContext->GetQueryTargetSlot(mType);
+    WebGLRefPtr<WebGLQuery>& targetSlot = mContext->GetQuerySlotByTarget(mType);
 
-    MOZ_ASSERT(targetSlot, "unknown query object's type");
-    return targetSlot && *targetSlot == this;
+    return targetSlot.get() == this;
 }
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLQuery)
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLQuery, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLQuery, Release)
 
 } // namespace mozilla
--- a/dom/canvas/WebGLShaderPrecisionFormat.h
+++ b/dom/canvas/WebGLShaderPrecisionFormat.h
@@ -5,18 +5,16 @@
 
 #ifndef WEBGL_SHADER_PRECISION_FORMAT_H_
 #define WEBGL_SHADER_PRECISION_FORMAT_H_
 
 #include "WebGLObjectModel.h"
 
 namespace mozilla {
 
-class WebGLBuffer;
-
 class WebGLShaderPrecisionFormat final
     : public WebGLContextBoundObject
 {
 public:
     WebGLShaderPrecisionFormat(WebGLContext* context, GLint rangeMin,
                                GLint rangeMax, GLint precision)
         : WebGLContextBoundObject(context)
         , mRangeMin(rangeMin)
--- a/dom/canvas/WebGLStrongTypes.h
+++ b/dom/canvas/WebGLStrongTypes.h
@@ -440,14 +440,18 @@ STRONG_GLENUM_BEGIN(RBParam)
 STRONG_GLENUM_END(RBParam)
 
 STRONG_GLENUM_BEGIN(VAOBinding)
     STRONG_GLENUM_VALUE(NONE),
     STRONG_GLENUM_VALUE(VERTEX_ARRAY_BINDING),
 STRONG_GLENUM_END(VAOBinding)
 
 STRONG_GLENUM_BEGIN(BufferBinding)
-    STRONG_GLENUM_VALUE(NONE),
-    STRONG_GLENUM_VALUE(ARRAY_BUFFER),
-    STRONG_GLENUM_VALUE(ELEMENT_ARRAY_BUFFER),
+    STRONG_GLENUM_VALUE(NONE),                      // 0x0000
+    STRONG_GLENUM_VALUE(ARRAY_BUFFER),              // 0x8892
+    STRONG_GLENUM_VALUE(ELEMENT_ARRAY_BUFFER),      // 0x8893
+    STRONG_GLENUM_VALUE(PIXEL_PACK_BUFFER),         // 0x88EB
+    STRONG_GLENUM_VALUE(PIXEL_UNPACK_BUFFER),       // 0x88EC
+    STRONG_GLENUM_VALUE(UNIFORM_BUFFER),            // 0x8A11
+    STRONG_GLENUM_VALUE(TRANSFORM_FEEDBACK_BUFFER), // 0x8C8E
 STRONG_GLENUM_END(BufferBinding)
 
 #endif // WEBGL_STRONG_TYPES_H_
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -14,17 +14,16 @@
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "GraphicsFilter.h"
 #include "mozilla/RefPtr.h"
 
 #define NS_ICANVASRENDERINGCONTEXTINTERNAL_IID \
 { 0x3cc9e801, 0x1806, 0x4ff6, \
   { 0x86, 0x14, 0xf9, 0xd0, 0xf4, 0xfb, 0x3b, 0x08 } }
 
-class gfxContext;
 class gfxASurface;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 namespace layers {
 class CanvasLayer;
 class LayerManager;
 }
--- a/dom/datastore/DataStoreCursor.h
+++ b/dom/datastore/DataStoreCursor.h
@@ -4,18 +4,16 @@
 
 #ifndef mozilla_dom_DataStoreCursor_h
 #define mozilla_dom_DataStoreCursor_h
 
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionParticipant.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class Promise;
 class DataStore;
--- a/dom/datastore/DataStoreDB.h
+++ b/dom/datastore/DataStoreDB.h
@@ -16,17 +16,16 @@
 #define DATASTOREDB_REVISION       "revision"
 
 namespace mozilla {
 namespace dom {
 
 namespace indexedDB {
 class IDBDatabase;
 class IDBFactory;
-class IDBObjectStore;
 class IDBOpenDBRequest;
 class IDBTransaction;
 }
 
 class DataStoreDBCallback;
 
 class DataStoreDB final : public nsIDOMEventListener
 {
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -28,21 +28,20 @@ class nsPresContext;
 namespace mozilla {
 namespace dom {
 
 class EventTarget;
 class WantsPopupControlCheck;
 #define GENERATED_EVENT(EventClass_) class EventClass_;
 #include "mozilla/dom/GeneratedEventList.h"
 #undef GENERATED_EVENT
-// ExtendableEvent and InstallEvent are ServiceWorker events that are not
-// autogenerated since they have some extra methods.
+// ExtendableEvent is a ServiceWorker event that is not
+// autogenerated since it has some extra methods.
 namespace workers {
 class ExtendableEvent;
-class InstallEvent;
 } // namespace workers
 
 // Dummy class so we can cast through it to get from nsISupports to
 // Event subclasses with only two non-ambiguous static casts.
 class EventBase : public nsIDOMEvent
 {
 };
 
@@ -97,28 +96,23 @@ public:
 #define GENERATED_EVENT(EventClass_) \
   virtual EventClass_* As##EventClass_()  \
   {                                       \
     return nullptr;                       \
   }
 #include "mozilla/dom/GeneratedEventList.h"
 #undef GENERATED_EVENT
 
-  // ExtendableEvent and InstallEvent are ServiceWorker events that are not
-  // autogenerated since they have some extra methods.
+  // ExtendableEvent is a ServiceWorker event that is not
+  // autogenerated since it has some extra methods.
   virtual workers::ExtendableEvent* AsExtendableEvent()
   {
     return nullptr;
   }
 
-  virtual workers::InstallEvent* AsInstallEvent()
-  {
-    return nullptr;
-  }
-
   // nsIDOMEvent Interface
   NS_DECL_NSIDOMEVENT
 
   void InitPresContextData(nsPresContext* aPresContext);
 
   // Returns true if the event should be trusted.
   bool Init(EventTarget* aGlobal);
 
--- a/dom/events/EventDispatcher.h
+++ b/dom/events/EventDispatcher.h
@@ -11,17 +11,16 @@
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 // Microsoft's API Name hackery sucks
 #undef CreateEvent
 
 class nsIContent;
 class nsIDOMEvent;
-class nsIScriptGlobalObject;
 class nsPresContext;
 
 template<class E> class nsCOMArray;
 
 namespace mozilla {
 namespace dom {
 class EventTarget;
 } // namespace dom
--- a/dom/events/EventListenerManager.h
+++ b/dom/events/EventListenerManager.h
@@ -14,17 +14,16 @@
 #include "nsCycleCollectionParticipant.h"
 #include "nsGkAtoms.h"
 #include "nsIDOMEventListener.h"
 #include "nsTObserverArray.h"
 
 class nsIDocShell;
 class nsIDOMEvent;
 class nsIEventListenerInfo;
-class nsIScriptContext;
 class nsPIDOMWindow;
 class JSTracer;
 
 struct EventTypeData;
 
 template<class T> class nsCOMArray;
 
 namespace mozilla {
--- a/dom/events/EventTarget.h
+++ b/dom/events/EventTarget.h
@@ -6,17 +6,16 @@
 #ifndef mozilla_dom_EventTarget_h_
 #define mozilla_dom_EventTarget_h_
 
 #include "nsIDOMEventTarget.h"
 #include "nsWrapperCache.h"
 #include "nsIAtom.h"
 
 class nsIDOMWindow;
-class nsIDOMEventListener;
 
 namespace mozilla {
 
 class ErrorResult;
 class EventListenerManager;
 
 namespace dom {
 
--- a/dom/events/IMEStateManager.h
+++ b/dom/events/IMEStateManager.h
@@ -8,17 +8,16 @@
 
 #include "mozilla/EventForwards.h"
 #include "nsIWidget.h"
 
 class nsIContent;
 class nsIDOMMouseEvent;
 class nsIEditor;
 class nsINode;
-class nsPIDOMWindow;
 class nsPresContext;
 class nsISelection;
 
 namespace mozilla {
 
 class EventDispatchingCallback;
 class IMEContentObserver;
 class TextCompositionArray;
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -15,17 +15,16 @@
 #include "nsString.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/workers/bindings/WorkerFeature.h"
 
-class nsIOutputStream;
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
 class InternalRequest;
 class OwningArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
--- a/dom/fetch/FetchDriver.h
+++ b/dom/fetch/FetchDriver.h
@@ -19,17 +19,16 @@
 class nsIDocument;
 class nsIOutputStream;
 class nsILoadGroup;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
-class BlobSet;
 class InternalRequest;
 class InternalResponse;
 
 class FetchDriverObserver
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchDriverObserver);
   virtual void OnResponseAvailable(InternalResponse* aResponse) = 0;
--- a/dom/fetch/Headers.h
+++ b/dom/fetch/Headers.h
@@ -9,18 +9,16 @@
 
 #include "mozilla/dom/HeadersBinding.h"
 
 #include "nsClassHashtable.h"
 #include "nsWrapperCache.h"
 
 #include "InternalHeaders.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 template<typename T> class MozMap;
 class HeadersOrByteStringSequenceSequenceOrByteStringMozMap;
--- a/dom/fetch/InternalHeaders.h
+++ b/dom/fetch/InternalHeaders.h
@@ -9,26 +9,23 @@
 
 // needed for HeadersGuardEnum.
 #include "mozilla/dom/HeadersBinding.h"
 #include "mozilla/dom/UnionTypes.h"
 
 #include "nsClassHashtable.h"
 #include "nsWrapperCache.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 template<typename T> class MozMap;
-class HeadersOrByteStringSequenceSequenceOrByteStringMozMap;
 
 class InternalHeaders final
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(InternalHeaders)
 
 public:
   struct Entry
   {
--- a/dom/fetch/InternalRequest.h
+++ b/dom/fetch/InternalRequest.h
@@ -14,19 +14,16 @@
 #include "nsIInputStream.h"
 #include "nsISupportsImpl.h"
 #ifdef DEBUG
 #include "nsIURLParser.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
 #endif
 
-class nsIDocument;
-class nsPIDOMWindow;
-
 namespace mozilla {
 namespace dom {
 
 /*
  * The mapping of RequestContext and nsContentPolicyType is currently as the
  * following.  Note that this mapping is not perfect yet (see the TODO comments
  * below for examples), so for now we'll have to keep both an mContext and an
  * mContentPolicyType, because we cannot have a two way conversion.
@@ -79,17 +76,16 @@ namespace dom {
  * TODO: Add a content type for location
  * TODO: Add a content type for hyperlink
  * TODO: Add a content type for form
  * TODO: Add a content type for favicon
  * TODO: Add a content type for download
  * TODO: Split TYPE_OBJECT into TYPE_EMBED and TYPE_OBJECT
  */
 
-class FetchBodyStream;
 class Request;
 
 #define kFETCH_CLIENT_REFERRER_STR "about:client"
 
 class InternalRequest final
 {
   friend class Request;
 
--- a/dom/fetch/Request.h
+++ b/dom/fetch/Request.h
@@ -11,24 +11,21 @@
 #include "nsWrapperCache.h"
 
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/InternalRequest.h"
 // Required here due to certain WebIDL enums/classes being declared in both
 // files.
 #include "mozilla/dom/RequestBinding.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 namespace dom {
 
 class Headers;
 class InternalHeaders;
-class Promise;
 class RequestOrUSVString;
 
 class Request final : public nsISupports
                     , public FetchBody<Request>
                     , public nsWrapperCache
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
--- a/dom/fetch/Response.h
+++ b/dom/fetch/Response.h
@@ -9,25 +9,21 @@
 #include "nsWrapperCache.h"
 #include "nsISupportsImpl.h"
 
 #include "mozilla/dom/Fetch.h"
 #include "mozilla/dom/ResponseBinding.h"
 
 #include "InternalResponse.h"
 
-class nsPIDOMWindow;
-
 namespace mozilla {
 namespace dom {
 
-class ArrayBufferOrArrayBufferViewOrUSVStringOrURLSearchParams;
 class Headers;
 class InternalHeaders;
-class Promise;
 
 class Response final : public nsISupports
                      , public FetchBody<Response>
                      , public nsWrapperCache
 {
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Response)
 
--- a/dom/filesystem/FileSystemTaskBase.h
+++ b/dom/filesystem/FileSystemTaskBase.h
@@ -14,17 +14,16 @@
 class nsIDOMFile;
 
 namespace mozilla {
 namespace dom {
 
 class BlobParent;
 class FileSystemBase;
 class FileSystemParams;
-class Promise;
 
 /*
  * The base class to implement a Task class.
  * The task is used to handle the OOP (out of process) operations.
  * The file system operations can only be performed in the parent process. When
  * performing such a parent-process-only operation, a task will delivered the
  * operation to the parent process if needed.
  *
--- a/dom/fmradio/FMRadio.h
+++ b/dom/fmradio/FMRadio.h
@@ -9,17 +9,16 @@
 #include "AudioChannelAgent.h"
 #include "FMRadioCommon.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/HalTypes.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWeakReference.h"
 
 class nsPIDOMWindow;
-class nsIScriptContext;
 
 BEGIN_FMRADIO_NAMESPACE
 
 class DOMRequest;
 
 class FMRadio final : public DOMEventTargetHelper
                     , public hal::SwitchObserver
                     , public FMRadioEventObserver
--- a/dom/gamepad/GamepadService.h
+++ b/dom/gamepad/GamepadService.h
@@ -11,18 +11,16 @@
 #include "nsCOMArray.h"
 #include "nsIGamepadServiceTest.h"
 #include "nsGlobalWindow.h"
 #include "nsIFocusManager.h"
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
 
-class nsIDOMDocument;
-
 namespace mozilla {
 namespace dom {
 
 class EventTarget;
 
 class GamepadService : public nsIObserver
 {
  public:
--- a/dom/html/HTMLAllCollection.h
+++ b/dom/html/HTMLAllCollection.h
@@ -16,19 +16,16 @@
 #include <stdint.h>
 
 class nsContentList;
 class nsHTMLDocument;
 class nsIContent;
 class nsINode;
 
 namespace mozilla {
-
-class ErrorResult;
-
 namespace dom {
 
 class OwningNodeOrHTMLCollection;
 template<typename> struct Nullable;
 
 class HTMLAllCollection final : public nsISupports
                               , public nsWrapperCache
 {
--- a/dom/html/HTMLFrameElement.h
+++ b/dom/html/HTMLFrameElement.h
@@ -6,18 +6,16 @@
 #ifndef mozilla_dom_HTMLFrameElement_h
 #define mozilla_dom_HTMLFrameElement_h
 
 #include "mozilla/Attributes.h"
 #include "nsIDOMHTMLFrameElement.h"
 #include "nsGenericHTMLFrameElement.h"
 #include "nsGkAtoms.h"
 
-class nsIDOMDocument;
-
 namespace mozilla {
 namespace dom {
 
 class HTMLFrameElement final : public nsGenericHTMLFrameElement,
                                public nsIDOMHTMLFrameElement
 {
 public:
   using nsGenericHTMLFrameElement::SwapFrameLoaders;
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -20,17 +20,16 @@
 #include "mozilla/dom/HTMLInputElementBinding.h"
 #include "nsIFilePicker.h"
 #include "nsIContentPrefService2.h"
 #include "mozilla/Decimal.h"
 #include "nsContentUtils.h"
 #include "nsTextEditorState.h"
 
 class nsIRadioGroupContainer;
-class nsIRadioGroupVisitor;
 class nsIRadioVisitor;
 
 namespace mozilla {
 
 class EventChainPostVisitor;
 class EventChainPreVisitor;
 
 namespace dom {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -655,17 +655,17 @@ protected:
     explicit WakeLockBoolWrapper(bool val = false)
       : mValue(val), mCanPlay(true), mOuter(nullptr) {}
 
     ~WakeLockBoolWrapper();
 
     void SetOuter(HTMLMediaElement* outer) { mOuter = outer; }
     void SetCanPlay(bool aCanPlay);
 
-    operator bool() const { return mValue; }
+    MOZ_IMPLICIT operator bool() const { return mValue; }
 
     WakeLockBoolWrapper& operator=(bool val);
 
     bool operator !() const { return !mValue; }
 
     static void TimerCallback(nsITimer* aTimer, void* aClosure);
 
   private:
--- a/dom/html/HTMLSourceElement.h
+++ b/dom/html/HTMLSourceElement.h
@@ -12,17 +12,16 @@
 #include "nsGenericHTMLElement.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 
 class nsMediaList;
 
 namespace mozilla {
 namespace dom {
 
-class ResponsiveImageSelector;
 class HTMLSourceElement final : public nsGenericHTMLElement,
                                 public nsIDOMHTMLSourceElement
 {
 public:
   explicit HTMLSourceElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
--- a/dom/html/HTMLTableCellElement.h
+++ b/dom/html/HTMLTableCellElement.h
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef mozilla_dom_HTMLTableCellElement_h
 #define mozilla_dom_HTMLTableCellElement_h
 
 #include "mozilla/Attributes.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMHTMLTableCellElement.h"
 
-class nsIDOMHTMLTableRowElement;
-
 namespace mozilla {
 namespace dom {
 
 class HTMLTableElement;
 
 class HTMLTableCellElement final : public nsGenericHTMLElement,
                                    public nsIDOMHTMLTableCellElement
 {
--- a/dom/html/HTMLTableRowElement.h
+++ b/dom/html/HTMLTableRowElement.h
@@ -3,17 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef mozilla_dom_HTMLTableRowElement_h
 #define mozilla_dom_HTMLTableRowElement_h
 
 #include "mozilla/Attributes.h"
 #include "nsGenericHTMLElement.h"
 
-class nsIDOMHTMLTableElement;
 class nsContentList;
 
 namespace mozilla {
 namespace dom {
 
 class HTMLTableSectionElement;
 
 class HTMLTableRowElement final : public nsGenericHTMLElement
--- a/dom/html/UndoManager.h
+++ b/dom/html/UndoManager.h
@@ -11,17 +11,16 @@
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIContent.h"
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "mozilla/dom/Nullable.h"
 
 class nsITransactionManager;
-class nsIMutationObserver;
 
 namespace mozilla {
 class ErrorResult;
 namespace dom {
 
 class DOMTransaction;
 class DOMTransactionCallback;
 
--- a/dom/html/nsFormSubmission.h
+++ b/dom/html/nsFormSubmission.h
@@ -7,22 +7,17 @@
 
 #include "mozilla/Attributes.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 
 class nsIURI;
 class nsIInputStream;
 class nsGenericHTMLElement;
-class nsILinkHandler;
 class nsIContent;
-class nsIFormControl;
-class nsIDOMHTMLElement;
-class nsIDocShell;
-class nsIRequest;
 class nsISaveAsCharset;
 class nsIMultiplexInputStream;
 
 namespace mozilla {
 namespace dom {
 class File;
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -17,17 +17,16 @@
 #include "pldhash.h"
 #include "nsIHttpChannel.h"
 #include "nsHTMLStyleSheet.h"
 
 #include "nsICommandManager.h"
 #include "mozilla/dom/HTMLSharedElement.h"
 
 class nsIEditor;
-class nsIParser;
 class nsIURI;
 class nsIDocShell;
 class nsICachingChannel;
 class nsIWyciwygChannel;
 class nsILoadGroup;
 
 namespace mozilla {
 namespace dom {
--- a/dom/html/nsIForm.h
+++ b/dom/html/nsIForm.h
@@ -4,18 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIForm_h___
 #define nsIForm_h___
 
 #include "nsISupports.h"
 #include "nsAString.h"
 
 class nsIFormControl;
-class nsISimpleEnumerator;
-class nsIURI;
 
 #define NS_FORM_METHOD_GET  0
 #define NS_FORM_METHOD_POST 1
 #define NS_FORM_ENCTYPE_URLENCODED 0
 #define NS_FORM_ENCTYPE_MULTIPART  1
 #define NS_FORM_ENCTYPE_TEXTPLAIN  2
 
 // IID for the nsIForm interface
--- a/dom/html/nsIFormControl.h
+++ b/dom/html/nsIFormControl.h
@@ -3,18 +3,16 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIFormControl_h___
 #define nsIFormControl_h___
 
 #include "nsISupports.h"
 class nsIDOMHTMLFormElement;
 class nsPresState;
-class nsString;
-class nsIFormProcessor;
 class nsFormSubmission;
 
 namespace mozilla {
 namespace dom {
 class Element;
 class HTMLFieldSetElement;
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/nsIHTMLCollection.h
+++ b/dom/html/nsIHTMLCollection.h
@@ -10,18 +10,16 @@
 #include "nsTArrayForwardDeclare.h"
 #include "nsWrapperCache.h"
 #include "js/TypeDecls.h"
 
 class nsINode;
 class nsString;
 
 namespace mozilla {
-class ErrorResult;
-
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 // IID for the nsIHTMLCollection interface
 #define NS_IHTMLCOLLECTION_IID \
 { 0x4e169191, 0x5196, 0x4e17, \
--- a/dom/html/nsIHTMLDocument.h
+++ b/dom/html/nsIHTMLDocument.h
@@ -4,22 +4,19 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIHTMLDocument_h
 #define nsIHTMLDocument_h
 
 #include "nsISupports.h"
 #include "nsCompatibility.h"
 
-class nsIDOMHTMLFormElement;
 class nsIContent;
-class nsIScriptElement;
 class nsIEditor;
 class nsContentList;
-class nsWrapperCache;
 
 #define NS_IHTMLDOCUMENT_IID \
 { 0xcf814492, 0x303c, 0x4718, \
   { 0x9a, 0x3e, 0x39, 0xbc, 0xd5, 0x2c, 0x10, 0xdb } }
 
 /**
  * HTML document extensions to nsIDocument.
  */
--- a/dom/html/nsIRadioGroupContainer.h
+++ b/dom/html/nsIRadioGroupContainer.h
@@ -2,17 +2,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef nsIRadioGroupContainer_h___
 #define nsIRadioGroupContainer_h___
 
 #include "nsISupports.h"
 
-class nsString;
 class nsIRadioVisitor;
 class nsIFormControl;
 
 namespace mozilla {
 namespace dom {
 class HTMLInputElement;
 }
 }
--- a/dom/imptests/testharness.js
+++ b/dom/imptests/testharness.js
@@ -459,16 +459,84 @@ policies and contribution forms [3].
                     if (value instanceof AssertionError) {
                         throw value;
                     }
                     assert(false, "promise_test", null,
                            "Unhandled rejection with value: ${value}", {value:value});
                 }));
     }
 
+    /**
+     * This constructor helper allows DOM events to be handled using Promises,
+     * which can make it a lot easier to test a very specific series of events,
+     * including ensuring that unexpected events are not fired at any point.
+     */
+    function EventWatcher(test, watchedNode, eventTypes)
+    {
+        if (typeof eventTypes == 'string') {
+            eventTypes = [eventTypes];
+        }
+
+        var waitingFor = null;
+
+        var eventHandler = test.step_func(function(evt) {
+            assert_true(!!waitingFor,
+                        'Not expecting event, but got ' + evt.type + ' event');
+            assert_equals(evt.type, waitingFor.types[0],
+                          'Expected ' + waitingFor.types[0] + ' event, but got ' +
+                          evt.type + ' event instead');
+            if (waitingFor.types.length > 1) {
+                // Pop first event from array
+                waitingFor.types.shift();
+                return;
+            }
+            // We need to null out waitingFor before calling the resolve function
+            // since the Promise's resolve handlers may call wait_for() which will
+            // need to set waitingFor.
+            var resolveFunc = waitingFor.resolve;
+            waitingFor = null;
+            resolveFunc(evt);
+        });
+
+        for (var i = 0; i < eventTypes.length; i++) {
+            watchedNode.addEventListener(eventTypes[i], eventHandler);
+        }
+
+        /**
+         * Returns a Promise that will resolve after the specified event or
+         * series of events has occured.
+         */
+        this.wait_for = function(types) {
+            if (waitingFor) {
+                return Promise.reject('Already waiting for an event or events');
+            }
+            if (typeof types == 'string') {
+                types = [types];
+            }
+            return new Promise(function(resolve, reject) {
+                waitingFor = {
+                    types: types,
+                    resolve: resolve,
+                    reject: reject
+                };
+            });
+        };
+
+        function stop_watching() {
+            for (var i = 0; i < eventTypes.length; i++) {
+                watchedNode.removeEventListener(eventTypes[i], eventHandler);
+            }
+        };
+
+        test.add_cleanup(stop_watching);
+
+        return this;
+    }
+    expose(EventWatcher, 'EventWatcher');
+
     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) {
--- a/dom/indexedDB/ActorsChild.h
+++ b/dom/indexedDB/ActorsChild.h
@@ -37,17 +37,16 @@ namespace indexedDB {
 class FileInfo;
 class IDBCursor;
 class IDBDatabase;
 class IDBFactory;
 class IDBMutableFile;
 class IDBOpenDBRequest;
 class IDBRequest;
 class Key;
-class PBackgroundIDBFileChild;
 class PermissionRequestChild;
 class PermissionRequestParent;
 class SerializedStructuredCloneReadInfo;
 
 class ThreadLocal
 {
   friend class nsAutoPtr<ThreadLocal>;
   friend class IDBFactory;
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -5097,23 +5097,20 @@ private:
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   void
   AssertIsOnOwningThread() const
   {
     AssertIsOnBackgroundThread();
-
-#ifdef DEBUG
     MOZ_ASSERT(mOwningThread);
-    bool current;
+    DebugOnly<bool> current;
     MOZ_ASSERT(NS_SUCCEEDED(mOwningThread->IsOnCurrentThread(&current)));
     MOZ_ASSERT(current);
-#endif
   }
 
   void
   NoteActorDestroyed()
   {
     AssertIsOnOwningThread();
 
     mActorDestroyed = true;
@@ -5123,17 +5120,18 @@ public:
   bool
   IsActorDestroyed() const
   {
     AssertIsOnOwningThread();
 
     return mActorDestroyed;
   }
 
-  // May be called on any thread.
+  // May be called on any thread, but you should call IsActorDestroyed() if
+  // you know you're on the background thread because it is slightly faster.
   bool
   OperationMayProceed() const
   {
     return mOperationMayProceed;
   }
 
   const nsID&
   BackgroundChildLoggingId() const
@@ -6624,16 +6622,19 @@ private:
 
   void
   EnsureDatabaseActor();
 
   nsresult
   EnsureDatabaseActorIsAlive();
 
   void
+  CleanupBackgroundThreadObjects(bool aInvalidate);
+
+  void
   MetadataToSpec(DatabaseSpec& aSpec);
 
   void
   AssertMetadataConsistency(const FullDatabaseMetadata* aMetadata)
 #ifdef DEBUG
   ;
 #else
   { }
@@ -7732,17 +7733,17 @@ public:
   }
 
   static bool
   IsShuttingDownOnMainThread()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     if (sInstance) {
-      return sInstance->mShutdownRequested;
+      return sInstance->IsShuttingDown();
     }
 
     return QuotaManager::IsShuttingDown();
   }
 
   static bool
   IsShuttingDownOnNonMainThread()
   {
@@ -7807,23 +7808,21 @@ private:
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 };
 
 class QuotaClient::ShutdownWorkThreadsRunnable final
   : public nsRunnable
 {
   nsRefPtr<QuotaClient> mQuotaClient;
-  bool mHasRequestedShutDown;
 
 public:
 
   explicit ShutdownWorkThreadsRunnable(QuotaClient* aQuotaClient)
     : mQuotaClient(aQuotaClient)
-    , mHasRequestedShutDown(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
     MOZ_ASSERT(aQuotaClient->mShutdownRequested);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -11066,20 +11065,16 @@ Factory::~Factory()
 already_AddRefed<Factory>
 Factory::Create(const LoggingInfo& aLoggingInfo)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!QuotaClient::IsShuttingDownOnNonMainThread());
 
   // If this is the first instance then we need to do some initialization.
   if (!sFactoryInstanceCount) {
-    if (!gConnectionPool) {
-      gConnectionPool = new ConnectionPool();
-    }
-
     MOZ_ASSERT(!gLiveDatabaseHashtable);
     gLiveDatabaseHashtable = new DatabaseActorHashtable();
 
     MOZ_ASSERT(!gLoggingInfoHashtable);
     gLoggingInfoHashtable = new DatabaseLoggingInfoHashtable();
 
 #ifdef DEBUG
     if (kDEBUGThreadPriority != nsISupportsPriority::PRIORITY_NORMAL) {
@@ -11901,16 +11896,20 @@ Database::RecvPBackgroundIDBTransactionC
   MOZ_ASSERT(!mClosed);
 
   if (IsInvalidated()) {
     // This is an expected race. We don't want the child to die here, just don't
     // actually do any work.
     return true;
   }
 
+  if (!gConnectionPool) {
+    gConnectionPool = new ConnectionPool();
+  }
+
   auto* transaction = static_cast<NormalTransaction*>(aActor);
 
   nsRefPtr<StartTransactionOp> startOp = new StartTransactionOp(transaction);
 
   uint64_t transactionId =
     gConnectionPool->Start(GetLoggingInfo()->Id(),
                            mMetadata->mDatabaseId,
                            transaction->LoggingSerialNumber(),
@@ -15313,38 +15312,32 @@ WaitForTransactionsRunnable::Run()
 NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownWorkThreadsRunnable,
                              nsRunnable)
 
 NS_IMETHODIMP
 QuotaClient::
 ShutdownWorkThreadsRunnable::Run()
 {
   if (NS_IsMainThread()) {
-    MOZ_ASSERT(mHasRequestedShutDown);
     MOZ_ASSERT(QuotaClient::GetInstance() == mQuotaClient);
     MOZ_ASSERT(mQuotaClient->mShutdownRunnable == this);
 
     mQuotaClient->mShutdownRunnable = nullptr;
     mQuotaClient = nullptr;
 
     return NS_OK;
   }
 
   AssertIsOnBackgroundThread();
 
-  if (!mHasRequestedShutDown) {
-    mHasRequestedShutDown = true;
-
-    nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
-    if (connectionPool) {
-      connectionPool->Shutdown();
-
-      gConnectionPool = nullptr;
-    }
-
+  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+  if (connectionPool) {
+    connectionPool->Shutdown();
+
+    gConnectionPool = nullptr;
   }
 
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
 
   return NS_OK;
 }
 
 /*******************************************************************************
@@ -17119,25 +17112,25 @@ OpenDatabaseOp::OpenDatabaseOp(Factory* 
   }
 }
 
 void
 OpenDatabaseOp::ActorDestroy(ActorDestroyReason aWhy)
 {
   AssertIsOnOwningThread();
 
-  if (mDatabase && aWhy != Deletion) {
-    mDatabase->Invalidate();
+  FactoryOp::ActorDestroy(aWhy);
+
+  if (aWhy != Deletion) {
+    CleanupBackgroundThreadObjects(/* aInvalidate */ true);
   }
 
   if (mVersionChangeOp) {
     mVersionChangeOp->NoteActorDestroyed();
   }
-
-  FactoryOp::ActorDestroy(aWhy);
 }
 
 nsresult
 OpenDatabaseOp::QuotaManagerOpen()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mState == State_OpenPending);
   MOZ_ASSERT(!mOfflineStorage);
@@ -17640,17 +17633,16 @@ OpenDatabaseOp::BeginVersionChange()
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_BeginVersionChange);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
   MOZ_ASSERT(mMetadata->mCommonMetadata.version() <= mRequestedVersion);
   MOZ_ASSERT(!mDatabase);
   MOZ_ASSERT(!mVersionChangeTransaction);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
-      !OperationMayProceed() ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   EnsureDatabaseActor();
 
   if (mDatabase->IsInvalidated()) {
@@ -17758,17 +17750,18 @@ OpenDatabaseOp::DispatchToWorkThread()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_WaitingForTransactionsToComplete);
   MOZ_ASSERT(mVersionChangeTransaction);
   MOZ_ASSERT(mVersionChangeTransaction->GetMode() ==
                IDBTransaction::VERSION_CHANGE);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
-  if (IsActorDestroyed()) {
+  if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
+      IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State_DatabaseWorkVersionChange;
 
   // Intentionally empty.
   nsTArray<nsString> objectStoreNames;
@@ -17777,16 +17770,20 @@ OpenDatabaseOp::DispatchToWorkThread()
     mVersionChangeTransaction->LoggingSerialNumber();
   const nsID& backgroundChildLoggingId =
     mVersionChangeTransaction->GetLoggingInfo()->Id();
 
   if (NS_WARN_IF(!mDatabase->RegisterTransaction(mVersionChangeTransaction))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  if (!gConnectionPool) {
+    gConnectionPool = new ConnectionPool();
+  }
+
   nsRefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
 
   uint64_t transactionId =
     gConnectionPool->Start(backgroundChildLoggingId,
                            mVersionChangeTransaction->DatabaseId(),
                            loggingSerialNumber,
                            objectStoreNames,
                            /* aIsWriteTransaction */ true,
@@ -17806,17 +17803,16 @@ OpenDatabaseOp::SendUpgradeNeeded()
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_DatabaseWorkVersionChange);
   MOZ_ASSERT(mVersionChangeTransaction);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
   MOZ_ASSERT_IF(!IsActorDestroyed(), mDatabase);
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
-      !OperationMayProceed() ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   nsRefPtr<VersionChangeTransaction> transaction;
   mVersionChangeTransaction.swap(transaction);
 
@@ -17867,17 +17863,21 @@ OpenDatabaseOp::SendResults()
 
   if (mVersionChangeTransaction) {
     MOZ_ASSERT(NS_FAILED(mResultCode));
 
     mVersionChangeTransaction->Abort(mResultCode, /* aForce */ true);
     mVersionChangeTransaction = nullptr;
   }
 
-  if (!IsActorDestroyed()) {
+  if (IsActorDestroyed()) {
+    if (NS_SUCCEEDED(mResultCode)) {
+      mResultCode = NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+    }
+  } else {
     FactoryRequestResponse response;
 
     if (NS_SUCCEEDED(mResultCode)) {
       // If we just successfully completed a versionchange operation then we
       // need to update the version in our metadata.
       mMetadata->mCommonMetadata.version() = mRequestedVersion;
 
       nsresult rv = EnsureDatabaseActorIsAlive();
@@ -17902,30 +17902,17 @@ OpenDatabaseOp::SendResults()
 #endif
       response = ClampResultCode(mResultCode);
     }
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
-  if (NS_FAILED(mResultCode) && mOfflineStorage) {
-    mOfflineStorage->CloseOnOwningThread();
-
-    nsCOMPtr<nsIRunnable> callback =
-      NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
-
-    nsRefPtr<WaitForTransactionsHelper> helper =
-      new WaitForTransactionsHelper(mDatabaseId, callback);
-    helper->WaitForTransactions();
-  }
-
-  // Make sure to release the database on this thread.
-  nsRefPtr<Database> database;
-  mDatabase.swap(database);
+  CleanupBackgroundThreadObjects(NS_FAILED(mResultCode));
 
   FinishSendResults();
 }
 
 void
 OpenDatabaseOp::ConnectionClosedCallback()
 {
   AssertIsOnOwningThread();
@@ -18008,16 +17995,43 @@ OpenDatabaseOp::EnsureDatabaseActorIsAli
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   return NS_OK;
 }
 
 void
+OpenDatabaseOp::CleanupBackgroundThreadObjects(bool aInvalidate)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(IsActorDestroyed());
+
+  if (mDatabase) {
+    MOZ_ASSERT(!mOfflineStorage);
+
+    if (aInvalidate) {
+      mDatabase->Invalidate();
+    }
+
+    // Make sure to release the database on this thread.
+    mDatabase = nullptr;
+  } else if (mOfflineStorage) {
+    mOfflineStorage->CloseOnOwningThread();
+
+    nsCOMPtr<nsIRunnable> callback =
+      NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(mDatabaseId, callback);
+    helper->WaitForTransactions();
+  }
+}
+
+void
 OpenDatabaseOp::MetadataToSpec(DatabaseSpec& aSpec)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mMetadata);
 
   class MOZ_STACK_CLASS Helper final
   {
     DatabaseSpec& mSpec;
@@ -18515,17 +18529,16 @@ DeleteDatabaseOp::DoDatabaseWork()
 nsresult
 DeleteDatabaseOp::BeginVersionChange()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_BeginVersionChange);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
-      !OperationMayProceed() ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   DatabaseActorInfo* info;
   if (gLiveDatabaseHashtable->Get(mDatabaseId, &info)) {
     MOZ_ASSERT(!info->mWaitingFactoryOp);
@@ -18555,17 +18568,16 @@ DeleteDatabaseOp::BeginVersionChange()
 nsresult
 DeleteDatabaseOp::DispatchToWorkThread()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_WaitingForTransactionsToComplete);
   MOZ_ASSERT(mMaybeBlockedDatabases.IsEmpty());
 
   if (NS_WARN_IF(QuotaClient::IsShuttingDownOnNonMainThread()) ||
-      !OperationMayProceed() ||
       IsActorDestroyed()) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   mState = State_DatabaseWorkVersionChange;
 
   nsRefPtr<VersionChangeOp> versionChangeOp = new VersionChangeOp(this);
--- a/dom/indexedDB/ActorsParent.h
+++ b/dom/indexedDB/ActorsParent.h
@@ -1,26 +1,22 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_indexeddb_actorsparent_h__
 #define mozilla_dom_indexeddb_actorsparent_h__
 
 template <class> struct already_AddRefed;
-class nsCString;
-struct nsID;
 class nsIPrincipal;
-class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
-class TabParent;
 
 namespace quota {
 
 class Client;
 
 } // namespace quota
 
 namespace indexedDB {
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -14,17 +14,16 @@
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsString.h"
 #include "nsTHashtable.h"
 
 class nsIDocument;
-class nsIWeakReference;
 class nsPIDOMWindow;
 
 namespace mozilla {
 
 class ErrorResult;
 class EventChainPostVisitor;
 
 namespace dom {
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -22,31 +22,27 @@ class nsPIDOMWindow;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class DOMStringList;
-class nsIContentParent;
 template <typename> class Sequence;
 
 namespace indexedDB {
 
-class FileManager;
 class IDBCursor;
-class IDBKeyRange;
 class IDBRequest;
 class IDBTransaction;
 class IndexUpdateInfo;
 class Key;
 class KeyPath;
 class ObjectStoreSpec;
-struct StructuredCloneFile;
 struct StructuredCloneReadInfo;
 
 class IDBObjectStore final
   : public nsISupports
   , public nsWrapperCache
 {
   // For AddOrPut() and DeleteInternal().
   friend class IDBCursor; 
--- a/dom/indexedDB/IDBRequest.h
+++ b/dom/indexedDB/IDBRequest.h
@@ -23,17 +23,16 @@ struct PRThread;
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
 
 class DOMError;
-struct ErrorEventInit;
 template <typename> struct Nullable;
 class OwningIDBObjectStoreOrIDBIndexOrIDBCursor;
 
 namespace indexedDB {
 
 class IDBCursor;
 class IDBDatabase;
 class IDBFactory;
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -22,32 +22,30 @@ namespace mozilla {
 
 class ErrorResult;
 class EventChainPreVisitor;
 
 namespace dom {
 
 class DOMError;
 class DOMStringList;
-class PBlobChild;
 
 namespace indexedDB {
 
 class BackgroundCursorChild;
 class BackgroundRequestChild;
 class BackgroundTransactionChild;
 class BackgroundVersionChangeTransactionChild;
 class IDBDatabase;
 class IDBObjectStore;
 class IDBOpenDBRequest;
 class IDBRequest;
 class IndexMetadata;
 class ObjectStoreSpec;
 class OpenCursorParams;
-class PBackgroundIDBDatabaseFileChild;
 class RequestParams;
 
 class IDBTransaction final
   : public IDBWrapperCache
   , public nsIRunnable
 {
   friend class BackgroundCursorChild;
   friend class BackgroundRequestChild;
--- a/dom/indexedDB/IndexedDatabase.h
+++ b/dom/indexedDB/IndexedDatabase.h
@@ -7,30 +7,26 @@
 #ifndef mozilla_dom_indexeddb_indexeddatabase_h__
 #define mozilla_dom_indexeddb_indexeddatabase_h__
 
 #include "js/StructuredClone.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
-class nsIInputStream;
-
 namespace mozilla {
 namespace dom {
 
 class File;
 
 namespace indexedDB {
 
 class FileInfo;
 class IDBDatabase;
-class IDBTransaction;
 class SerializedStructuredCloneReadInfo;
-class SerializedStructuredCloneWriteInfo;
 
 struct StructuredCloneFile
 {
   nsRefPtr<File> mFile;
   nsRefPtr<FileInfo> mFileInfo;
 
   // In IndexedDatabaseInlines.h
   inline
--- a/dom/indexedDB/IndexedDatabaseManager.h
+++ b/dom/indexedDB/IndexedDatabaseManager.h
@@ -11,22 +11,20 @@
 
 #include "js/TypeDecls.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/Mutex.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 
-class nsPIDOMWindow;
 struct PRLogModuleInfo;
 
 namespace mozilla {
 
-class DOMEventTargetHelper;
 class EventChainPostVisitor;
 
 namespace dom {
 
 class TabContext;
 
 namespace indexedDB {
 
--- a/dom/indexedDB/PermissionRequestBase.h
+++ b/dom/indexedDB/PermissionRequestBase.h
@@ -9,17 +9,16 @@
 #include "nsCOMPtr.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
 #include "nsIPermissionManager.h"
 #include "nsISupportsImpl.h"
 #include "nsString.h"
 
 class nsIPrincipal;
-class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 
 class Element;
 
 namespace indexedDB {
 
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+    'nsIPushNotificationService.idl',
+    'nsIPushObserverNotification.idl',
+]
+
+XPIDL_MODULE = 'dom_push'
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/nsIPushNotificationService.idl
@@ -0,0 +1,54 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * A service for components to subscribe and receive push messages from web
+ * services. This functionality is exposed to content via the Push API, which
+ * uses service workers to notify applications. This interface exists to allow
+ * privileged code to receive messages without migrating to service workers.
+ */
+[scriptable, uuid(3da6a16c-69f8-4843-9149-1e89d58a53e2)]
+interface nsIPushNotificationService : nsISupports
+{
+  /**
+   * Creates a push subscription for the given |scope| URL and |pageURL|.
+   * Returns a promise for the new subscription record, or the existing
+   * record if this |scope| already has a subscription.
+   *
+   * The |pushEndpoint| property of the subscription record is a URL string
+   * that can be used to send push messages to subscribers. For details,
+   * please see the Simple Push protocol docs.
+   *
+   * Each incoming message fires a `push-notification` observer
+   * notification, with an `nsIPushObserverNotification` as the subject and
+   * the |scope| as the data.
+   *
+   * If the server drops a subscription, a `push-subscription-change` observer
+   * will be fired, with the subject set to `null` and the data set to |scope|.
+   * Servers may drop subscriptions at any time, so callers should recreate
+   * subscriptions if desired.
+   */
+  jsval register(in string scope, [optional] in string pageURL);
+
+  /**
+   * Revokes a push subscription for the given |scope|. Returns a promise
+   * for the revoked subscription record, or `null` if the |scope| is not
+   * subscribed to receive notifications.
+   */
+  jsval unregister(in string scope);
+
+  /**
+   * Returns a promise for the subscription record associated with the
+   * given |scope|, or `null` if the |scope| does not have a subscription.
+   */
+  jsval registration(in string scope);
+
+  /**
+   * Clear all subscriptions
+   */
+   jsval clearAll();
+};
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/nsIPushObserverNotification.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * A push message received by an `nsIPushNotificationService`, used as the
+ * subject of a `push-notification` observer notification.
+ */
+[scriptable, uuid(66a87970-6dc9-46e0-ac61-adb4a13791de)]
+interface nsIPushObserverNotification : nsISupports
+{
+  /* The URL that receives push messages from an application server. */
+  attribute string pushEndpoint;
+
+  /**
+   * The notification version sent by the application server. This is a
+   * monotonically increasing number.
+   */
+  attribute long long version;
+
+  /**
+   * The notification payload. Delivery is not guaranteed; if the browser is
+   * offline when the application server sends the push message, the payload
+   * may be discarded.
+   */
+  attribute string data;
+};
--- a/dom/ipc/BlobChild.h
+++ b/dom/ipc/BlobChild.h
@@ -5,17 +5,16 @@
 #ifndef mozilla_dom_ipc_BlobChild_h
 #define mozilla_dom_ipc_BlobChild_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/PBlobChild.h"
 #include "nsCOMPtr.h"
 #include "nsID.h"
 
-class nsIDOMBlob;
 class nsIEventTarget;
 class nsIRemoteBlob;
 class nsString;
 
 namespace mozilla {
 namespace ipc {
 
 class PBackgroundChild;
--- a/dom/ipc/BlobParent.h
+++ b/dom/ipc/BlobParent.h
@@ -9,17 +9,16 @@
 #include "mozilla/StaticPtr.h"
 #include "mozilla/dom/PBlobParent.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 template <class, class> class nsDataHashtable;
 class nsIDHashKey;
-class nsIDOMBlob;
 class nsIEventTarget;
 class nsIRemoteBlob;
 template <class> class nsRevocableEventPtr;
 class nsString;
 
 namespace mozilla {
 
 class Mutex;
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -33,16 +33,17 @@
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/ExternalHelperAppChild.h"
 #include "mozilla/dom/PCrashReporterChild.h"
 #include "mozilla/dom/ProcessGlobal.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
 #include "mozilla/dom/nsIContentChild.h"
+#include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/CompositorChild.h"
@@ -198,16 +199,17 @@ using namespace mozilla::dom::telephony;
 using namespace mozilla::dom::voicemail;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
+using namespace mozilla::psm;
 using namespace mozilla::widget;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
 #endif
 using namespace mozilla::widget;
 
 #ifdef MOZ_NUWA_PROCESS
 static bool sNuwaForking = false;
@@ -1618,16 +1620,32 @@ bool
 ContentChild::DeallocPScreenManagerChild(PScreenManagerChild* aService)
 {
     // nsScreenManagerProxy is AddRef'd in its constructor.
     nsScreenManagerProxy *child = static_cast<nsScreenManagerProxy*>(aService);
     child->Release();
     return true;
 }
 
+PPSMContentDownloaderChild*
+ContentChild::AllocPPSMContentDownloaderChild(const uint32_t& aCertType)
+{
+    // NB: We don't need aCertType in the child actor.
+    nsRefPtr<PSMContentDownloaderChild> child = new PSMContentDownloaderChild();
+    return child.forget().take();
+}
+
+bool
+ContentChild::DeallocPPSMContentDownloaderChild(PPSMContentDownloaderChild* aListener)
+{
+    auto* listener = static_cast<PSMContentDownloaderChild*>(aListener);
+    nsRefPtr<PSMContentDownloaderChild> child = dont_AddRef(listener);
+    return true;
+}
+
 PExternalHelperAppChild*
 ContentChild::AllocPExternalHelperAppChild(const OptionalURIParams& uri,
                                            const nsCString& aMimeContentType,
                                            const nsCString& aContentDisposition,
                                            const uint32_t& aContentDispositionHint,
                                            const nsString& aContentDispositionFilename,
                                            const bool& aForceSave,
                                            const int64_t& aContentLength,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -15,43 +15,37 @@
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 #include "nsTHashtable.h"
 
 #include "nsWeakPtr.h"
 
 
 struct ChromePackage;
-class nsIDOMBlob;
 class nsIObserver;
 struct ResourceMapping;
 struct OverrideMapping;
 class nsIDomainPolicy;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 
 namespace ipc {
 class OptionalURIParams;
 class PFileDescriptorSetChild;
 class URIParams;
 }// namespace ipc
 
-namespace jsipc {
-class JavaScriptShared;
-}
-
 namespace layers {
 class PCompositorChild;
 } // namespace layers
 
 namespace dom {
 
 class AlertObserver;
-class PrefObserver;
 class ConsoleListener;
 class PStorageChild;
 class ClonedMessageData;
 class TabChild;
 
 class ContentChild final : public PContentChild
                          , public nsIContentChild
 {
@@ -233,16 +227,20 @@ public:
     virtual bool DeallocPPrintingChild(PPrintingChild*) override;
 
     virtual PScreenManagerChild*
     AllocPScreenManagerChild(uint32_t* aNumberOfScreens,
                              float* aSystemDefaultScale,
                              bool* aSuccess) override;
     virtual bool DeallocPScreenManagerChild(PScreenManagerChild*) override;
 
+    virtual PPSMContentDownloaderChild* AllocPPSMContentDownloaderChild(
+            const uint32_t& aCertType) override;
+    virtual bool DeallocPPSMContentDownloaderChild(PPSMContentDownloaderChild* aDownloader) override;
+
     virtual PExternalHelperAppChild *AllocPExternalHelperAppChild(
             const OptionalURIParams& uri,
             const nsCString& aMimeContentType,
             const nsCString& aContentDisposition,
             const uint32_t& aContentDispositionHint,
             const nsString& aContentDispositionFilename,
             const bool& aForceSave,
             const int64_t& aContentLength,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -151,16 +151,17 @@
 #include "URIUtils.h"
 #include "nsIWebBrowserChrome.h"
 #include "nsIDocShell.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "gfxPrefs.h"
 #include "prio.h"
 #include "private/pprio.h"
 #include "ContentProcessManager.h"
+#include "mozilla/psm/PSMContentListener.h"
 
 #include "nsIBidiKeyboard.h"
 
 #if defined(ANDROID) || defined(LINUX)
 #include "nsSystemInfo.h"
 #endif
 
 #if defined(XP_LINUX)
@@ -244,16 +245,17 @@ using namespace mozilla::dom::telephony;
 using namespace mozilla::dom::voicemail;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
+using namespace mozilla::psm;
 using namespace mozilla::widget;
 
 #ifdef ENABLE_TESTS
 
 class BackgroundTester final : public nsIIPCBackgroundChildCreateCallback,
                                public nsIObserver
 {
     static uint32_t sCallbackCount;
@@ -3660,16 +3662,32 @@ ContentParent::AllocPScreenManagerParent
 
 bool
 ContentParent::DeallocPScreenManagerParent(PScreenManagerParent* aActor)
 {
     delete aActor;
     return true;
 }
 
+PPSMContentDownloaderParent*
+ContentParent::AllocPPSMContentDownloaderParent(const uint32_t& aCertType)
+{
+    nsRefPtr<PSMContentDownloaderParent> downloader =
+        new PSMContentDownloaderParent(aCertType);
+    return downloader.forget().take();
+}
+
+bool
+ContentParent::DeallocPPSMContentDownloaderParent(PPSMContentDownloaderParent* aListener)
+{
+    auto* listener = static_cast<PSMContentDownloaderParent*>(aListener);
+    nsRefPtr<PSMContentDownloaderParent> downloader = dont_AddRef(listener);
+    return true;
+}
+
 PExternalHelperAppParent*
 ContentParent::AllocPExternalHelperAppParent(const OptionalURIParams& uri,
                                              const nsCString& aMimeContentType,
                                              const nsCString& aContentDisposition,
                                              const uint32_t& aContentDispositionHint,
                                              const nsString& aContentDispositionFilename,
                                              const bool& aForceSave,
                                              const int64_t& aContentLength,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -25,35 +25,32 @@
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "PermissionMessageUtils.h"
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
 
 class mozIApplication;
 class nsConsoleService;
 class nsICycleCollectorLogSink;
-class nsIDOMBlob;
 class nsIDumpGCAndCCLogsCallback;
-class nsIMemoryReporter;
 class nsITimer;
 class ParentIdleListener;
 class nsIWidget;
 
 namespace mozilla {
 class PRemoteSpellcheckEngineParent;
 
 namespace ipc {
 class OptionalURIParams;
 class PFileDescriptorSetParent;
 class URIParams;
 class TestShellParent;
 } // namespace ipc
 
 namespace jsipc {
-class JavaScriptShared;
 class PJavaScriptParent;
 }
 
 namespace layers {
 class PCompositorParent;
 class PSharedBufferManagerParent;
 } // namespace layers
 
@@ -604,16 +601,20 @@ private:
     virtual PTestShellParent* AllocPTestShellParent() override;
     virtual bool DeallocPTestShellParent(PTestShellParent* shell) override;
 
     virtual PMobileConnectionParent* AllocPMobileConnectionParent(const uint32_t& aClientId) override;
     virtual bool DeallocPMobileConnectionParent(PMobileConnectionParent* aActor) override;
 
     virtual bool DeallocPNeckoParent(PNeckoParent* necko) override;
 
+    virtual PPSMContentDownloaderParent* AllocPPSMContentDownloaderParent(
+            const uint32_t& aCertType) override;
+    virtual bool DeallocPPSMContentDownloaderParent(PPSMContentDownloaderParent* aDownloader) override;
+
     virtual PExternalHelperAppParent* AllocPExternalHelperAppParent(
             const OptionalURIParams& aUri,
             const nsCString& aMimeContentType,
             const nsCString& aContentDisposition,
             const uint32_t& aContentDispositionHint,
             const nsString& aContentDispositionFilename,
             const bool& aForceSave,
             const int64_t& aContentLength,
--- a/dom/ipc/CrashReporterParent.h
+++ b/dom/ipc/CrashReporterParent.h
@@ -12,17 +12,16 @@
 #include "nsIFile.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 #include "nsDataHashtable.h"
 #endif
 
 namespace mozilla {
 namespace dom {
-class ProcessReporter;
 
 class CrashReporterParent :
     public PCrashReporterParent
 {
 #ifdef MOZ_CRASHREPORTER
   typedef CrashReporter::AnnotationTable AnnotationTable;
 #endif
 public:
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -11,16 +11,17 @@ include protocol PBluetooth;
 include protocol PBrowser;
 include protocol PCellBroadcast;
 include protocol PCompositor;
 include protocol PContentBridge;
 include protocol PContentPermissionRequest;
 include protocol PCycleCollectWithLogs;
 include protocol PCrashReporter;
 include protocol PDocAccessible;
+include protocol PPSMContentDownloader;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
 include protocol PHal;
 include protocol PIcc;
 include protocol PProcessHangMonitor;
@@ -383,16 +384,17 @@ prio(normal upto urgent) sync protocol P
     manages PBrowser;
     manages PCellBroadcast;
     manages PContentPermissionRequest;
     manages PCrashReporter;
     manages PCycleCollectWithLogs;
     manages PDocAccessible;
     manages PDeviceStorageRequest;
     manages PFileSystemRequest;
+    manages PPSMContentDownloader;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
     manages PHal;
     manages PIcc;
     manages PMemoryReportRequest;
     manages PMobileConnection;
     manages PNecko;
@@ -668,17 +670,17 @@ parent:
      * |pluginEpoch| is the epoch last observed by the content
      * process. |newPluginEpoch| is the current epoch in the chrome process. If
      * |pluginEpoch == newPluginEpoch|, then |plugins| will be left empty.
      */
     sync FindPlugins(uint32_t pluginEpoch) returns (PluginTag[] plugins, uint32_t newPluginEpoch);
 
     async PJavaScript();
 
-    sync PRemoteSpellcheckEngine();
+    PRemoteSpellcheckEngine();
     PDeviceStorageRequest(DeviceStorageParams params);
 
     PFileSystemRequest(FileSystemParams params);
 
     sync PCrashReporter(NativeThreadId tid, uint32_t processType);
 
     sync GetRandomValues(uint32_t length)
         returns (uint8_t[] randomValues);
@@ -751,16 +753,18 @@ parent:
                           nsString bidi,
                           nsString lang,
                           nsString data,
                           Principal principal,
                           bool inPrivateBrowsing);
 
     CloseAlert(nsString name, Principal principal);
 
+    PPSMContentDownloader(uint32_t aCertType);
+
     PExternalHelperApp(OptionalURIParams uri,
                        nsCString aMimeContentType,
                        nsCString aContentDisposition,
                        uint32_t aContentDispositionHint,
                        nsString aContentDispositionFilename,
                        bool aForceSave,
                        int64_t aContentLength,
                        OptionalURIParams aReferrer,
--- a/dom/ipc/PreallocatedProcessManager.h
+++ b/dom/ipc/PreallocatedProcessManager.h
@@ -6,18 +6,16 @@
 
 #ifndef mozilla_PreallocatedProcessManager_h
 #define mozilla_PreallocatedProcessManager_h
 
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsIObserver.h"
 
-class nsIRunnable;
-
 namespace mozilla {
 namespace dom {
 class ContentParent;
 }
 
 /**
  * This class manages a ContentParent that it starts up ahead of any particular
  * need.  You can then call Take() to get this process and use it.  Since we
--- a/dom/ipc/ProcessHangMonitor.h
+++ b/dom/ipc/ProcessHangMonitor.h
@@ -5,33 +5,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ProcessHangMonitor_h
 #define mozilla_ProcessHangMonitor_h
 
 #include "mozilla/Atomics.h"
 #include "nsIObserver.h"
 
-class nsGlobalWindow;
 class nsITabChild;
 
 class MessageLoop;
 
 namespace base {
 class Thread;
 };
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
 }
 
 class PProcessHangMonitorParent;
-class PProcessHangMonitorChild;
 
 class ProcessHangMonitor final
   : public nsIObserver
 {
  private:
   ProcessHangMonitor();
   virtual ~ProcessHangMonitor();
 
--- a/dom/ipc/StructuredCloneUtils.h
+++ b/dom/ipc/StructuredCloneUtils.h
@@ -9,19 +9,16 @@
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "mozilla/dom/File.h"
 
 #include "js/StructuredClone.h"
 
 namespace mozilla {
-
-struct SerializedStructuredCloneBuffer;
-
 namespace dom {
 
 struct
 StructuredCloneClosure
 {
   nsTArray<nsRefPtr<File>> mBlobs;
 };
 
--- a/dom/locales/en-US/chrome/appstrings.properties
+++ b/dom/locales/en-US/chrome/appstrings.properties
@@ -24,13 +24,14 @@ contentEncodingError=The page you are tr
 unsafeContentType=The page you are trying to view cannot be shown because it is contained in a file type that may not be safe to open. Please contact the website owners to inform them of this problem.
 externalProtocolTitle=External Protocol Request
 externalProtocolPrompt=An external application must be launched to handle %1$S: links.\n\n\nRequested link:\n\n%2$S\n\nApplication: %3$S\n\n\nIf you were not expecting this request it may be an attempt to exploit a weakness in that other program. Cancel this request unless you are sure it is not malicious.\n
 #LOCALIZATION NOTE (externalProtocolUnknown): The following string is shown if the application name can't be determined
 externalProtocolUnknown=<Unknown>
 externalProtocolChkMsg=Remember my choice for all links of this type.
 externalProtocolLaunchBtn=Launch application
 malwareBlocked=The site at %S has been reported as an attack site and has been blocked based on your security preferences.
+unwantedBlocked=The site at %S has been reported as serving unwanted software and has been blocked based on your security preferences.
 phishingBlocked=The website at %S has been reported as a web forgery designed to trick users into sharing personal or financial information.
 cspBlocked=This page has a content security policy that prevents it from being loaded in this way.
 corruptedContentError=The page you are trying to view cannot be shown because an error in the data transmission was detected.
 remoteXUL=This page uses an unsupported technology that is no longer available by default.
 sslv3Used=The safety of your data on %S could not be guaranteed because it uses SSLv3, a broken security protocol.
--- a/dom/locales/en-US/chrome/netError.dtd
+++ b/dom/locales/en-US/chrome/netError.dtd
@@ -76,16 +76,21 @@
 ">
 
 <!ENTITY malwareBlocked.title "Suspected Attack Site!">
 <!ENTITY malwareBlocked.longDesc "
 <p>Attack sites try to install programs that steal private information, use your computer to attack others, or damage your system.</p>
 <p>Website owners who believe their site has been reported as an attack site in error may <a href='http://www.stopbadware.org/home/reviewinfo' >request a review</a>.</p>
 ">
 
+<!ENTITY unwantedBlocked.title "Suspected Unwanted Software Site!">
+<!ENTITY unwantedBlocked.longDesc "
+<p>Unwanted software pages try to install software that can be deceptive and affect your system in unexpected ways.</p>
+">
+
 <!ENTITY phishingBlocked.title "Suspected Web Forgery!">
 <!ENTITY phishingBlocked.longDesc "
 <p>Entering any personal information on this page may result in identity theft or other fraud.</p>
 <p>These types of web forgeries are used in scams known as phishing attacks, in which fraudulent web pages and emails are used to imitate sources you may trust.</p>
 ">
 
 <!ENTITY cspBlocked.title "Blocked by Content Security Policy">
 <!ENTITY cspBlocked.longDesc "<p>The browser prevented this page from loading in this way because the page has a content security policy that disallows it.</p>">
--- a/dom/media/AbstractMediaDecoder.h
+++ b/dom/media/AbstractMediaDecoder.h
@@ -18,17 +18,16 @@ namespace mozilla
 
 namespace layers
 {
   class ImageContainer;
 }
 class MediaResource;
 class ReentrantMonitor;
 class VideoFrameContainer;
-class TimedMetadata;
 class MediaDecoderOwner;
 #ifdef MOZ_EME
 class CDMProxy;
 #endif
 
 typedef nsDataHashtable<nsCStringHashKey, nsCString> MetadataTags;
 
 static inline bool IsCurrentThread(nsIThread* aThread) {
--- a/dom/media/AudioSegment.h
+++ b/dom/media/AudioSegment.h
@@ -38,17 +38,16 @@ public:
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
   nsTArray<nsTArray<T> > mBuffers;
 };
 
-class AudioStream;
 class AudioMixer;
 
 /**
  * For auto-arrays etc, guess this as the common number of channels.
  */
 const int GUESS_AUDIO_CHANNELS = 2;
 
 // We ensure that the graph advances in steps that are multiples of the Web
--- a/dom/media/AudioSink.h
+++ b/dom/media/AudioSink.h
@@ -7,17 +7,16 @@
 #define AudioSink_h__
 
 #include "nsISupportsImpl.h"
 #include "MediaDecoderReader.h"
 #include "mozilla/dom/AudioChannelBinding.h"
 
 namespace mozilla {
 
-class AudioAvailableEventManager;
 class AudioStream;
 class MediaDecoderStateMachine;
 
 class AudioSink {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioSink)
 
   AudioSink(MediaDecoderStateMachine* aStateMachine,
--- a/dom/media/AudioTrack.h
+++ b/dom/media/AudioTrack.h
@@ -7,18 +7,16 @@
 #ifndef mozilla_dom_AudioTrack_h
 #define mozilla_dom_AudioTrack_h
 
 #include "MediaTrack.h"
 
 namespace mozilla {
 namespace dom {
 
-class AudioTrackList;
-
 class AudioTrack : public MediaTrack
 {
 public:
   AudioTrack(const nsAString& aId,
              const nsAString& aKind,
              const nsAString& aLabel,
              const nsAString& aLanguage,
              bool aEnabled);
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -10,18 +10,16 @@
 #include "nsWrapperCache.h"
 #include "StreamBuffer.h"
 #include "nsIDOMWindow.h"
 #include "nsIPrincipal.h"
 #include "mozilla/PeerIdentity.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/CORSMode.h"
 
-class nsXPCClassInfo;
-
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with NS_DECL_NSIDOMMEDIASTREAM, containing
 // currentTime getter.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 // X11 has a #define for CurrentTime. Unbelievable :-(.
 // See dom/media/webaudio/AudioContext.h for more fun!
--- a/dom/media/EncodedBufferCache.h
+++ b/dom/media/EncodedBufferCache.h
@@ -7,25 +7,23 @@
 #ifndef EncodedBufferCache_h_
 #define EncodedBufferCache_h_
 
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 #include "mozilla/Mutex.h"
 
 struct PRFileDesc;
-class nsIDOMBlob;
 
 namespace mozilla {
 
 namespace dom {
 class File;
 }
 
-class ReentrantMonitor;
 /**
  * Data is moved into a temporary file when it grows beyond
  * the maximal size passed in the Init function.
  * The AppendBuffer and ExtractBlob methods are thread-safe and can be called on
  * different threads at the same time.
  */
 class EncodedBufferCache
 {
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -55,17 +55,16 @@ static const int AUDIO_TARGET_MS = 2*MED
  * near the end of the iteration of the control loop. The maximum delay
  * to the setting of the next video frame is 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
  * SCHEDULE_SAFETY_MARGIN_MS. This is not optimal yet.
  */
 static const int VIDEO_TARGET_MS = 2*MEDIA_GRAPH_TARGET_PERIOD_MS +
     SCHEDULE_SAFETY_MARGIN_MS;
 
 class MediaStreamGraphImpl;
-class MessageBlock;
 
 /**
  * Microseconds relative to the start of the graph timeline.
  */
 typedef int64_t GraphTime;
 const GraphTime GRAPH_TIME_MAX = MEDIA_TIME_MAX;
 
 class AudioCallbackDriver;
--- a/dom/media/Latency.h
+++ b/dom/media/Latency.h
@@ -11,17 +11,16 @@
 #include "prlog.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "mozilla/Monitor.h"
 #include "nsISupportsImpl.h"
 #include "nsIObserver.h"
 
 class AsyncLatencyLogger;
-class LogEvent;
 
 PRLogModuleInfo* GetLatencyLog();
 
 // This class is a singleton. It is refcounted.
 class AsyncLatencyLogger : public nsIObserver
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -87,26 +87,21 @@ hardware (via AudioStream).
 #include "MediaDecoder.h"
 #include "mozilla/ReentrantMonitor.h"
 #include "MediaDecoderReader.h"
 #include "MediaDecoderOwner.h"
 #include "MediaMetadataManager.h"
 #include "mozilla/RollingMean.h"
 #include "MediaTimer.h"
 
-class nsITimer;
-
 namespace mozilla {
 
 class AudioSegment;
-class VideoSegment;
 class MediaTaskQueue;
-class SharedThreadPool;
 class AudioSink;
-class MediaDecoderStateMachineScheduler;
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaDecoderStateMachine::GetCurrentTime
 // implementation.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
--- a/dom/media/MediaDeviceInfo.h
+++ b/dom/media/MediaDeviceInfo.h
@@ -9,19 +9,16 @@
 #include "nsISupportsImpl.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "MediaDeviceInfoBinding.h"
 #include "nsPIDOMWindow.h"
 
 namespace mozilla {
 namespace dom {
 
-class Promise;
-struct MediaStreamConstraints;
-
 #define MOZILLA_DOM_MEDIADEVICEINFO_IMPLEMENTATION_IID \
 {0x25091870, 0x84d6, 0x4acf, {0xaf, 0x97, 0x6e, 0xd5, 0x5b, 0xe0, 0x47, 0xb2}}
 
 class MediaDeviceInfo final : public nsISupports, public nsWrapperCache
 {
 public:
   explicit MediaDeviceInfo(const nsAString& aDeviceId,
                            MediaDeviceKind aKind,
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -40,18 +40,16 @@
 
 #ifdef MOZ_WIDGET_GONK
 #include "DOMCameraManager.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 struct MediaStreamConstraints;
-class NavigatorUserMediaSuccessCallback;
-class NavigatorUserMediaErrorCallback;
 struct MediaTrackConstraintSet;
 }
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetMediaManagerLog();
 #define MM_LOG(msg) PR_LOG(GetMediaManagerLog(), PR_LOG_DEBUG, msg)
 #else
 #define MM_LOG(msg)
--- a/dom/media/MediaShutdownManager.h
+++ b/dom/media/MediaShutdownManager.h
@@ -14,17 +14,16 @@
 #include "nsIThread.h"
 #include "nsCOMPtr.h"
 #include "nsTHashtable.h"
 #include "nsHashKeys.h"
 
 namespace mozilla {
 
 class MediaDecoder;
-class StateMachineThread;
 
 // The MediaShutdownManager manages shutting down the MediaDecoder
 // infrastructure in response to an xpcom-shutdown notification. This happens
 // when Gecko is shutting down in the middle of operation. This is tricky, as
 // there are a number of moving parts that must be shutdown in a particular
 // order. Additionally the xpcom-shutdown observer *must* block until all
 // threads are shutdown, which is tricky since we have a number of threads
 // here and their shutdown is asynchronous. We can't have each element of
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -243,17 +243,16 @@ struct AudioNodeSizes
 
 class MediaStreamGraphImpl;
 class SourceMediaStream;
 class ProcessedMediaStream;
 class MediaInputPort;
 class AudioNodeEngine;
 class AudioNodeExternalInputStream;
 class AudioNodeStream;
-struct AudioChunk;
 class CameraPreviewMediaStream;
 
 /**
  * A stream of synchronized audio and video data. All (not blocked) streams
  * progress at the same rate --- "real time". Streams cannot seek. The only
  * operation readers can perform on a stream is to read the next data.
  *
  * Consumers of a stream can be reading from it at different offsets, but that
--- a/dom/media/SelfRef.h
+++ b/dom/media/SelfRef.h
@@ -30,17 +30,17 @@ public:
   void Drop(T* t)
   {
     if (mHeld) {
       mHeld = false;
       t->Release();
     }
   }
 
-  operator bool() const { return mHeld; }
+  MOZ_IMPLICIT operator bool() const { return mHeld; }
 
   SelfReference(const SelfReference& aOther) = delete;
   void operator=(const SelfReference& aOther) = delete;
 private:
   bool mHeld;
 };
 } // mozilla
 
--- a/dom/media/TextTrack.h
+++ b/dom/media/TextTrack.h
@@ -14,17 +14,16 @@
 #include "nsString.h"
 
 namespace mozilla {
 namespace dom {
 
 class TextTrackList;
 class TextTrackCue;
 class TextTrackCueList;
-class TextTrackRegion;
 class HTMLTrackElement;
 
 enum TextTrackSource {
   Track,
   AddTextTrack,
   MediaResourceSpecific
 };
 
--- a/dom/media/android/AndroidMediaPluginHost.h
+++ b/dom/media/android/AndroidMediaPluginHost.h
@@ -8,18 +8,16 @@
 
 #include "nsTArray.h"
 #include "MediaResource.h"
 #include "MPAPI.h"
 #include "AndroidMediaResourceServer.h"
 
 namespace mozilla {
 
-class AndroidMediaReader;
-
 class AndroidMediaPluginHost {
   nsRefPtr<AndroidMediaResourceServer> mResourceServer;
   nsTArray<MPAPI::Manifest *> mPlugins;
 
   MPAPI::Manifest *FindPlugin(const nsACString& aMimeType);
 public:
   AndroidMediaPluginHost();
   ~AndroidMediaPluginHost();
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -20,20 +20,16 @@ class nsACString;
 namespace mozilla {
 
 class AbstractMediaDecoder;
 
 namespace layers {
 class ImageContainer;
 }
 
-namespace dom {
-class TimeRanges;
-}
- 
 class AndroidMediaReader : public MediaDecoderReader
 {
   nsCString mType;
   MPAPI::Decoder *mPlugin;
   bool mHasAudio;
   bool mHasVideo;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
--- a/dom/media/directshow/DirectShowReader.h
+++ b/dom/media/directshow/DirectShowReader.h
@@ -10,27 +10,22 @@
 #include "windows.h" // HRESULT, DWORD
 #include "MediaDecoderReader.h"
 #include "mozilla/RefPtr.h"
 #include "MP3FrameParser.h"
 
 struct IGraphBuilder;
 struct IMediaControl;
 struct IMediaSeeking;
-struct IMediaEventEx;
 
 namespace mozilla {
 
 class AudioSinkFilter;
 class SourceFilter;
 
-namespace dom {
-class TimeRanges;
-}
-
 // Decoder backend for decoding MP3 using DirectShow. DirectShow operates as
 // a filter graph. The basic design of the DirectShowReader is that we have
 // a SourceFilter that wraps the MediaResource that connects to the
 // MP3 decoder filter. The MP3 decoder filter "pulls" data as it requires it
 // downstream on its own thread. When the MP3 decoder has produced a block of
 // decoded samples, its thread calls downstream into our AudioSinkFilter,
 // passing the decoded buffer in. The AudioSinkFilter inserts the samples into
 // a SampleSink object. The SampleSink blocks the MP3 decoder's thread until
--- a/dom/media/directshow/SourceFilter.h
+++ b/dom/media/directshow/SourceFilter.h
@@ -14,17 +14,16 @@
 #include "nsDeque.h"
 #include "nsAutoPtr.h"
 #include "DirectShowUtils.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 
 class MediaResource;
-class ReadRequest;
 class OutputPin;
 
 
 // SourceFilter is an asynchronous DirectShow source filter which
 // reads from an MediaResource, and supplies data via a pull model downstream
 // using OutputPin. It us used to supply a generic byte stream into
 // DirectShow.
 //
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -59,29 +59,22 @@ CDMProxy::Init(PromiseId aPromiseId,
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
     mps->GetThread(getter_AddRefs(mGMPThread));
     if (!mGMPThread) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
       return;
     }
-    bool hasPlugin;
-    nsTArray<nsCString> tags;
-    tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
-    nsresult rv = mps->GetPluginVersionForAPI(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
-                                              &tags, &hasPlugin, pluginVersion);
-    NS_ENSURE_SUCCESS_VOID(rv);
   }
 
   nsAutoPtr<InitData> data(new InitData());
   data->mPromiseId = aPromiseId;
   data->mOrigin = aOrigin;
   data->mTopLevelOrigin = aTopLevelOrigin;
-  data->mPluginVersion = pluginVersion;
   data->mInPrivateBrowsing = aInPrivateBrowsing;
   nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<InitData>>(this,
                                                      &CDMProxy::gmp_Init,
                                                      Move(data)));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
@@ -170,17 +163,16 @@ CDMProxy::gmp_Init(nsAutoPtr<InitData>&&
   // Make a copy before we transfer ownership of aData to the
   // gmp_InitGetGMPDecryptorCallback.
   InitData data(*aData);
   UniquePtr<GetNodeIdCallback> callback(
     new gmp_InitGetGMPDecryptorCallback(this, Move(aData)));
   nsresult rv = mps->GetNodeId(data.mOrigin,
                                data.mTopLevelOrigin,
                                data.mInPrivateBrowsing,
-                               data.mPluginVersion,
                                Move(callback));
   if (NS_FAILED(rv)) {
     RejectPromise(data.mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR);
   }
 }
 
 void
 CDMProxy::gmp_InitGetGMPDecryptor(nsresult aResult,
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -176,17 +176,16 @@ public:
 private:
   friend class gmp_InitDoneCallback;
   friend class gmp_InitGetGMPDecryptorCallback;
 
   struct InitData {
     uint32_t mPromiseId;
     nsAutoString mOrigin;
     nsAutoString mTopLevelOrigin;
-    nsAutoCString mPluginVersion;
     bool mInPrivateBrowsing;
   };
 
   // GMP thread only.
   void gmp_Init(nsAutoPtr<InitData>&& aData);
   void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
   void gmp_InitGetGMPDecryptor(nsresult aResult,
                                const nsACString& aNodeId,
--- a/dom/media/eme/MediaKeySession.h
+++ b/dom/media/eme/MediaKeySession.h
@@ -18,19 +18,16 @@
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaKeySessionBinding.h"
 #include "mozilla/dom/MediaKeysBinding.h"
 #include "mozilla/dom/MediaKeyMessageEventBinding.h"
 
 struct JSContext;
 
 namespace mozilla {
-
-class CDMProxy;
-
 namespace dom {
 
 class ArrayBufferViewOrArrayBuffer;
 class MediaKeyError;
 class MediaKeyStatusMap;
 
 class MediaKeySession final : public DOMEventTargetHelper
 {
--- a/dom/media/encoder/fmp4_muxer/ISOMediaBoxes.h
+++ b/dom/media/encoder/fmp4_muxer/ISOMediaBoxes.h
@@ -23,17 +23,16 @@ namespace mozilla {
 /**
  * track type from spec 8.4.3.3
  */
 #define Audio_Track 0x01
 #define Video_Track 0x02
 
 class AudioTrackMetadata;
 class VideoTrackMetadata;
-class ES_Descriptor;
 class ISOControl;
 
 /**
  * This is the base class for all ISO media format boxes.
  * It provides the fields of box type(four CC) and size.
  * The data members in the beginning of a Box (or its descendants)
  * are the 14496-12 defined member. Other members prefix with 'm'
  * are private control data.
--- a/dom/media/encoder/fmp4_muxer/ISOMediaWriter.h
+++ b/dom/media/encoder/fmp4_muxer/ISOMediaWriter.h
@@ -8,17 +8,16 @@
 
 #include "ContainerWriter.h"
 #include "nsIRunnable.h"
 
 namespace mozilla {
 
 class ISOControl;
 class FragmentBuffer;
-class ISOMediaWriterRunnable;
 
 class ISOMediaWriter : public ContainerWriter
 {
 public:
   // Generate an fragmented MP4 stream, ISO/IEC 14496-12.
   // Brand names in 'ftyp' box are 'isom' and 'mp42'.
   const static uint32_t TYPE_FRAG_MP4 = 1 << 0;
 
--- a/dom/media/fmp4/PlatformDecoderModule.h
+++ b/dom/media/fmp4/PlatformDecoderModule.h
@@ -8,32 +8,28 @@
 #define PlatformDecoderModule_h_
 
 #include "MediaDecoderReader.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsTArray.h"
 #include "mozilla/RefPtr.h"
 #include <queue>
 
-class nsIThreadPool;
-
 namespace mozilla {
 class TrackInfo;
 class AudioInfo;
 class VideoInfo;
 class MediaRawData;
-class MediaByteBuffer;
 
 namespace layers {
 class ImageContainer;
 }
 
 class MediaDataDecoder;
 class MediaDataDecoderCallback;
-class MediaInputQueue;
 class FlushableMediaTaskQueue;
 class CDMProxy;
 typedef int64_t Microseconds;
 
 // The PlatformDecoderModule interface is used by the MP4Reader to abstract
 // access to the H264 and Audio (AAC/MP3) decoders provided by various platforms.
 // It may be extended to support other codecs in future. Each platform (Windows,
 // MacOSX, Linux, B2G etc) must implement a PlatformDecoderModule to provide
--- a/dom/media/gmp/GMPContentParent.h
+++ b/dom/media/gmp/GMPContentParent.h
@@ -4,24 +4,20 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GMPContentParent_h_
 #define GMPContentParent_h_
 
 #include "mozilla/gmp/PGMPContentParent.h"
 #include "nsISupportsImpl.h"
 
-class nsITimer;
-
 namespace mozilla {
 namespace gmp {
 
-class GeckoMediaPluginService;
 class GMPAudioDecoderParent;
-class GMPCapability;
 class GMPDecryptorParent;
 class GMPParent;
 class GMPVideoDecoderParent;
 class GMPVideoEncoderParent;
 
 class GMPContentParent final : public PGMPContentParent,
                                public GMPSharedMem
 {
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -18,17 +18,16 @@
 #include "nsCOMPtr.h"
 #include "nscore.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsIFile.h"
 #include "ThreadSafeRefcountingWithMainThreadDestruction.h"
 
-class nsILineInputStream;
 class nsIThread;
 
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"
 
 namespace mozilla {
 namespace dom {
 class PCrashReporterParent;
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -143,62 +143,56 @@ GeckoMediaPluginServiceChild::GetPluginV
   aOutVersion = version;
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 class GetNodeIdDone : public GetServiceChildCallback
 {
 public:
   GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
-                bool aInPrivateBrowsing, const nsACString& aVersion,
-                UniquePtr<GetNodeIdCallback>&& aCallback)
+                bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback)
     : mOrigin(aOrigin),
       mTopLevelOrigin(aTopLevelOrigin),
       mInPrivateBrowsing(aInPrivateBrowsing),
-      mVersion(aVersion),
       mCallback(Move(aCallback))
   {
   }
 
   virtual void Done(GMPServiceChild* aGMPServiceChild)
   {
     if (!aGMPServiceChild) {
       mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
       return;
     }
 
     nsCString outId;
     if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin,
-                                            mInPrivateBrowsing, mVersion,
-                                            &outId)) {
+                                            mInPrivateBrowsing, &outId)) {
       mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
       return;
     }
 
     mCallback->Done(NS_OK, outId);
   }
 
 private:
   nsString mOrigin;
   nsString mTopLevelOrigin;
   bool mInPrivateBrowsing;
-  nsCString mVersion;
   UniquePtr<GetNodeIdCallback> mCallback;
 };
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
                                         const nsAString& aTopLevelOrigin,
                                         bool aInPrivateBrowsing,
-                                        const nsACString& aVersion,
                                         UniquePtr<GetNodeIdCallback>&& aCallback)
 {
   UniquePtr<GetServiceChildCallback> callback(
-    new GetNodeIdDone(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, aVersion,
-                      Move(aCallback)));
+    new GetNodeIdDone(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, Move(aCallback)));
   GetServiceChild(Move(callback));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceChild::Observe(nsISupports* aSubject,
                                       const char* aTopic,
                                       const char16_t* aSomeData)
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -29,17 +29,16 @@ public:
 
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
                        bool aInPrivateBrowsingMode,
-                       const nsACString& aVersion,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
 
   NS_DECL_NSIOBSERVER
 
   void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
 
   void RemoveGMPContentParent(GMPContentParent* aGMPContentParent);
 
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -579,19 +579,27 @@ GeckoMediaPluginServiceParent::SelectPlu
           return gmp;
         }
       } else if (gmp->CanBeUsedFrom(aNodeId)) {
         MOZ_ASSERT(!aNodeId.IsEmpty());
         gmp->SetNodeId(aNodeId);
         return gmp;
       }
 
-      // This GMP has the correct type but has the wrong nodeId; hold on to it
-      // in case we need to clone it.
-      gmpToClone = gmp;
+      if (!gmpToClone ||
+          (gmpToClone->IsMarkedForDeletion() && !gmp->IsMarkedForDeletion())) {
+        // This GMP has the correct type but has the wrong nodeId; hold on to it
+        // in case we need to clone it.
+        // Prefer GMPs in-use for the case where an upgraded plugin version is
+        // waiting for the old one to die. If the old plugin is in use, we
+        // should continue using it so that any persistent state remains
+        // consistent. Otherwise, just check that the plugin isn't scheduled
+        // for deletion.
+        gmpToClone = gmp;
+      }
       // Loop around and try the next plugin; it may be usable from aNodeId.
       index++;
     }
   }
 
   // Plugin exists, but we can't use it due to cross-origin separation. Create a
   // new one.
   if (gmpToClone) {
@@ -907,17 +915,16 @@ GeckoMediaPluginServiceParent::IsPersist
   *aOutAllowed = mPersistentStorageAllowed.Get(aNodeId);
   return NS_OK;
 }
 
 nsresult
 GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
                                          const nsAString& aTopLevelOrigin,
                                          bool aInPrivateBrowsing,
-                                         const nsACString& aVersion,
                                          nsACString& aOutId)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
        NS_ConvertUTF16toUTF8(aOrigin).get(),
        NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
        (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
 
@@ -936,18 +943,17 @@ GeckoMediaPluginServiceParent::GetNodeId
       return rv;
     }
     aOutId = salt;
     mPersistentStorageAllowed.Put(salt, false);
     return NS_OK;
   }
 
   const uint32_t hash = AddToHash(HashString(aOrigin),
-                                  HashString(aTopLevelOrigin),
-                                  HashString(aVersion));
+                                  HashString(aTopLevelOrigin));
 
   if (aInPrivateBrowsing) {
     // For PB mode, we store the node id, indexed by the origin pair,
     // so that if the same origin pair is opened in this session, it gets
     // the same node id.
     nsCString* salt = nullptr;
     if (!(salt = mTempNodeIds.Get(hash))) {
       // No salt stored, generate and temporarily store some for this id.
@@ -1057,21 +1063,20 @@ GeckoMediaPluginServiceParent::GetNodeId
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
                                          const nsAString& aTopLevelOrigin,
                                          bool aInPrivateBrowsing,
-                                         const nsACString& aVersion,
                                          UniquePtr<GetNodeIdCallback>&& aCallback)
 {
   nsCString nodeId;
-  nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, aVersion, nodeId);
+  nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, nodeId);
   aCallback->Done(rv, nodeId);
   return rv;
 }
 
 static bool
 ExtractHostName(const nsACString& aOrigin, nsACString& aOutData)
 {
   nsCString str;
@@ -1430,21 +1435,20 @@ GMPServiceParent::RecvLoadGMP(const nsCS
 
   return aAlreadyBridgedTo.Contains(*aId) || gmp->Bridge(this);
 }
 
 bool
 GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
                                    const nsString& aTopLevelOrigin,
                                    const bool& aInPrivateBrowsing,
-                                   const nsCString& aVersion,
                                    nsCString* aID)
 {
   nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin,
-                                    aInPrivateBrowsing, aVersion, *aID);
+                                    aInPrivateBrowsing, *aID);
   return NS_SUCCEEDED(rv);
 }
 
 /* static */
 bool
 GMPServiceParent::RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                                 nsTArray<nsCString>&& aTags,
                                                 bool* aHasPlugin,
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -37,17 +37,16 @@ public:
   // mozIGeckoMediaPluginService
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
                        bool aInPrivateBrowsingMode,
-                       const nsACString& aVersion,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
 
   NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
   NS_DECL_NSIOBSERVER
 
   void AsyncShutdownNeeded(GMPParent* aParent);
   void AsyncShutdownComplete(GMPParent* aParent);
   void AbortAsyncShutdown();
@@ -65,18 +64,17 @@ private:
                                 const nsCString& aAPI,
                                 const nsTArray<nsCString>& aTags);
   GMPParent* FindPluginForAPIFrom(size_t aSearchStartIndex,
                                   const nsCString& aAPI,
                                   const nsTArray<nsCString>& aTags,
                                   size_t* aOutPluginIndex);
 
   nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
-                     bool aInPrivateBrowsing, const nsACString& aVersion,
-                     nsACString& aOutId);
+                     bool aInPrivateBrowsing, nsACString& aOutId);
 
   void UnloadPlugins();
   void CrashPlugins();
   void SetAsyncShutdownComplete();
 
   void LoadFromEnvironment();
   void ProcessPossiblePlugin(nsIFile* aDir);
 
@@ -194,17 +192,16 @@ public:
                            nsTArray<nsCString>&& aTags,
                            nsTArray<ProcessId>&& aAlreadyBridgedTo,
                            base::ProcessId* aID,
                            nsCString* aDisplayName,
                            nsCString* aPluginId) override;
   virtual bool RecvGetGMPNodeId(const nsString& aOrigin,
                                 const nsString& aTopLevelOrigin,
                                 const bool& aInPrivateBrowsing,
-                                const nsCString& aVersion,
                                 nsCString* aID) override;
   static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                             nsTArray<nsCString>&& aTags,
                                             bool* aHasPlugin,
                                             nsCString* aVersion);
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
 
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -14,14 +14,14 @@ sync protocol PGMPService
 {
   parent spawns PGMP as child;
 
 parent:
   sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags,
                ProcessId[] alreadyBridgedTo)
     returns (ProcessId id, nsCString displayName, nsCString pluginId);
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
-                    bool inPrivateBrowsing, nsCString version)
+                    bool inPrivateBrowsing)
     returns (nsCString id);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -47,17 +47,17 @@ public:
 
 [ptr] native TagArray(nsTArray<nsCString>);
 native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
 native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
 native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
 native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
 native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
 
-[scriptable, uuid(6e3023f4-d9a2-46b4-8f50-70d4c918471d)]
+[scriptable, uuid(787cf744-eea8-48c8-b692-376e0485ef97)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -141,11 +141,10 @@ interface mozIGeckoMediaPluginService : 
 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   [noscript]
   void getNodeId(in AString origin,
                  in AString topLevelOrigin,
                  in bool inPrivateBrowsingMode,
-                 in ACString version,
                  in GetNodeIdCallback callback);
 };
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -492,17 +492,16 @@ GetNodeId(const nsAString& aOrigin,
   nsresult result;
   UniquePtr<GetNodeIdCallback> callback(new TestGetNodeIdCallback(nodeId,
                                                                   result));
   // We rely on the fact that the GetNodeId implementation for
   // GeckoMediaPluginServiceParent is synchronous.
   nsresult rv = service->GetNodeId(aOrigin,
                                    aTopLevelOrigin,
                                    aInPBMode,
-                                   NS_LITERAL_CSTRING(""),
                                    Move(callback));
   EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result));
   return nodeId;
 }
 
 static bool
 IsGMPStorageIsEmpty()
 {
--- a/dom/media/imagecapture/ImageCapture.h
+++ b/dom/media/imagecapture/ImageCapture.h
@@ -6,18 +6,16 @@
 
 #ifndef IMAGECAPTURE_H
 #define IMAGECAPTURE_H
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/ImageCaptureBinding.h"
 #include "prlog.h"
 
-class nsIDOMBlob;
-
 namespace mozilla {
 
 #ifdef PR_LOGGING
 
 #ifndef IC_LOG
 PRLogModuleInfo* GetICLog();
 #define IC_LOG(...) PR_LOG(GetICLog(), PR_LOG_DEBUG, (__VA_ARGS__))
 #endif
--- a/dom/media/mediasource/SourceBufferList.h
+++ b/dom/media/mediasource/SourceBufferList.h
@@ -17,17 +17,16 @@
 #include "nsISupports.h"
 #include "nsTArray.h"
 
 struct JSContext;
 class JSObject;
 
 namespace mozilla {
 
-class ErrorResult;
 template <typename T> class AsyncEventRunner;
 
 namespace dom {
 
 class MediaSource;
 
 class SourceBufferList final : public DOMEventTargetHelper
 {
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -17,20 +17,16 @@
 
 namespace android {
 class OmxDecoder;
 class MOZ_EXPORT MediaExtractor;
 }
 
 namespace mozilla {
 
-namespace dom {
-  class TimeRanges;
-}
-
 class AbstractMediaDecoder;
 
 class MediaOmxReader : public MediaOmxCommonReader
 {
   // This mutex is held when accessing the mIsShutdown variable, which is
   // modified on the decode task queue and read on main and IO threads.
   Mutex mShutdownMutex;
   nsCString mType;
--- a/dom/media/omx/OMXCodecProxy.h
+++ b/dom/media/omx/OMXCodecProxy.h
@@ -12,17 +12,16 @@
 #include <stagefright/MediaBuffer.h>
 #include <stagefright/MediaSource.h>
 #include <utils/threads.h>
 
 #include "MediaResourceManagerClient.h"
 
 namespace android {
 
-struct MediaBufferGroup;
 struct MetaData;
 
 class OMXCodecProxy : public MediaSource,
                       public MediaResourceManagerClient::EventListener
 {
 public:
   struct EventListener : public virtual RefBase {
     virtual void statusChanged() = 0;
--- a/dom/media/systemservices/LoadMonitor.h
+++ b/dom/media/systemservices/LoadMonitor.h
@@ -11,17 +11,16 @@
 #include "mozilla/RefPtr.h"
 #include "mozilla/Atomics.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "nsIObserver.h"
 
 namespace mozilla {
-class LoadInfoUpdateRunner;
 class LoadInfoCollectRunner;
 
 class LoadNotificationCallback
 {
 public:
     virtual void LoadChanged(float aSystemLoad, float aProcessLoad) = 0;
 };
 
--- a/dom/media/test/test_eme_playback.html
+++ b/dom/media/test/test_eme_playback.html
@@ -44,37 +44,33 @@ function startTest(test, token)
       onsessioncreated: function(session) {
         sessions.push(session);
         session.addEventListener("keystatuseschange", KeysChangeFunc(session, test.keys, token), false);
       }
     }
   );
 
   var gotEncrypted = 0;
-  var gotPlaying = false;
 
   v.addEventListener("encrypted", function(ev) {
     gotEncrypted += 1;
   });
 
-  v.addEventListener("playing", function () { gotPlaying = true; });
-
   v.addEventListener("loadedmetadata", function() {
     ok(SpecialPowers.do_lookupGetter(v, "isEncrypted").apply(v),
        TimeStamp(token) + " isEncrypted should be true");
     is(v.isEncrypted, undefined, "isEncrypted should not be accessible from content");
   });
 
   v.addEventListener("ended", function(ev) {
     ok(true, TimeStamp(token) + " got ended event");
 
     is(gotEncrypted, test.sessionCount,
        TimeStamp(token) + " encrypted events expected: " + test.sessionCount
        + ", actual: " + gotEncrypted);
-    ok(gotPlaying, TimeStamp(token) + " playing event should have fired");
 
     ok(Math.abs(test.duration - v.duration) < 0.1,
        TimeStamp(token) + " Duration of video should be corrrect");
     ok(Math.abs(test.duration - v.currentTime) < 0.1,
        TimeStamp(token) + " Current time should be same as duration");
 
     // Verify all sessions had all keys went sent to the CDM usable, and thus
     // that we received keystatuseschange event(s).
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -711,16 +711,49 @@ DataChannelWrapper.prototype = {
    */
   toString: function() {
     return "DataChannelWrapper (" + this._pc.label + '_' + this._channel.label + ")";
   }
 };
 
 
 /**
+ * This class provides helpers around analysing the audio content in a stream
+ * using WebAudio AnalyserNodes.
+ *
+ * @constructor
+ * @param {object} stream
+ *                 A MediaStream object whose audio track we shall analyse.
+ */
+function AudioStreamAnalyser(stream) {
+  if (stream.getAudioTracks().length === 0) {
+    throw new Error("No audio track in stream");
+  }
+  this.stream = stream;
+  this.audioContext = new AudioContext();
+  this.sourceNode = this.audioContext.createMediaStreamSource(this.stream);
+  this.analyser = this.audioContext.createAnalyser();
+  this.sourceNode.connect(this.analyser);
+  this.data = new Uint8Array(this.analyser.frequencyBinCount);
+}
+
+AudioStreamAnalyser.prototype = {
+  /**
+   * Get an array of frequency domain data for our stream's audio track.
+   *
+   * @returns {array} A Uint8Array containing the frequency domain data.
+   */
+  getByteFrequencyData: function() {
+    this.analyser.getByteFrequencyData(this.data);
+    return this.data;
+  }
+};
+
+
+/**
  * This class acts as a wrapper around a PeerConnection instance.
  *
  * @constructor
  * @param {string} label
  *        Description for the peer connection instance
  * @param {object} configuration
  *        Configuration for the peer connection instance
  */
@@ -1492,16 +1525,68 @@ PeerConnectionWrapper.prototype = {
    * Check that media flow is present on all media elements involved in this
    * test by waiting for confirmation that media flow is present.
    */
   checkMediaFlowPresent : function() {
     return Promise.all(this.mediaCheckers.map(checker => checker.waitForMediaFlow()));
   },
 
   /**
+   * Check that correct audio (typically a flat tone) is flowing to this
+   * PeerConnection. Uses WebAudio AnalyserNodes to compare input and output
+   * audio data in the frequency domain.
+   *
+   * @param {object} from
+   *        A PeerConnectionWrapper whose audio RTPSender we use as source for
+   *        the audio flow check.
+   * @returns {Promise}
+   *        A promise that resolves when we're receiving the tone from |from|.
+   */
+  checkReceivingToneFrom : function(from) {
+    var inputElem = from.localMediaElements[0];
+
+    // As input we use the stream of |from|'s first available audio sender.
+    var inputSenderTracks = from._pc.getSenders().map(sn => sn.track);
+    var inputAudioStream = from._pc.getLocalStreams()
+      .find(s => s.getAudioTracks().some(t => inputSenderTracks.includes(t)));
+    var inputAnalyser = new AudioStreamAnalyser(inputAudioStream);
+
+    // It would have been nice to have a working getReceivers() here, but until
+    // we do, let's use what remote streams we have.
+    var outputAudioStream = this._pc.getRemoteStreams()
+      .find(s => s.getAudioTracks().length > 0);
+    var outputAnalyser = new AudioStreamAnalyser(outputAudioStream);
+
+    var maxWithIndex = (a, b, i) => (b >= a.value) ? { value: b, index: i } : a;
+    var initial = { value: -1, index: -1 };
+
+    return new Promise((resolve, reject) => inputElem.ontimeupdate = () => {
+      var inputData = inputAnalyser.getByteFrequencyData();
+      var outputData = outputAnalyser.getByteFrequencyData();
+
+      var inputMax = inputData.reduce(maxWithIndex, initial);
+      var outputMax = outputData.reduce(maxWithIndex, initial);
+      info("Comparing maxima; input[" + inputMax.index + "] = " + inputMax.value +
+           ", output[" + outputMax.index + "] = " + outputMax.value);
+      if (!inputMax.value || !outputMax.value) {
+        return;
+      }
+
+      // When the input and output maxima are within reasonable distance
+      // from each other, we can be sure that the input tone has made it
+      // through the peer connection.
+      if (Math.abs(inputMax.index - outputMax.index) < 10) {
+        ok(true, "input and output audio data matches");
+        inputElem.ontimeupdate = null;
+        resolve();
+      }
+    });
+  },
+
+  /**
    * Check that stats are present by checking for known stats.
    */
   getStats : function(selector) {
     return this._pc.getStats(selector).then(stats => {
       info(this + ": Got stats: " + JSON.stringify(stats));
       this._last_stats = stats;
       return stats;
     });
--- a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
@@ -3,72 +3,143 @@
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript;version=1.8">
   createHTML({
     bug: "1032839",
-    title: "Replace video track",
+    title: "Replace video and audio (with WebAudio) tracks",
     visible: true
   });
 
-  function isSenderOfTrack(sender) {
-    return sender.track == this;
+  function allLocalStreamsHaveSender(pc) {
+    return pc.getLocalStreams()
+      .every(s => s.getTracks()        // Every local stream,
+        .some(t => pc.getSenders()     // should have some track,
+          .some(sn => sn.track == t))) // that's being sent over |pc|.
+  }
+
+  function replacetest(wrapper) {
+    var pc = wrapper._pc;
+    var oldSenderCount = pc.getSenders().length;
+    var sender = pc.getSenders().find(sn => sn.track.kind == "video");
+    var oldTrack = sender.track;
+    ok(sender, "We have a sender for video");
+    ok(allLocalStreamsHaveSender(pc),
+       "Shouldn't have any streams without a corresponding sender");
+
+    var newTrack;
+    var audiotrack;
+    return navigator.mediaDevices.getUserMedia({video:true, audio:true, fake:true})
+      .then(newStream => {
+        newTrack = newStream.getVideoTracks()[0];
+        audiotrack = newStream.getAudioTracks()[0];
+        isnot(newTrack, sender.track, "replacing with a different track");
+        ok(!pc.getLocalStreams().includes(newStream), "from a different stream");
+        return sender.replaceTrack(newTrack);
+      })
+      .then(() => {
+        is(pc.getSenders().length, oldSenderCount, "same sender count");
+        is(sender.track, newTrack, "sender.track has been replaced");
+        ok(!pc.getSenders().map(sn => sn.track).includes(oldTrack),
+           "old track not among senders");
+        ok(pc.getLocalStreams().some(s => s.getTracks().includes(sender.track)),
+           "track exists among pc's local streams");
+        return sender.replaceTrack(audiotrack)
+          .then(() => ok(false, "replacing with different kind should fail"),
+                e => is(e.name, "IncompatibleMediaStreamTrackError",
+                        "replacing with different kind should fail"));
+      });
   }
 
   runNetworkTest(function () {
     test = new PeerConnectionTest();
-    test.setMediaConstraints([{video: true}], [{video: true}]);
+    test.audioCtx = new AudioContext();
+    test.setMediaConstraints([{video: true, audio: true}], [{video: true}]);
     test.chain.removeAfter("PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT");
-    var flowtest = test.chain.remove("PC_REMOTE_CHECK_MEDIA_FLOW_PRESENT");
-    test.chain.append(flowtest);
+
+    // Test replaceTrack on pcRemote separately since it's video only.
+    test.chain.append([
+      function PC_REMOTE_VIDEOONLY_REPLACE_VIDEOTRACK(test) {
+        return replacetest(test.pcRemote);
+      },
+      function PC_LOCAL_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
+        return test.pcLocal.checkMediaFlowPresent();
+      }
+    ]);
 
-    var replacetest = [ function PC_LOCAL_REPLACE_VIDEOTRACK(test) {
-      var oldstream = test.pcLocal._pc.getLocalStreams()[0];
-      var oldtrack = oldstream.getVideoTracks()[0];
-      var sender = test.pcLocal._pc.getSenders().find(isSenderOfTrack, oldtrack);
-      ok(sender, "track has a sender");
-      var newtrack;
-      var audiotrack;
-      return navigator.mediaDevices.getUserMedia({video:true, audio:true, fake:true})
-        .then(newstream => {
-          newtrack = newstream.getVideoTracks()[0];
-          audiotrack = newstream.getAudioTracks()[0];
-          isnot(newtrack, oldtrack, "replacing with a different track");
-          isnot(newstream, oldstream, "from a different stream");
-          return sender.replaceTrack(newtrack);
-        })
-        .then(() => {
-          is(sender.track, newtrack, "sender.track has been replaced");
-          var stream = test.pcLocal._pc.getLocalStreams()[0];
-          var track = stream.getVideoTracks()[0];
-          is(track, newtrack, "track has been replaced in stream");
-          return sender.replaceTrack(audiotrack)
-            .then(() => ok(false, "replacing with different kind should fail"),
-                  e => is(e.name, "IncompatibleMediaStreamTrackError",
-                          "replacing with different kind should fail"));
-        });
-    } ];
-    // Do it twice to make sure it still works (does audio twice too, but hey)
-    test.chain.append(replacetest);
-    test.chain.append(flowtest);
-    test.chain.append(replacetest);
-    test.chain.append(flowtest);
+    // Replace video twice on pcLocal to make sure it still works
+    // (does audio twice too, but hey)
     test.chain.append([
-      function PC_LOCAL_REPLACE_VIDEOTRACK_WITHSAME(test) {
-        var oldstream = test.pcLocal._pc.getLocalStreams()[0];
-        var oldtrack = oldstream.getVideoTracks()[0];
-        var sender = test.pcLocal._pc.getSenders().find(isSenderOfTrack, oldtrack);
-        return sender.replaceTrack(oldtrack) // same track
-          .then(() => ok(true, "replacing with itself should succeed"));
+      function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_1(test) {
+        return replacetest(test.pcLocal);
+      },
+      function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_1(test) {
+        return test.pcRemote.checkMediaFlowPresent();
+      },
+      function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_2(test) {
+        return replacetest(test.pcLocal);
+      },
+      function PC_REMOTE_NEW_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT_2(test) {
+        return test.pcRemote.checkMediaFlowPresent();
       }
     ]);
-    test.chain.append(flowtest);
+
+    test.chain.append([
+      function PC_LOCAL_AUDIOVIDEO_REPLACE_VIDEOTRACK_WITHSAME(test) {
+        var pc = test.pcLocal._pc;
+        var sender = pc.getSenders().find(sn => sn.track.kind == "video");
+        ok(sender, "should still have a sender of video");
+        return sender.replaceTrack(sender.track)
+          .then(() => ok(true, "replacing with itself should succeed"));
+      },
+      function PC_REMOTE_NEW_SAME_VIDEOTRACK_CHECK_MEDIA_FLOW_PRESENT(test) {
+        return test.pcRemote.checkMediaFlowPresent();
+      }
+    ]);
+
+    // Replace the gUM audio track on pcLocal with a WebAudio track.
+    test.chain.append([
+      function PC_LOCAL_AUDIOVIDEO_REPLACE_AUDIOTRACK_WEBAUDIO(test) {
+        var pc = test.pcLocal._pc;
+        var sender = pc.getSenders().find(sn => sn.track.kind == "audio");
+        ok(sender, "track has a sender");
+        var oldSenderCount = pc.getSenders().length;
+        var oldTrack = sender.track;
+
+        var sourceNode = test.audioCtx.createOscillator();
+        sourceNode.type = 'sine';
+        // We need a frequency not too close to the fake audio track (1kHz).
+        sourceNode.frequency.value = 2000;
+        sourceNode.start();
+
+        var destNode = test.audioCtx.createMediaStreamDestination();
+        sourceNode.connect(destNode);
+        var newTrack = destNode.stream.getAudioTracks()[0];
+
+        return sender.replaceTrack(newTrack)
+          .then(() => {
+            is(pc.getSenders().length, oldSenderCount, "same sender count");
+            ok(!pc.getSenders().some(sn => sn.track == oldTrack),
+               "Replaced track should be removed from senders");
+            ok(allLocalStreamsHaveSender(pc),
+               "Shouldn't have any streams without a corresponding sender");
+            is(sender.track, newTrack, "sender.track has been replaced");
+            ok(pc.getLocalStreams().some(s => s.getTracks().includes(sender.track)),
+               "track exists among pc's local streams");
+          });
+      }
+    ]);
+    test.chain.append([
+      function PC_LOCAL_CHECK_WEBAUDIO_FLOW_PRESENT(test) {
+        return test.pcRemote.checkReceivingToneFrom(test.pcLocal);
+      }
+    ]);
     test.chain.append([
       function PC_LOCAL_INVALID_ADD_VIDEOTRACKS(test) {
         var stream = test.pcLocal._pc.getLocalStreams()[0];
         var track = stream.getVideoTracks()[0];
         try {
           test.pcLocal._pc.addTrack(track, stream);
           ok(false, "addTrack existing track should fail");
         } catch (e) {
--- a/dom/media/tests/mochitest/test_peerConnection_webAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_webAudio.html
@@ -6,117 +6,38 @@
 <body>
 <pre id="test">
 <script type="application/javascript;version=1.8">
 createHTML({
   bug: "1081819",
   title: "WebAudio on both input and output side of peerconnection"
 });
 
-// This tests WebAudio as input to a PeerConnection and a PeerConnection as
-// input to WebAudio. This is done by piping a 700Hz oscillator through an
-// analyser on the input side, the PeerConnection, and an analyser on the
-// output side. We then sanity check the audio by comparing the frequency domain
-// data from both analysers.
-
-/*
- * Use as callback to Array.reduce to get an object { value, index }
- * that contains the largest element in the array.
- */
-var maxWithIndex = function(a, b, i) {
-  if (b >= a.value) {
-    return { value: b, index: i };
-  } else {
-    return a;
-  }
-};
+// This tests WebAudio (a 700Hz OscillatorNode) as input to a PeerConnection.
+// It also tests that a PeerConnection works as input to WebAudio as the remote
+// stream is connected to an AnalyserNode and compared to the source node.
 
 runNetworkTest(function() {
   var test = new PeerConnectionTest();
-
-  var audioContext = new AudioContext();
-  var inputAnalyser, outputAnalyser;
-  var inputData, outputData;
-
+  test.audioContext = new AudioContext();
   test.setMediaConstraints([{audio: true}], []);
   test.chain.replace("PC_LOCAL_GUM", [
     function PC_LOCAL_WEBAUDIO_SOURCE(test) {
-      var oscillator = audioContext.createOscillator();
+      var oscillator = test.audioContext.createOscillator();
       oscillator.type = 'sine';
       oscillator.frequency.value = 700;
       oscillator.start();
-      inputAnalyser = audioContext.createAnalyser();
-      var dest = audioContext.createMediaStreamDestination();
-
-      oscillator.connect(inputAnalyser);
-      inputAnalyser.connect(dest);
+      var dest = test.audioContext.createMediaStreamDestination();
+      oscillator.connect(dest);
       test.pcLocal.attachMedia(dest.stream, 'audio', 'local');
-
-      return Promise.resolve();
     }
   ]);
-  test.chain.insertBefore("PC_REMOTE_SETUP_ADDSTREAM_HANDLER", [
-    function PC_REMOTE_REPLACE_ATTACHMEDIA(test) {
-      var realAttachMedia = test.pcRemote.attachMedia.bind(test.pcRemote);
-      test.pcRemote.attachMedia = function(stream, type, side) {
-        var source = audioContext.createMediaStreamSource(stream);
-        outputAnalyser = audioContext.createAnalyser();
-        var dest = audioContext.createMediaStreamDestination();
-
-        source.connect(outputAnalyser);