Merge m-c to b-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 05 Oct 2014 09:55:59 -0700
changeset 232085 644cb71aa537ad3b27fa4926a0e8867f13a3910e
parent 232084 d679551c9094418c53de88b33e9ffef5fddfb518 (current diff)
parent 232053 0ed32d9a42d67a72a18bfa13a95dd2123490c8bf (diff)
child 232086 7ffb75769ec95397be45ea299f6f8f72bfb7e095
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.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 m-c to b-i
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -55,16 +55,15 @@ MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
 
 if test "$OS_TARGET" = "Android"; then
 MOZ_NUWA_PROCESS=1
 MOZ_B2G_LOADER=1
 fi
-MOZ_FOLD_LIBS=1
 
 MOZ_JSDOWNLOADS=1
 
 MOZ_BUNDLED_FONTS=1
 
 JSGC_GENERATIONAL=1
 JS_GC_SMALL_CHUNK_SIZE=1
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1409700581000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1412277891000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
@@ -213,16 +213,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i77" id="{fa277cfc-1d75-4949-a1f9-4ac8e41b2dfd}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i710" id="{e0352044-1439-48ba-99b6-b05ed1a4d2de}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i40" id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i491" id="{515b2424-5911-40bd-8a2c-bdb20286d8f5}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -278,16 +284,22 @@
       <emItem  blockID="i630" id="webbooster@iminent.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
                   <pref>browser.startup.homepage</pref>
                   <pref>browser.search.defaultenginename</pref>
               </prefs>
     </emItem>
+      <emItem  blockID="i8" id="{B13721C7-F507-4982-B2E5-502A71474FED}">
+                        <versionRange  minVersion=" " severity="1">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i7" id="{2224e955-00e9-4613-a844-ce69fccaae91}">
                           <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i174" id="info@thebflix.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
@@ -474,16 +486,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i111" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}">
                         <versionRange  minVersion="0" maxVersion="15.0.5" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i716" id="{cc6cc772-f121-49e0-b1f0-c26583cb0c5e}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i136" id="Adobe@flash.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i672" id="/^(saamazon@mybrowserbar\.com)|(saebay@mybrowserbar\.com)$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
@@ -570,16 +588,24 @@
               </prefs>
     </emItem>
       <emItem  blockID="i42" id="{D19CA586-DD6C-4a0a-96F8-14644F340D60}">
                         <versionRange  minVersion="0.1" maxVersion="14.4.0" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i628" id="ffxtlbr@iminent.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+                  <pref>browser.startup.homepage</pref>
+                  <pref>browser.search.defaultenginename</pref>
+              </prefs>
+    </emItem>
       <emItem  blockID="i449" id="gystqfr@ylgga.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i502" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -630,22 +656,21 @@
               </prefs>
     </emItem>
       <emItem  blockID="i358" id="lfind@nijadsoft.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i628" id="ffxtlbr@iminent.com">
+      <emItem  blockID="i720" id="FXqG@xeeR.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
                   <pref>browser.startup.homepage</pref>
-                  <pref>browser.search.defaultenginename</pref>
               </prefs>
     </emItem>
       <emItem  blockID="i228" id="crossriderapp5060@crossrider.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
@@ -797,18 +822,18 @@
               </prefs>
     </emItem>
       <emItem  blockID="i678" id="{C4A4F5A0-4B89-4392-AFAC-D58010E349AF}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i8" id="{B13721C7-F507-4982-B2E5-502A71474FED}">
-                        <versionRange  minVersion=" " severity="1">
+      <emItem  blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
+                        <versionRange  minVersion="0" maxVersion="15.0.5" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i552" id="jid0-O6MIff3eO5dIGf5Tcv8RsJDKxrs@jetpack">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
@@ -929,16 +954,23 @@
               </prefs>
     </emItem>
       <emItem  blockID="i477" id="mbrnovone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i718" id="G4Ce4@w.net">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                    <prefs>
+                  <pref>browser.startup.homepage</pref>
+              </prefs>
+    </emItem>
       <emItem  blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
                           <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i446" id="{E90FA778-C2B7-41D0-9FA9-3FEC1CA54D66}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
@@ -1443,18 +1475,18 @@
               </prefs>
     </emItem>
       <emItem  blockID="i354" id="{c0c2693d-2ee8-47b4-9df7-b67a0ee31988}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
-      <emItem  blockID="i106" os="WINNT" id="{97E22097-9A2F-45b1-8DAF-36AD648C7EF4}">
-                        <versionRange  minVersion="0" maxVersion="15.0.5" severity="1">
+      <emItem  blockID="i714" id="{25dd52dc-89a8-469d-9e8f-8d483095d1e8}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
                         <versionRange  minVersion="0.1" maxVersion="*">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="9.0a1" maxVersion="9.0" />
@@ -1925,16 +1957,22 @@
               </prefs>
     </emItem>
       <emItem  blockID="i73" id="a1g0a9g219d@a1.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
+      <emItem  blockID="i712" id="{a2bfe612-4cf5-48ea-907c-f3fb25bc9d6b}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                    <prefs>
+              </prefs>
+    </emItem>
       <emItem  blockID="i96" id="youtubeee@youtuber3.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                     <prefs>
               </prefs>
     </emItem>
       <emItem  blockID="i706" id="thefoxonlybetter@quicksaver">
                         <versionRange  minVersion="1.10" maxVersion="*" severity="3">
--- a/browser/app/firefox.exe.manifest
+++ b/browser/app/firefox.exe.manifest
@@ -28,15 +28,16 @@
 </ms_asmv3:trustInfo>
   <ms_asmv3:application xmlns:ms_asmv3="urn:schemas-microsoft-com:asm.v3">
     <ms_asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
       <dpiAware>true</dpiAware>
     </ms_asmv3:windowsSettings>
   </ms_asmv3:application>
   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
     <application>
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
       <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
       <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
       <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
       <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
     </application>
   </compatibility>
 </assembly>
--- a/browser/components/loop/content/conversation.html
+++ b/browser/components/loop/content/conversation.html
@@ -28,16 +28,17 @@
     <script type="text/javascript" src="loop/shared/js/utils.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/mixins.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
     <script type="text/javascript" src="loop/shared/js/feedbackApiClient.js"></script>
     <script type="text/javascript" src="loop/shared/js/actions.js"></script>
     <script type="text/javascript" src="loop/shared/js/validate.js"></script>
     <script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
+    <script type="text/javascript" src="loop/shared/js/otSdkDriver.js"></script>
     <script type="text/javascript" src="loop/shared/js/conversationStore.js"></script>
     <script type="text/javascript" src="loop/js/conversationViews.js"></script>
     <script type="text/javascript" src="loop/shared/js/websocket.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript" src="loop/js/conversationViews.js"></script>
     <script type="text/javascript" src="loop/js/conversation.js"></script>
   </body>
 </html>
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -222,16 +222,18 @@ loop.Client = (function($) {
      * - err null on successful registration, non-null otherwise.
      * - result an object of the obtained data for starting the call, if successful
      *
      * @param {Array} calleeIds an array of emails and phone numbers.
      * @param {String} callType the type of call.
      * @param {Function} cb Callback(err, result)
      */
     setupOutgoingCall: function(calleeIds, callType, cb) {
+      // For direct calls, we only ever use the logged-in session. Direct
+      // calls by guests aren't valid.
       this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.FXA,
         "/calls", "POST", {
           calleeId: calleeIds,
           callType: callType
         },
         function (err, responseText) {
           if (err) {
             this._failureHandler(cb, err);
--- a/browser/components/loop/content/js/contacts.js
+++ b/browser/components/loop/content/js/contacts.js
@@ -12,16 +12,39 @@ loop.contacts = (function(_, mozL10n) {
   "use strict";
 
   const Button = loop.shared.views.Button;
   const ButtonGroup = loop.shared.views.ButtonGroup;
 
   // Number of contacts to add to the list at the same time.
   const CONTACTS_CHUNK_SIZE = 100;
 
+  // At least this number of contacts should be present for the filter to appear.
+  const MIN_CONTACTS_FOR_FILTERING = 7;
+
+  let getContactNames = function(contact) {
+    // The model currently does not enforce a name to be present, but we're
+    // going to assume it is awaiting more advanced validation of required fields
+    // by the model. (See bug 1069918)
+    // NOTE: this method of finding a firstname and lastname is not i18n-proof.
+    let names = contact.name[0].split(" ");
+    return {
+      firstName: names.shift(),
+      lastName: names.join(" ")
+    };
+  };
+
+  let getPreferredEmail = function(contact) {
+    // A contact may not contain email addresses, but only a phone number.
+    if (!contact.email || contact.email.length == 0) {
+      return { value: "" };
+    }
+    return contact.email.find(e => e.pref) || contact.email[0];
+  };
+
   const ContactDropdown = React.createClass({displayName: 'ContactDropdown',
     propTypes: {
       handleAction: React.PropTypes.func.isRequired,
       canEdit: React.PropTypes.bool
     },
 
     getInitialState: function () {
       return {
@@ -132,63 +155,35 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     componentShouldUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
-        this.getPreferredEmail(currContact).value !== this.getPreferredEmail(nextContact).value
+        getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
       }
     },
 
-    getContactNames: function() {
-      // The model currently does not enforce a name to be present, but we're
-      // going to assume it is awaiting more advanced validation of required fields
-      // by the model. (See bug 1069918)
-      // NOTE: this method of finding a firstname and lastname is not i18n-proof.
-      let names = this.props.contact.name[0].split(" ");
-      return {
-        firstName: names.shift(),
-        lastName: names.join(" ")
-      };
-    },
-
-    getPreferredEmail: function(contact = this.props.contact) {
-      let email;
-      // A contact may not contain email addresses, but only a phone number instead.
-      if (contact.email) {
-        email = contact.email[0];
-        contact.email.some(function(address) {
-          if (address.pref) {
-            email = address;
-            return true;
-          }
-          return false;
-        });
-      }
-      return email || { value: "" };
-    },
-
     canEdit: function() {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
-      let names = this.getContactNames();
-      let email = this.getPreferredEmail();
+      let names = getContactNames(this.props.contact);
+      let email = getPreferredEmail(this.props.contact);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
 
       return (
         React.DOM.li({className: contactCSSClass, onMouseLeave: this.hideDropdownMenu}, 
@@ -213,20 +208,23 @@ loop.contacts = (function(_, mozL10n) {
             : null
           
         )
       );
     }
   });
 
   const ContactsList = React.createClass({displayName: 'ContactsList',
+    mixins: [React.addons.LinkedStateMixin],
+
     getInitialState: function() {
       return {
         contacts: {},
-        importBusy: false
+        importBusy: false,
+        filter: "",
       };
     },
 
     componentDidMount: function() {
       let contactsAPI = navigator.mozLoop.contacts;
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
@@ -339,35 +337,58 @@ loop.contacts = (function(_, mozL10n) {
         return ContactDetail({key: item._guid, contact: item, 
                               handleContactAction: this.handleContactAction})
       };
 
       let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
+      let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
+                       MIN_CONTACTS_FOR_FILTERING;
+      if (showFilter) {
+        let filter = this.state.filter.trim().toLocaleLowerCase();
+        if (filter) {
+          let filterFn = contact => {
+            return contact.name[0].toLocaleLowerCase().contains(filter) ||
+                   getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
+          };
+          if (shownContacts.available) {
+            shownContacts.available = shownContacts.available.filter(filterFn);
+          }
+          if (shownContacts.blocked) {
+            shownContacts.blocked = shownContacts.blocked.filter(filterFn);
+          }
+        }
+      }
+
       // TODO: bug 1076767 - add a spinner whilst importing contacts.
       return (
         React.DOM.div(null, 
           React.DOM.div({className: "content-area"}, 
             ButtonGroup(null, 
               Button({caption: this.state.importBusy
                                ? mozL10n.get("importing_contacts_progress_button")
                                : mozL10n.get("import_contacts_button"), 
                       disabled: this.state.importBusy, 
                       onClick: this.handleImportButtonClick}), 
               Button({caption: mozL10n.get("new_contact_button"), 
                       onClick: this.handleAddContactButtonClick})
-            )
+            ), 
+            showFilter ?
+            React.DOM.input({className: "contact-filter", 
+                   placeholder: mozL10n.get("contacts_search_placesholder"), 
+                   valueLink: this.linkState("filter")})
+            : null
           ), 
           React.DOM.ul({className: "contact-list"}, 
             shownContacts.available ?
               shownContacts.available.sort(this.sortContacts).map(viewForItem) :
               null, 
-            shownContacts.blocked ?
+            shownContacts.blocked && shownContacts.blocked.length > 0 ?
               React.DOM.div({className: "contact-separator"}, mozL10n.get("contacts_blocked_contacts")) :
               null, 
             shownContacts.blocked ?
               shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
               null
           )
         )
       );
--- a/browser/components/loop/content/js/contacts.jsx
+++ b/browser/components/loop/content/js/contacts.jsx
@@ -12,16 +12,39 @@ loop.contacts = (function(_, mozL10n) {
   "use strict";
 
   const Button = loop.shared.views.Button;
   const ButtonGroup = loop.shared.views.ButtonGroup;
 
   // Number of contacts to add to the list at the same time.
   const CONTACTS_CHUNK_SIZE = 100;
 
+  // At least this number of contacts should be present for the filter to appear.
+  const MIN_CONTACTS_FOR_FILTERING = 7;
+
+  let getContactNames = function(contact) {
+    // The model currently does not enforce a name to be present, but we're
+    // going to assume it is awaiting more advanced validation of required fields
+    // by the model. (See bug 1069918)
+    // NOTE: this method of finding a firstname and lastname is not i18n-proof.
+    let names = contact.name[0].split(" ");
+    return {
+      firstName: names.shift(),
+      lastName: names.join(" ")
+    };
+  };
+
+  let getPreferredEmail = function(contact) {
+    // A contact may not contain email addresses, but only a phone number.
+    if (!contact.email || contact.email.length == 0) {
+      return { value: "" };
+    }
+    return contact.email.find(e => e.pref) || contact.email[0];
+  };
+
   const ContactDropdown = React.createClass({
     propTypes: {
       handleAction: React.PropTypes.func.isRequired,
       canEdit: React.PropTypes.bool
     },
 
     getInitialState: function () {
       return {
@@ -132,63 +155,35 @@ loop.contacts = (function(_, mozL10n) {
     },
 
     componentShouldUpdate: function(nextProps, nextState) {
       let currContact = this.props.contact;
       let nextContact = nextProps.contact;
       return (
         currContact.name[0] !== nextContact.name[0] ||
         currContact.blocked !== nextContact.blocked ||
-        this.getPreferredEmail(currContact).value !== this.getPreferredEmail(nextContact).value
+        getPreferredEmail(currContact).value !== getPreferredEmail(nextContact).value
       );
     },
 
     handleAction: function(actionName) {
       if (this.props.handleContactAction) {
         this.props.handleContactAction(this.props.contact, actionName);
       }
     },
 
-    getContactNames: function() {
-      // The model currently does not enforce a name to be present, but we're
-      // going to assume it is awaiting more advanced validation of required fields
-      // by the model. (See bug 1069918)
-      // NOTE: this method of finding a firstname and lastname is not i18n-proof.
-      let names = this.props.contact.name[0].split(" ");
-      return {
-        firstName: names.shift(),
-        lastName: names.join(" ")
-      };
-    },
-
-    getPreferredEmail: function(contact = this.props.contact) {
-      let email;
-      // A contact may not contain email addresses, but only a phone number instead.
-      if (contact.email) {
-        email = contact.email[0];
-        contact.email.some(function(address) {
-          if (address.pref) {
-            email = address;
-            return true;
-          }
-          return false;
-        });
-      }
-      return email || { value: "" };
-    },
-
     canEdit: function() {
       // We cannot modify imported contacts.  For the moment, the check for
       // determining whether the contact is imported is based on its category.
       return this.props.contact.category[0] != "google";
     },
 
     render: function() {
-      let names = this.getContactNames();
-      let email = this.getPreferredEmail();
+      let names = getContactNames(this.props.contact);
+      let email = getPreferredEmail(this.props.contact);
       let cx = React.addons.classSet;
       let contactCSSClass = cx({
         contact: true,
         blocked: this.props.contact.blocked
       });
 
       return (
         <li className={contactCSSClass} onMouseLeave={this.hideDropdownMenu}>
@@ -213,20 +208,23 @@ loop.contacts = (function(_, mozL10n) {
             : null
           }
         </li>
       );
     }
   });
 
   const ContactsList = React.createClass({
+    mixins: [React.addons.LinkedStateMixin],
+
     getInitialState: function() {
       return {
         contacts: {},
-        importBusy: false
+        importBusy: false,
+        filter: "",
       };
     },
 
     componentDidMount: function() {
       let contactsAPI = navigator.mozLoop.contacts;
 
       contactsAPI.getAll((err, contacts) => {
         if (err) {
@@ -339,35 +337,58 @@ loop.contacts = (function(_, mozL10n) {
         return <ContactDetail key={item._guid} contact={item}
                               handleContactAction={this.handleContactAction} />
       };
 
       let shownContacts = _.groupBy(this.state.contacts, function(contact) {
         return contact.blocked ? "blocked" : "available";
       });
 
+      let showFilter = Object.getOwnPropertyNames(this.state.contacts).length >=
+                       MIN_CONTACTS_FOR_FILTERING;
+      if (showFilter) {
+        let filter = this.state.filter.trim().toLocaleLowerCase();
+        if (filter) {
+          let filterFn = contact => {
+            return contact.name[0].toLocaleLowerCase().contains(filter) ||
+                   getPreferredEmail(contact).value.toLocaleLowerCase().contains(filter);
+          };
+          if (shownContacts.available) {
+            shownContacts.available = shownContacts.available.filter(filterFn);
+          }
+          if (shownContacts.blocked) {
+            shownContacts.blocked = shownContacts.blocked.filter(filterFn);
+          }
+        }
+      }
+
       // TODO: bug 1076767 - add a spinner whilst importing contacts.
       return (
         <div>
           <div className="content-area">
             <ButtonGroup>
               <Button caption={this.state.importBusy
                                ? mozL10n.get("importing_contacts_progress_button")
                                : mozL10n.get("import_contacts_button")}
                       disabled={this.state.importBusy}
                       onClick={this.handleImportButtonClick} />
               <Button caption={mozL10n.get("new_contact_button")}
                       onClick={this.handleAddContactButtonClick} />
             </ButtonGroup>
+            {showFilter ?
+            <input className="contact-filter"
+                   placeholder={mozL10n.get("contacts_search_placesholder")}
+                   valueLink={this.linkState("filter")} />
+            : null }
           </div>
           <ul className="contact-list">
             {shownContacts.available ?
               shownContacts.available.sort(this.sortContacts).map(viewForItem) :
               null}
-            {shownContacts.blocked ?
+            {shownContacts.blocked && shownContacts.blocked.length > 0 ?
               <div className="contact-separator">{mozL10n.get("contacts_blocked_contacts")}</div> :
               null}
             {shownContacts.blocked ?
               shownContacts.blocked.sort(this.sortContacts).map(viewForItem) :
               null}
           </ul>
         </div>
       );
--- a/browser/components/loop/content/js/conversation.js
+++ b/browser/components/loop/content/js/conversation.js
@@ -535,19 +535,25 @@ loop.conversation = (function(mozL10n) {
       set: function(guid, callback) {
         navigator.mozLoop.setLoopCharPref("ot.guid", guid);
         callback(null);
       }
     });
 
     var dispatcher = new loop.Dispatcher();
     var client = new loop.Client();
+    var sdkDriver = new loop.OTSdkDriver({
+      dispatcher: dispatcher,
+      sdk: OT
+    });
+
     var conversationStore = new loop.store.ConversationStore({}, {
       client: client,
-      dispatcher: dispatcher
+      dispatcher: dispatcher,
+      sdkDriver: sdkDriver
     });
 
     // XXX For now key this on the pref, but this should really be
     // set by the information from the mozLoop API when we can get it (bug 1072323).
     var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
 
     // XXX Old class creation for the incoming conversation view, whilst
     // we transition across (bug 1072323).
--- a/browser/components/loop/content/js/conversation.jsx
+++ b/browser/components/loop/content/js/conversation.jsx
@@ -535,19 +535,25 @@ loop.conversation = (function(mozL10n) {
       set: function(guid, callback) {
         navigator.mozLoop.setLoopCharPref("ot.guid", guid);
         callback(null);
       }
     });
 
     var dispatcher = new loop.Dispatcher();
     var client = new loop.Client();
+    var sdkDriver = new loop.OTSdkDriver({
+      dispatcher: dispatcher,
+      sdk: OT
+    });
+
     var conversationStore = new loop.store.ConversationStore({}, {
       client: client,
-      dispatcher: dispatcher
+      dispatcher: dispatcher,
+      sdkDriver: sdkDriver
     });
 
     // XXX For now key this on the pref, but this should really be
     // set by the information from the mozLoop API when we can get it (bug 1072323).
     var outgoingEmail = navigator.mozLoop.getLoopCharPref("outgoingemail");
 
     // XXX Old class creation for the incoming conversation view, whilst
     // we transition across (bug 1072323).
--- a/browser/components/loop/content/js/conversationViews.js
+++ b/browser/components/loop/content/js/conversationViews.js
@@ -153,41 +153,95 @@ loop.conversationViews = (function(mozL1
       /**
        * OT inserts inline styles into the markup. Using a listener for
        * resize events helps us trigger a full width/height on the element
        * so that they update to the correct dimensions.
        * XXX: this should be factored as a mixin.
        */
       window.addEventListener('orientationchange', this.updateVideoContainer);
       window.addEventListener('resize', this.updateVideoContainer);
+
+      // The SDK needs to know about the configuration and the elements to use
+      // for display. So the best way seems to pass the information here - ideally
+      // the sdk wouldn't need to know this, but we can't change that.
+      this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
+        publisherConfig: this._getPublisherConfig(),
+        getLocalElementFunc: this._getElement.bind(this, ".local"),
+        getRemoteElementFunc: this._getElement.bind(this, ".remote")
+      }));
     },
 
     componentWillUnmount: function() {
       window.removeEventListener('orientationchange', this.updateVideoContainer);
       window.removeEventListener('resize', this.updateVideoContainer);
     },
 
+    /**
+     * Returns either the required DOMNode
+     *
+     * @param {String} className The name of the class to get the element for.
+     */
+    _getElement: function(className) {
+      return this.getDOMNode().querySelector(className);
+    },
+
+    /**
+     * Returns the required configuration for publishing video on the sdk.
+     */
+    _getPublisherConfig: function() {
+      // height set to 100%" to fix video layout on Google Chrome
+      // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
+      return {
+        insertMode: "append",
+        width: "100%",
+        height: "100%",
+        publishVideo: this.props.video.enabled,
+        style: {
+          bugDisplayMode: "off",
+          buttonDisplayMode: "off",
+          nameDisplayMode: "off"
+        }
+      }
+    },
+
+    /**
+     * Used to update the video container whenever the orientation or size of the
+     * display area changes.
+     */
     updateVideoContainer: function() {
-      var localStreamParent = document.querySelector('.local .OT_publisher');
-      var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
+      var localStreamParent = this._getElement('.local .OT_publisher');
+      var remoteStreamParent = this._getElement('.remote .OT_subscriber');
       if (localStreamParent) {
         localStreamParent.style.width = "100%";
       }
       if (remoteStreamParent) {
         remoteStreamParent.style.height = "100%";
       }
     },
 
+    /**
+     * Hangs up the call.
+     */
     hangup: function() {
       this.props.dispatcher.dispatch(
         new sharedActions.HangupCall());
     },
 
+    /**
+     * Used to control publishing a stream - i.e. to mute a stream
+     *
+     * @param {String} type The type of stream, e.g. "audio" or "video".
+     * @param {Boolean} enabled True to enable the stream, false otherwise.
+     */
     publishStream: function(type, enabled) {
-      // XXX Add this as part of bug 972017.
+      this.props.dispatcher.dispatch(
+        new sharedActions.SetMute({
+          type: type,
+          enabled: enabled
+        }));
     },
 
     render: function() {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": !this.props.video.enabled
       });
@@ -281,17 +335,18 @@ loop.conversationViews = (function(mozL1
         case CALL_STATES.TERMINATED: {
           return (CallFailedView({
             dispatcher: this.props.dispatcher}
           ));
         }
         case CALL_STATES.ONGOING: {
           return (OngoingConversationView({
             dispatcher: this.props.dispatcher, 
-            video: {enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
+            video: {enabled: this.state.videoMuted}, 
+            audio: {enabled: this.state.audioMuted}}
             )
           );
         }
         case CALL_STATES.FINISHED: {
           return this._renderFeedbackView();
         }
         default: {
           return (PendingConversationView({
--- a/browser/components/loop/content/js/conversationViews.jsx
+++ b/browser/components/loop/content/js/conversationViews.jsx
@@ -153,41 +153,95 @@ loop.conversationViews = (function(mozL1
       /**
        * OT inserts inline styles into the markup. Using a listener for
        * resize events helps us trigger a full width/height on the element
        * so that they update to the correct dimensions.
        * XXX: this should be factored as a mixin.
        */
       window.addEventListener('orientationchange', this.updateVideoContainer);
       window.addEventListener('resize', this.updateVideoContainer);
+
+      // The SDK needs to know about the configuration and the elements to use
+      // for display. So the best way seems to pass the information here - ideally
+      // the sdk wouldn't need to know this, but we can't change that.
+      this.props.dispatcher.dispatch(new sharedActions.SetupStreamElements({
+        publisherConfig: this._getPublisherConfig(),
+        getLocalElementFunc: this._getElement.bind(this, ".local"),
+        getRemoteElementFunc: this._getElement.bind(this, ".remote")
+      }));
     },
 
     componentWillUnmount: function() {
       window.removeEventListener('orientationchange', this.updateVideoContainer);
       window.removeEventListener('resize', this.updateVideoContainer);
     },
 
+    /**
+     * Returns either the required DOMNode
+     *
+     * @param {String} className The name of the class to get the element for.
+     */
+    _getElement: function(className) {
+      return this.getDOMNode().querySelector(className);
+    },
+
+    /**
+     * Returns the required configuration for publishing video on the sdk.
+     */
+    _getPublisherConfig: function() {
+      // height set to 100%" to fix video layout on Google Chrome
+      // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1020445
+      return {
+        insertMode: "append",
+        width: "100%",
+        height: "100%",
+        publishVideo: this.props.video.enabled,
+        style: {
+          bugDisplayMode: "off",
+          buttonDisplayMode: "off",
+          nameDisplayMode: "off"
+        }
+      }
+    },
+
+    /**
+     * Used to update the video container whenever the orientation or size of the
+     * display area changes.
+     */
     updateVideoContainer: function() {
-      var localStreamParent = document.querySelector('.local .OT_publisher');
-      var remoteStreamParent = document.querySelector('.remote .OT_subscriber');
+      var localStreamParent = this._getElement('.local .OT_publisher');
+      var remoteStreamParent = this._getElement('.remote .OT_subscriber');
       if (localStreamParent) {
         localStreamParent.style.width = "100%";
       }
       if (remoteStreamParent) {
         remoteStreamParent.style.height = "100%";
       }
     },
 
+    /**
+     * Hangs up the call.
+     */
     hangup: function() {
       this.props.dispatcher.dispatch(
         new sharedActions.HangupCall());
     },
 
+    /**
+     * Used to control publishing a stream - i.e. to mute a stream
+     *
+     * @param {String} type The type of stream, e.g. "audio" or "video".
+     * @param {Boolean} enabled True to enable the stream, false otherwise.
+     */
     publishStream: function(type, enabled) {
-      // XXX Add this as part of bug 972017.
+      this.props.dispatcher.dispatch(
+        new sharedActions.SetMute({
+          type: type,
+          enabled: enabled
+        }));
     },
 
     render: function() {
       var localStreamClasses = React.addons.classSet({
         local: true,
         "local-stream": true,
         "local-stream-audio": !this.props.video.enabled
       });
@@ -281,17 +335,18 @@ loop.conversationViews = (function(mozL1
         case CALL_STATES.TERMINATED: {
           return (<CallFailedView
             dispatcher={this.props.dispatcher}
           />);
         }
         case CALL_STATES.ONGOING: {
           return (<OngoingConversationView
             dispatcher={this.props.dispatcher}
-            video={{enabled: this.state.callType === CALL_TYPES.AUDIO_VIDEO}}
+            video={{enabled: this.state.videoMuted}}
+            audio={{enabled: this.state.audioMuted}}
             />
           );
         }
         case CALL_STATES.FINISHED: {
           return this._renderFeedbackView();
         }
         default: {
           return (<PendingConversationView
--- a/browser/components/loop/content/shared/css/contacts.css
+++ b/browser/components/loop/content/shared/css/contacts.css
@@ -1,20 +1,24 @@
 /* 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/. */
 
+.content-area input.contact-filter {
+  margin-top: 14px;
+  border-radius: 10000px;
+}
+
 .contact-list {
   border-top: 1px solid #ccc;
   overflow-x: hidden;
   overflow-y: auto;
-  /* We need enough space to show the context menu of the first contact. */
-  min-height: 204px;
-  /* Show six contacts and scroll for the rest. */
-  max-height: 306px;
+  /* Space for six contacts, not affected by filtering.  This is enough space
+     to show the dropdown menu when there is only one contact. */
+  height: 306px;
 }
 
 .contact,
 .contact-separator {
   padding: 5px 10px;
   font-size: 13px;
 }
 
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -65,26 +65,61 @@ loop.shared.actions = (function() {
 
     /**
      * Used for hanging up the call at the end of a successful call.
      */
     HangupCall: Action.define("hangupCall", {
     }),
 
     /**
+     * Used to indicate the peer hung up the call.
+     */
+    PeerHungupCall: Action.define("peerHungupCall", {
+    }),
+
+    /**
      * Used for notifying of connection progress state changes.
      * The connection refers to the overall connection flow as indicated
      * on the websocket.
      */
     ConnectionProgress: Action.define("connectionProgress", {
       // The connection state from the websocket.
       wsState: String
     }),
 
     /**
      * Used for notifying of connection failures.
      */
     ConnectionFailure: Action.define("connectionFailure", {
       // A string relating to the reason the connection failed.
       reason: String
+    }),
+
+    /**
+     * Used by the ongoing views to notify stores about the elements
+     * required for the sdk.
+     */
+    SetupStreamElements: Action.define("setupStreamElements", {
+      // The configuration for the publisher/subscribe options
+      publisherConfig: Object,
+      // The local stream element
+      getLocalElementFunc: Function,
+      // The remote stream element
+      getRemoteElementFunc: Function
+    }),
+
+    /**
+     * Used for notifying that the media is now up for the call.
+     */
+    MediaConnected: Action.define("mediaConnected", {
+    }),
+
+    /**
+     * Used to mute or unmute a stream
+     */
+    SetMute: Action.define("setMute", {
+      // The part of the stream to enable, e.g. "audio" or "video"
+      type: String,
+      // Whether or not to enable the stream.
+      enabled: Boolean
     })
   };
 })();
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -3,17 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global loop:true */
 
 var loop = loop || {};
 loop.store = (function() {
 
   var sharedActions = loop.shared.actions;
-  var sharedUtils = loop.shared.utils;
+  var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   /**
    * Websocket states taken from:
    * https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
    */
   var WS_STATES = {
     // The call is starting, and the remote party is not yet being alerted.
     INIT: "init",
@@ -62,31 +62,35 @@ loop.store = (function() {
       // The error information, if there was a failure
       error: undefined,
       // True if the call is outgoing, false if not, undefined if unknown
       outgoing: undefined,
       // The id of the person being called for outgoing calls
       calleeId: undefined,
       // The call type for the call.
       // XXX Don't hard-code, this comes from the data in bug 1072323
-      callType: sharedUtils.CALL_TYPES.AUDIO_VIDEO,
+      callType: CALL_TYPES.AUDIO_VIDEO,
 
       // Call Connection information
       // The call id from the loop-server
       callId: undefined,
       // The connection progress url to connect the websocket
       progressURL: undefined,
       // The websocket token that allows connection to the progress url
       websocketToken: undefined,
       // SDK API key
       apiKey: undefined,
       // SDK session ID
       sessionId: undefined,
       // SDK session token
-      sessionToken: undefined
+      sessionToken: undefined,
+      // If the audio is muted
+      audioMuted: true,
+      // If the video is muted
+      videoMuted: true
     },
 
     /**
      * Constructor
      *
      * Options:
      * - {loop.Dispatcher} dispatcher The dispatcher for dispatching actions and
      *                                registering to consume actions.
@@ -99,38 +103,46 @@ loop.store = (function() {
       options = options || {};
 
       if (!options.dispatcher) {
         throw new Error("Missing option dispatcher");
       }
       if (!options.client) {
         throw new Error("Missing option client");
       }
+      if (!options.sdkDriver) {
+        throw new Error("Missing option sdkDriver");
+      }
 
       this.client = options.client;
       this.dispatcher = options.dispatcher;
+      this.sdkDriver = options.sdkDriver;
 
       this.dispatcher.register(this, [
         "connectionFailure",
         "connectionProgress",
         "gatherCallData",
         "connectCall",
         "hangupCall",
+        "peerHungupCall",
         "cancelCall",
-        "retryCall"
+        "retryCall",
+        "mediaConnected",
+        "setMute"
       ]);
     },
 
     /**
      * Handles the connection failure action, setting the state to
      * terminated.
      *
      * @param {sharedActions.ConnectionFailure} actionData The action data.
      */
     connectionFailure: function(actionData) {
+      this._endSession();
       this.set({
         callState: CALL_STATES.TERMINATED,
         callStateReason: actionData.reason
       });
     },
 
     /**
      * Handles the connection progress action, setting the next state
@@ -147,17 +159,25 @@ loop.store = (function() {
             this.set({callState: CALL_STATES.CONNECTING});
           }
           break;
         }
         case WS_STATES.ALERTING: {
           this.set({callState: CALL_STATES.ALERTING});
           break;
         }
-        case WS_STATES.CONNECTING:
+        case WS_STATES.CONNECTING: {
+          this.sdkDriver.connectSession({
+            apiKey: this.get("apiKey"),
+            sessionId: this.get("sessionId"),
+            sessionToken: this.get("sessionToken")
+          });
+          this.set({callState: CALL_STATES.ONGOING});
+          break;
+        }
         case WS_STATES.HALF_CONNECTED:
         case WS_STATES.CONNECTED: {
           this.set({callState: CALL_STATES.ONGOING});
           break;
         }
         default: {
           console.error("Unexpected websocket state passed to connectionProgress:",
             actionData.wsState);
@@ -174,16 +194,18 @@ loop.store = (function() {
     gatherCallData: function(actionData) {
       this.set({
         calleeId: actionData.calleeId,
         outgoing: !!actionData.calleeId,
         callId: actionData.callId,
         callState: CALL_STATES.GATHER
       });
 
+      this.videoMuted = this.get("callType") !== CALL_TYPES.AUDIO_VIDEO;
+
       if (this.get("outgoing")) {
         this._setupOutgoingCall();
       } // XXX Else, other types aren't supported yet.
     },
 
     /**
      * Handles the connect call action, this saves the appropriate
      * data and starts the connection for the websocket to notify the
@@ -195,51 +217,47 @@ loop.store = (function() {
       this.set(actionData.sessionData);
       this._connectWebSocket();
     },
 
     /**
      * Hangs up an ongoing call.
      */
     hangupCall: function() {
-      // XXX Stop the SDK once we add it.
-
-      // Ensure the websocket has been disconnected.
       if (this._websocket) {
         // Let the server know the user has hung up.
         this._websocket.mediaFail();
-        this._ensureWebSocketDisconnected();
       }
 
+      this._endSession();
+      this.set({callState: CALL_STATES.FINISHED});
+    },
+
+    /**
+     * The peer hungup the call.
+     */
+    peerHungupCall: function() {
+      this._endSession();
       this.set({callState: CALL_STATES.FINISHED});
     },
 
     /**
      * Cancels a call
      */
     cancelCall: function() {
       var callState = this.get("callState");
-      if (callState === CALL_STATES.TERMINATED) {
-        // All we need to do is close the window.
-        this.set({callState: CALL_STATES.CLOSE});
-        return;
+      if (this._websocket &&
+          (callState === CALL_STATES.CONNECTING ||
+           callState === CALL_STATES.ALERTING)) {
+         // Let the server know the user has hung up.
+        this._websocket.cancel();
       }
 
-      if (callState === CALL_STATES.CONNECTING ||
-          callState === CALL_STATES.ALERTING) {
-        if (this._websocket) {
-          // Let the server know the user has hung up.
-          this._websocket.cancel();
-          this._ensureWebSocketDisconnected();
-        }
-        this.set({callState: CALL_STATES.CLOSE});
-        return;
-      }
-
-      console.log("Unsupported cancel in state", callState);
+      this._endSession();
+      this.set({callState: CALL_STATES.CLOSE});
     },
 
     /**
      * Retries a call
      */
     retryCall: function() {
       var callState = this.get("callState");
       if (callState !== CALL_STATES.TERMINATED) {
@@ -249,16 +267,33 @@ loop.store = (function() {
 
       this.set({callState: CALL_STATES.GATHER});
       if (this.get("outgoing")) {
         this._setupOutgoingCall();
       }
     },
 
     /**
+     * Notifies that all media is now connected
+     */
+    mediaConnected: function() {
+      this._websocket.mediaUp();
+    },
+
+    /**
+     * Records the mute state for the stream.
+     *
+     * @param {sharedActions.setMute} actionData The mute state for the stream type.
+     */
+    setMute: function(actionData) {
+      var muteType = actionData.type + "Muted";
+      this.set(muteType, actionData.enabled);
+    },
+
+    /**
      * Obtains the outgoing call data from the server and handles the
      * result.
      */
     _setupOutgoingCall: function() {
       // XXX For now, we only have one calleeId, so just wrap that in an array.
       this.client.setupOutgoingCall([this.get("calleeId")],
         this.get("callType"),
         function(err, result) {
@@ -303,24 +338,27 @@ loop.store = (function() {
           }));
         }.bind(this)
       );
 
       this.listenTo(this._websocket, "progress", this._handleWebSocketProgress);
     },
 
     /**
-     * Ensures the websocket gets disconnected.
+     * Ensures the session is ended and the websocket is disconnected.
      */
-    _ensureWebSocketDisconnected: function() {
-     this.stopListening(this._websocket);
+    _endSession: function(nextState) {
+      this.sdkDriver.disconnectSession();
+      if (this._websocket) {
+        this.stopListening(this._websocket);
 
-      // Now close the websocket.
-      this._websocket.close();
-      delete this._websocket;
+        // Now close the websocket.
+        this._websocket.close();
+        delete this._websocket;
+      }
     },
 
     /**
      * Used to handle any progressed received from the websocket. This will
      * dispatch new actions so that the data can be handled appropriately.
      */
     _handleWebSocketProgress: function(progressData) {
       var action;
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -0,0 +1,237 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global loop:true */
+
+var loop = loop || {};
+loop.OTSdkDriver = (function() {
+
+  var sharedActions = loop.shared.actions;
+
+  /**
+   * This is a wrapper for the OT sdk. It is used to translate the SDK events into
+   * actions, and instruct the SDK what to do as a result of actions.
+   */
+  var OTSdkDriver = function(options) {
+      if (!options.dispatcher) {
+        throw new Error("Missing option dispatcher");
+      }
+      if (!options.sdk) {
+        throw new Error("Missing option sdk");
+      }
+
+      this.dispatcher = options.dispatcher;
+      this.sdk = options.sdk;
+
+      this.dispatcher.register(this, [
+        "setupStreamElements",
+        "setMute"
+      ]);
+  };
+
+  OTSdkDriver.prototype = {
+    /**
+     * Handles the setupStreamElements action. Saves the required data and
+     * kicks off the initialising of the publisher.
+     *
+     * @param {sharedActions.SetupStreamElements} actionData The data associated
+     *   with the action. See action.js.
+     */
+    setupStreamElements: function(actionData) {
+      this.getLocalElement = actionData.getLocalElementFunc;
+      this.getRemoteElement = actionData.getRemoteElementFunc;
+      this.publisherConfig = actionData.publisherConfig;
+
+      // At this state we init the publisher, even though we might be waiting for
+      // the initial connect of the session. This saves time when setting up
+      // the media.
+      this.publisher = this.sdk.initPublisher(this.getLocalElement(),
+        this.publisherConfig,
+        this._onPublishComplete.bind(this));
+    },
+
+    /**
+     * Handles the setMute action. Informs the published stream to mute
+     * or unmute audio as appropriate.
+     *
+     * @param {sharedActions.SetMute} actionData The data associated with the
+     *                                           action. See action.js.
+     */
+    setMute: function(actionData) {
+      if (actionData.type === "audio") {
+        this.publisher.publishAudio(actionData.enabled);
+      } else {
+        this.publisher.publishVideo(actionData.enabled);
+      }
+    },
+
+    /**
+     * Connects a session for the SDK, listening to the required events.
+     *
+     * sessionData items:
+     * - sessionId: The OT session ID
+     * - apiKey: The OT API key
+     * - sessionToken: The token for the OT session
+     *
+     * @param {Object} sessionData The session data for setting up the OT session.
+     */
+    connectSession: function(sessionData) {
+      this.session = this.sdk.initSession(sessionData.sessionId);
+
+      this.session.on("streamCreated", this._onRemoteStreamCreated.bind(this));
+      this.session.on("connectionDestroyed",
+        this._onConnectionDestroyed.bind(this));
+      this.session.on("sessionDisconnected",
+        this._onSessionDisconnected.bind(this));
+
+      // This starts the actual session connection.
+      this.session.connect(sessionData.apiKey, sessionData.sessionToken,
+        this._onConnectionComplete.bind(this));
+    },
+
+    /**
+     * Disconnects the sdk session.
+     */
+    disconnectSession: function() {
+      if (this.session) {
+        this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
+        this.session.off("connectionDestroyed",
+          this._onConnectionDestroyed.bind(this));
+        this.session.off("sessionDisconnected",
+          this._onSessionDisconnected.bind(this));
+
+        this.session.disconnect();
+        delete this.session;
+      }
+      if (this.publisher) {
+        this.publisher.destroy();
+        delete this.publisher;
+      }
+
+      // Also, tidy these variables ready for next time.
+      delete this._sessionConnected;
+      delete this._publisherReady;
+      delete this._publishedLocalStream;
+      delete this._subscribedRemoteStream;
+    },
+
+    /**
+     * Called once the session has finished connecting.
+     *
+     * @param {Error} error An OT error object, null if there was no error.
+     */
+    _onConnectionComplete: function(error) {
+      if (error) {
+        console.error("Failed to complete connection", error);
+        this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
+          reason: "couldNotConnect"
+        }));
+        return;
+      }
+
+      this._sessionConnected = true;
+      this._maybePublishLocalStream();
+    },
+
+    /**
+     * Handles the connection event for a peer's connection being dropped.
+     *
+     * @param {SessionDisconnectEvent} event The event details
+     * https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
+     */
+    _onConnectionDestroyed: function(event) {
+      var action;
+      if (event.reason === "clientDisconnected") {
+        action = new sharedActions.PeerHungupCall();
+      } else {
+        // Strictly speaking this isn't a failure on our part, but since our
+        // flow requires a full reconnection, then we just treat this as
+        // if a failure of our end had occurred.
+        action = new sharedActions.ConnectionFailure({
+          reason: "peerNetworkDisconnected"
+        });
+      }
+      this.dispatcher.dispatch(action);
+    },
+
+    /**
+     * Handles the session event for the connection for this client being
+     * destroyed.
+     *
+     * @param {SessionDisconnectEvent} event The event details:
+     * https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
+     */
+    _onSessionDisconnected: function(event) {
+      // We only need to worry about the network disconnected reason here.
+      if (event.reason === "networkDisconnected") {
+        this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
+          reason: "networkDisconnected"
+        }));
+      }
+    },
+
+    /**
+     * Handles the event when the remote stream is created.
+     *
+     * @param {StreamEvent} event The event details:
+     * https://tokbox.com/opentok/libraries/client/js/reference/StreamEvent.html
+     */
+    _onRemoteStreamCreated: function(event) {
+      this.session.subscribe(event.stream,
+        this.getRemoteElement(), this.publisherConfig);
+
+      this._subscribedRemoteStream = true;
+      if (this._checkAllStreamsConnected()) {
+        this.dispatcher.dispatch(new sharedActions.MediaConnected());
+      }
+    },
+
+    /**
+     * Handles the publishing being complete.
+     *
+     * @param {Error} error An OT error object, null if there was no error.
+     */
+    _onPublishComplete: function(error) {
+      if (error) {
+        console.error("Failed to initialize publisher", error);
+        this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
+          reason: "noMedia"
+        }));
+        return;
+      }
+
+      this._publisherReady = true;
+      this._maybePublishLocalStream();
+    },
+
+    /**
+     * Publishes the local stream if the session is connected
+     * and the publisher is ready.
+     */
+    _maybePublishLocalStream: function() {
+      if (this._sessionConnected && this._publisherReady) {
+        // We are clear to publish the stream to the session.
+        this.session.publish(this.publisher);
+
+        // Now record the fact, and check if we've got all media yet.
+        this._publishedLocalStream = true;
+        if (this._checkAllStreamsConnected()) {
+          this.dispatcher.dispatch(new sharedActions.MediaConnected());
+        }
+      }
+    },
+
+    /**
+     * Used to check if both local and remote streams are available
+     * and send an action if they are.
+     */
+    _checkAllStreamsConnected: function() {
+      return this._publishedLocalStream &&
+        this._subscribedRemoteStream;
+    }
+  };
+
+  return OTSdkDriver;
+
+})();
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -54,16 +54,17 @@ browser.jar:
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js           (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js (content/shared/js/conversationStore.js)
   content/browser/loop/shared/js/dispatcher.js        (content/shared/js/dispatcher.js)
   content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
   content/browser/loop/shared/js/models.js            (content/shared/js/models.js)
   content/browser/loop/shared/js/mixins.js            (content/shared/js/mixins.js)
+  content/browser/loop/shared/js/otSdkDriver.js       (content/shared/js/otSdkDriver.js)
   content/browser/loop/shared/js/views.js             (content/shared/js/views.js)
   content/browser/loop/shared/js/utils.js             (content/shared/js/utils.js)
   content/browser/loop/shared/js/validate.js          (content/shared/js/validate.js)
   content/browser/loop/shared/js/websocket.js         (content/shared/js/websocket.js)
 
   # Shared libs
 #ifdef DEBUG
   content/browser/loop/shared/libs/react-0.11.1.js    (content/shared/libs/react-0.11.1.js)
--- a/browser/components/loop/test/desktop-local/conversationViews_test.js
+++ b/browser/components/loop/test/desktop-local/conversationViews_test.js
@@ -156,36 +156,130 @@ describe("loop.conversationViews", funct
         React.addons.TestUtils.Simulate.click(cancelBtn);
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "cancelCall"));
       });
   });
 
+  describe("OngoingConversationView", function() {
+    function mountTestComponent(props) {
+      return TestUtils.renderIntoDocument(
+        loop.conversationViews.OngoingConversationView(props));
+    }
+
+    it("should dispatch a setupStreamElements action when the view is created",
+      function() {
+        view = mountTestComponent({
+          dispatcher: dispatcher
+        });
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "setupStreamElements"));
+      });
+
+    it("should dispatch a hangupCall action when the hangup button is pressed",
+      function() {
+        view = mountTestComponent({
+          dispatcher: dispatcher
+        });
+
+        var hangupBtn = view.getDOMNode().querySelector('.btn-hangup');
+
+        React.addons.TestUtils.Simulate.click(hangupBtn);
+
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "hangupCall"));
+      });
+
+    it("should dispatch a setMute action when the audio mute button is pressed",
+      function() {
+        view = mountTestComponent({
+          dispatcher: dispatcher,
+          audio: {enabled: false}
+        });
+
+        var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
+
+        React.addons.TestUtils.Simulate.click(muteBtn);
+
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "setMute"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("enabled", true));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("type", "audio"));
+      });
+
+    it("should dispatch a setMute action when the video mute button is pressed",
+      function() {
+        view = mountTestComponent({
+          dispatcher: dispatcher,
+          video: {enabled: true}
+        });
+
+        var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
+
+        React.addons.TestUtils.Simulate.click(muteBtn);
+
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "setMute"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("enabled", false));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("type", "video"));
+      });
+
+    it("should set the mute button as mute off", function() {
+      view = mountTestComponent({
+        dispatcher: dispatcher,
+        video: {enabled: true}
+      });
+
+      var muteBtn = view.getDOMNode().querySelector('.btn-mute-video');
+
+      expect(muteBtn.classList.contains("muted")).eql(false);
+    });
+
+    it("should set the mute button as mute on", function() {
+      view = mountTestComponent({
+        dispatcher: dispatcher,
+        audio: {enabled: false}
+      });
+
+      var muteBtn = view.getDOMNode().querySelector('.btn-mute-audio');
+
+      expect(muteBtn.classList.contains("muted")).eql(true);
+    });
+  });
+
   describe("OutgoingConversationView", function() {
     var store;
 
     function mountTestComponent() {
       return TestUtils.renderIntoDocument(
         loop.conversationViews.OutgoingConversationView({
+          dispatcher: dispatcher,
           store: store
         }));
     }
 
     beforeEach(function() {
-      store = new loop.store.ConversationStore({}, {
-        dispatcher: dispatcher,
-        client: {}
-      });
-
       navigator.mozLoop = {
         getLoopCharPref: function() { return "fake"; },
         appVersionInfo: sinon.spy()
       };
+
+      store = new loop.store.ConversationStore({}, {
+        dispatcher: dispatcher,
+        client: {},
+        sdkDriver: {}
+      });
     });
 
     afterEach(function() {
       delete navigator.mozLoop;
     });
 
     it("should render the CallFailedView when the call state is 'terminated'",
       function() {
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -138,17 +138,18 @@ describe("loop.conversation", function()
       oldTitle = document.title;
       client = new loop.Client();
       conversation = new loop.shared.models.ConversationModel({}, {
         sdk: {}
       });
       dispatcher = new loop.Dispatcher();
       store = new loop.store.ConversationStore({}, {
         client: client,
-        dispatcher: dispatcher
+        dispatcher: dispatcher,
+        sdkDriver: {}
       });
     });
 
     afterEach(function() {
       ccView = undefined;
       document.title = oldTitle;
     });
 
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -37,16 +37,17 @@
   <script src="../../content/shared/js/conversationStore.js"></script>
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/actions.js"></script>
   <script src="../../content/shared/js/validate.js"></script>
   <script src="../../content/shared/js/dispatcher.js"></script>
+  <script src="../../content/shared/js/otSdkDriver.js"></script>
   <script src="../../content/js/client.js"></script>
   <script src="../../content/js/conversationViews.js"></script>
   <script src="../../content/js/conversation.js"></script>
   <script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
   <script src="../../content/js/panel.js"></script>
 
   <!-- Test scripts -->
   <script src="client_test.js"></script>
--- a/browser/components/loop/test/shared/conversationStore_test.js
+++ b/browser/components/loop/test/shared/conversationStore_test.js
@@ -5,18 +5,19 @@ var expect = chai.expect;
 
 describe("loop.ConversationStore", function () {
   "use strict";
 
   var CALL_STATES = loop.store.CALL_STATES;
   var WS_STATES = loop.store.WS_STATES;
   var sharedActions = loop.shared.actions;
   var sharedUtils = loop.shared.utils;
-  var sandbox, dispatcher, client, store, fakeSessionData;
+  var sandbox, dispatcher, client, store, fakeSessionData, sdkDriver;
   var connectPromise, resolveConnectPromise, rejectConnectPromise;
+  var wsCancelSpy, wsCloseSpy, wsMediaUpSpy, fakeWebsocket;
 
   function checkFailures(done, f) {
     try {
       f();
       done();
     } catch (err) {
       done(err);
     }
@@ -24,19 +25,35 @@ describe("loop.ConversationStore", funct
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
 
     dispatcher = new loop.Dispatcher();
     client = {
       setupOutgoingCall: sinon.stub()
     };
+    sdkDriver = {
+      connectSession: sinon.stub(),
+      disconnectSession: sinon.stub()
+    };
+
+    wsCancelSpy = sinon.spy();
+    wsCloseSpy = sinon.spy();
+    wsMediaUpSpy = sinon.spy();
+
+    fakeWebsocket = {
+      cancel: wsCancelSpy,
+      close: wsCloseSpy,
+      mediaUp: wsMediaUpSpy
+    };
+
     store = new loop.store.ConversationStore({}, {
       client: client,
-      dispatcher: dispatcher
+      dispatcher: dispatcher,
+      sdkDriver: sdkDriver
     });
     fakeSessionData = {
       apiKey: "fakeKey",
       callId: "142536",
       sessionId: "321456",
       sessionToken: "341256",
       websocketToken: "543216",
       progressURL: "fakeURL"
@@ -58,28 +75,61 @@ describe("loop.ConversationStore", funct
 
   afterEach(function() {
     sandbox.restore();
   });
 
   describe("#initialize", function() {
     it("should throw an error if the dispatcher is missing", function() {
       expect(function() {
-        new loop.store.ConversationStore({}, {client: client});
+        new loop.store.ConversationStore({}, {
+          client: client,
+          sdkDriver: sdkDriver
+        });
       }).to.Throw(/dispatcher/);
     });
 
     it("should throw an error if the client is missing", function() {
       expect(function() {
-        new loop.store.ConversationStore({}, {dispatcher: dispatcher});
+        new loop.store.ConversationStore({}, {
+          dispatcher: dispatcher,
+          sdkDriver: sdkDriver
+        });
       }).to.Throw(/client/);
     });
+
+    it("should throw an error if the sdkDriver is missing", function() {
+      expect(function() {
+        new loop.store.ConversationStore({}, {
+          client: client,
+          dispatcher: dispatcher
+        });
+      }).to.Throw(/sdkDriver/);
+    });
   });
 
   describe("#connectionFailure", function() {
+    beforeEach(function() {
+      store._websocket = fakeWebsocket;
+    });
+
+    it("should disconnect the session", function() {
+      dispatcher.dispatch(
+        new sharedActions.ConnectionFailure({reason: "fake"}));
+
+      sinon.assert.calledOnce(sdkDriver.disconnectSession);
+    });
+
+    it("should ensure the websocket is closed", function() {
+      dispatcher.dispatch(
+        new sharedActions.ConnectionFailure({reason: "fake"}));
+
+      sinon.assert.calledOnce(wsCloseSpy);
+    });
+
     it("should set the state to 'terminated'", function() {
       store.set({callState: CALL_STATES.ALERTING});
 
       dispatcher.dispatch(
         new sharedActions.ConnectionFailure({reason: "fake"}));
 
       expect(store.get("callState")).eql(CALL_STATES.TERMINATED);
       expect(store.get("callStateReason")).eql("fake");
@@ -114,45 +164,39 @@ describe("loop.ConversationStore", funct
         dispatcher.dispatch(
           new sharedActions.ConnectionProgress({wsState: WS_STATES.ALERTING}));
 
         expect(store.get("callState")).eql(CALL_STATES.ALERTING);
       });
     });
 
     describe("progress: connecting", function() {
-      it("should change the state to 'ongoing'", function() {
+      beforeEach(function() {
         store.set({callState: CALL_STATES.ALERTING});
+      });
 
+      it("should change the state to 'ongoing'", function() {
         dispatcher.dispatch(
           new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
 
         expect(store.get("callState")).eql(CALL_STATES.ONGOING);
       });
-    });
 
-    describe("progress: half-connected", function() {
-      it("should change the state to 'ongoing'", function() {
-        store.set({callState: CALL_STATES.ALERTING});
+      it("should connect the session", function() {
+        store.set(fakeSessionData);
 
         dispatcher.dispatch(
-          new sharedActions.ConnectionProgress({wsState: WS_STATES.HALF_CONNECTED}));
-
-        expect(store.get("callState")).eql(CALL_STATES.ONGOING);
-      });
-    });
+          new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTING}));
 
-    describe("progress: connecting", function() {
-      it("should change the state to 'ongoing'", function() {
-        store.set({callState: CALL_STATES.ALERTING});
-
-        dispatcher.dispatch(
-          new sharedActions.ConnectionProgress({wsState: WS_STATES.CONNECTED}));
-
-        expect(store.get("callState")).eql(CALL_STATES.ONGOING);
+        sinon.assert.calledOnce(sdkDriver.connectSession);
+        sinon.assert.calledWithExactly(sdkDriver.connectSession, {
+          apiKey: "fakeKey",
+          sessionId: "321456",
+          sessionToken: "341256"
+        });
       });
     });
   });
 
   describe("#gatherCallData", function() {
     beforeEach(function() {
       store.set({callState: CALL_STATES.INIT});
     });
@@ -327,16 +371,22 @@ describe("loop.ConversationStore", funct
 
       store._websocket = {
         mediaFail: wsMediaFailSpy,
         close: wsCloseSpy
       };
       store.set({callState: CALL_STATES.ONGOING});
     });
 
+    it("should disconnect the session", function() {
+      dispatcher.dispatch(new sharedActions.HangupCall());
+
+      sinon.assert.calledOnce(sdkDriver.disconnectSession);
+    });
+
     it("should send a media-fail message to the websocket if it is open", function() {
       dispatcher.dispatch(new sharedActions.HangupCall());
 
       sinon.assert.calledOnce(wsMediaFailSpy);
     });
 
     it("should ensure the websocket is closed", function() {
       dispatcher.dispatch(new sharedActions.HangupCall());
@@ -346,56 +396,87 @@ describe("loop.ConversationStore", funct
 
     it("should set the callState to finished", function() {
       dispatcher.dispatch(new sharedActions.HangupCall());
 
       expect(store.get("callState")).eql(CALL_STATES.FINISHED);
     });
   });
 
+  describe("#peerHungupCall", function() {
+    var wsMediaFailSpy, wsCloseSpy;
+    beforeEach(function() {
+      wsMediaFailSpy = sinon.spy();
+      wsCloseSpy = sinon.spy();
+
+      store._websocket = {
+        mediaFail: wsMediaFailSpy,
+        close: wsCloseSpy
+      };
+      store.set({callState: CALL_STATES.ONGOING});
+    });
+
+    it("should disconnect the session", function() {
+      dispatcher.dispatch(new sharedActions.PeerHungupCall());
+
+      sinon.assert.calledOnce(sdkDriver.disconnectSession);
+    });
+
+    it("should ensure the websocket is closed", function() {
+      dispatcher.dispatch(new sharedActions.PeerHungupCall());
+
+      sinon.assert.calledOnce(wsCloseSpy);
+    });
+
+    it("should set the callState to finished", function() {
+      dispatcher.dispatch(new sharedActions.PeerHungupCall());
+
+      expect(store.get("callState")).eql(CALL_STATES.FINISHED);
+    });
+  });
+
   describe("#cancelCall", function() {
+    beforeEach(function() {
+      store._websocket = fakeWebsocket;
+
+      store.set({callState: CALL_STATES.CONNECTING});
+    });
+
+    it("should disconnect the session", function() {
+      dispatcher.dispatch(new sharedActions.CancelCall());
+
+      sinon.assert.calledOnce(sdkDriver.disconnectSession);
+    });
+
+    it("should send a cancel message to the websocket if it is open", function() {
+      dispatcher.dispatch(new sharedActions.CancelCall());
+
+      sinon.assert.calledOnce(wsCancelSpy);
+    });
+
+    it("should ensure the websocket is closed", function() {
+      dispatcher.dispatch(new sharedActions.CancelCall());
+
+      sinon.assert.calledOnce(wsCloseSpy);
+    });
+
+    it("should set the state to close if the call is connecting", function() {
+      dispatcher.dispatch(new sharedActions.CancelCall());
+
+      expect(store.get("callState")).eql(CALL_STATES.CLOSE);
+    });
+
     it("should set the state to close if the call has terminated already", function() {
       store.set({callState: CALL_STATES.TERMINATED});
 
       dispatcher.dispatch(new sharedActions.CancelCall());
 
       expect(store.get("callState")).eql(CALL_STATES.CLOSE);
     });
 
-    describe("whilst connecting", function() {
-      var wsCancelSpy, wsCloseSpy;
-      beforeEach(function() {
-        wsCancelSpy = sinon.spy();
-        wsCloseSpy = sinon.spy();
-
-        store._websocket = {
-          cancel: wsCancelSpy,
-          close: wsCloseSpy
-        };
-        store.set({callState: CALL_STATES.CONNECTING});
-      });
-
-      it("should send a cancel message to the websocket if it is open", function() {
-        dispatcher.dispatch(new sharedActions.CancelCall());
-
-        sinon.assert.calledOnce(wsCancelSpy);
-      });
-
-      it("should ensure the websocket is closed", function() {
-        dispatcher.dispatch(new sharedActions.CancelCall());
-
-        sinon.assert.calledOnce(wsCloseSpy);
-      });
-
-      it("should set the state to close if the call is connecting", function() {
-        dispatcher.dispatch(new sharedActions.CancelCall());
-
-        expect(store.get("callState")).eql(CALL_STATES.CLOSE);
-      });
-    });
   });
 
   describe("#retryCall", function() {
     it("should set the state to gather", function() {
       store.set({callState: CALL_STATES.TERMINATED});
 
       dispatcher.dispatch(new sharedActions.RetryCall());
 
@@ -413,16 +494,50 @@ describe("loop.ConversationStore", funct
       dispatcher.dispatch(new sharedActions.RetryCall());
 
       sinon.assert.calledOnce(client.setupOutgoingCall);
       sinon.assert.calledWith(client.setupOutgoingCall,
         ["fake"], sharedUtils.CALL_TYPES.AUDIO_VIDEO);
     });
   });
 
+  describe("#mediaConnected", function() {
+    it("should send mediaUp via the websocket", function() {
+      store._websocket = fakeWebsocket;
+
+      dispatcher.dispatch(new sharedActions.MediaConnected());
+
+      sinon.assert.calledOnce(wsMediaUpSpy);
+    });
+  });
+
+  describe("#setMute", function() {
+    it("should save the mute state for the audio stream", function() {
+      store.set({"audioMuted": false});
+
+      dispatcher.dispatch(new sharedActions.SetMute({
+        type: "audio",
+        enabled: true
+      }));
+
+      expect(store.get("audioMuted")).eql(true);
+    });
+
+    it("should save the mute state for the video stream", function() {
+      store.set({"videoMuted": true});
+
+      dispatcher.dispatch(new sharedActions.SetMute({
+        type: "video",
+        enabled: false
+      }));
+
+      expect(store.get("videoMuted")).eql(false);
+    });
+  });
+
   describe("Events", function() {
     describe("Websocket progress", function() {
       beforeEach(function() {
         dispatcher.dispatch(
           new sharedActions.ConnectCall({sessionData: fakeSessionData}));
 
         sandbox.stub(dispatcher, "dispatch");
       });
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -37,27 +37,29 @@
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
   <script src="../../content/shared/js/validate.js"></script>
   <script src="../../content/shared/js/actions.js"></script>
   <script src="../../content/shared/js/dispatcher.js"></script>
+  <script src="../../content/shared/js/otSdkDriver.js"></script>
   <script src="../../content/shared/js/conversationStore.js"></script>
 
   <!-- Test scripts -->
   <script src="models_test.js"></script>
   <script src="mixins_test.js"></script>
   <script src="utils_test.js"></script>
   <script src="views_test.js"></script>
   <script src="websocket_test.js"></script>
   <script src="feedbackApiClient_test.js"></script>
   <script src="validate_test.js"></script>
   <script src="dispatcher_test.js"></script>
   <script src="conversationStore_test.js"></script>
+  <script src="otSdkDriver_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -0,0 +1,300 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var expect = chai.expect;
+
+describe("loop.OTSdkDriver", function () {
+  "use strict";
+
+  var sharedActions = loop.shared.actions;
+
+  var sandbox;
+  var dispatcher, driver, publisher, sdk, session, sessionData;
+  var fakeLocalElement, fakeRemoteElement, publisherConfig;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+
+    fakeLocalElement = {fake: 1};
+    fakeRemoteElement = {fake: 2};
+    publisherConfig = {
+      fake: "config"
+    };
+    sessionData = {
+      apiKey: "1234567890",
+      sessionId: "3216549870",
+      sessionToken: "1357924680"
+    };
+
+    dispatcher = new loop.Dispatcher();
+    session = _.extend({
+      connect: sinon.stub(),
+      disconnect: sinon.stub(),
+      publish: sinon.stub(),
+      subscribe: sinon.stub()
+    }, Backbone.Events);
+
+    publisher = {
+      destroy: sinon.stub(),
+      publishAudio: sinon.stub(),
+      publishVideo: sinon.stub()
+    };
+
+    sdk = {
+      initPublisher: sinon.stub(),
+      initSession: sinon.stub().returns(session)
+    };
+
+    driver = new loop.OTSdkDriver({
+      dispatcher: dispatcher,
+      sdk: sdk
+    });
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("Constructor", function() {
+    it("should throw an error if the dispatcher is missing", function() {
+      expect(function() {
+        new loop.OTSdkDriver({sdk: sdk});
+      }).to.Throw(/dispatcher/);
+    });
+
+    it("should throw an error if the sdk is missing", function() {
+      expect(function() {
+        new loop.OTSdkDriver({dispatcher: dispatcher});
+      }).to.Throw(/sdk/);
+    });
+  });
+
+  describe("#setupStreamElements", function() {
+    it("should call initPublisher", function() {
+      dispatcher.dispatch(new sharedActions.SetupStreamElements({
+        getLocalElementFunc: function() {return fakeLocalElement;},
+        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        publisherConfig: publisherConfig
+      }));
+
+      sinon.assert.calledOnce(sdk.initPublisher);
+      sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
+    });
+
+    describe("On Publisher Complete", function() {
+      it("should publish the stream if the connection is ready", function() {
+        sdk.initPublisher.callsArgWith(2, null);
+
+        driver.session = session;
+        driver._sessionConnected = true;
+
+        dispatcher.dispatch(new sharedActions.SetupStreamElements({
+          getLocalElementFunc: function() {return fakeLocalElement;},
+          getRemoteElementFunc: function() {return fakeRemoteElement;},
+          publisherConfig: publisherConfig
+        }));
+
+        sinon.assert.calledOnce(session.publish);
+      });
+
+      it("should dispatch connectionFailure if connecting failed", function() {
+        sdk.initPublisher.callsArgWith(2, new Error("Failure"));
+
+        // Special stub, as we want to use the dispatcher, but also know that
+        // we've been called correctly for the second dispatch.
+        var dispatchStub = (function() {
+          var originalDispatch = dispatcher.dispatch.bind(dispatcher);
+          return sandbox.stub(dispatcher, "dispatch", function(action) {
+            originalDispatch(action);
+          });
+        }());
+
+        driver.session = session;
+        driver._sessionConnected = true;
+
+        dispatcher.dispatch(new sharedActions.SetupStreamElements({
+          getLocalElementFunc: function() {return fakeLocalElement;},
+          getRemoteElementFunc: function() {return fakeRemoteElement;},
+          publisherConfig: publisherConfig
+        }));
+
+        sinon.assert.called(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionFailure"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("reason", "noMedia"));
+      });
+    });
+  });
+
+  describe("#setMute", function() {
+    beforeEach(function() {
+      sdk.initPublisher.returns(publisher);
+
+      dispatcher.dispatch(new sharedActions.SetupStreamElements({
+        getLocalElementFunc: function() {return fakeLocalElement;},
+        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        publisherConfig: publisherConfig
+      }));
+    });
+
+    it("should publishAudio with the correct enabled value", function() {
+      dispatcher.dispatch(new sharedActions.SetMute({
+        type: "audio",
+        enabled: false
+      }));
+
+      sinon.assert.calledOnce(publisher.publishAudio);
+      sinon.assert.calledWithExactly(publisher.publishAudio, false);
+    });
+
+    it("should publishVideo with the correct enabled value", function() {
+      dispatcher.dispatch(new sharedActions.SetMute({
+        type: "video",
+        enabled: true
+      }));
+
+      sinon.assert.calledOnce(publisher.publishVideo);
+      sinon.assert.calledWithExactly(publisher.publishVideo, true);
+    });
+  });
+
+  describe("#connectSession", function() {
+    it("should initialise a new session", function() {
+      driver.connectSession(sessionData);
+
+      sinon.assert.calledOnce(sdk.initSession);
+      sinon.assert.calledWithExactly(sdk.initSession, "3216549870");
+    });
+
+    it("should connect the session", function () {
+      driver.connectSession(sessionData);
+
+      sinon.assert.calledOnce(session.connect);
+      sinon.assert.calledWith(session.connect, "1234567890", "1357924680");
+    });
+
+    describe("On connection complete", function() {
+      it("should publish the stream if the publisher is ready", function() {
+        driver._publisherReady = true;
+        session.connect.callsArg(2);
+
+        driver.connectSession(sessionData);
+
+        sinon.assert.calledOnce(session.publish);
+      });
+
+      it("should dispatch connectionFailure if connecting failed", function() {
+        session.connect.callsArgWith(2, new Error("Failure"));
+        sandbox.stub(dispatcher, "dispatch");
+
+        driver.connectSession(sessionData);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionFailure"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("reason", "couldNotConnect"));
+      });
+    });
+  });
+
+  describe("#disconnectionSession", function() {
+    it("should disconnect the session", function() {
+      driver.session = session;
+
+      driver.disconnectSession();
+
+      sinon.assert.calledOnce(session.disconnect);
+    });
+
+    it("should destroy the publisher", function() {
+      driver.publisher = publisher;
+
+      driver.disconnectSession();
+
+      sinon.assert.calledOnce(publisher.destroy);
+    });
+  });
+
+  describe("Events", function() {
+    beforeEach(function() {
+      driver.connectSession(sessionData);
+
+      dispatcher.dispatch(new sharedActions.SetupStreamElements({
+        getLocalElementFunc: function() {return fakeLocalElement;},
+        getRemoteElementFunc: function() {return fakeRemoteElement;},
+        publisherConfig: publisherConfig
+      }));
+
+      sandbox.stub(dispatcher, "dispatch");
+    });
+
+    describe("connectionDestroyed", function() {
+      it("should dispatch a peerHungupCall action if the client disconnected", function() {
+        session.trigger("connectionDestroyed", {
+          reason: "clientDisconnected"
+        });
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "peerHungupCall"));
+      });
+
+      it("should dispatch a connectionFailure action if the connection failed", function() {
+        session.trigger("connectionDestroyed", {
+          reason: "networkDisconnected"
+        });
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionFailure"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("reason", "peerNetworkDisconnected"));
+      });
+    });
+
+    describe("sessionDisconnected", function() {
+      it("should dispatch a connectionFailure action if the session was disconnected",
+        function() {
+          session.trigger("sessionDisconnected", {
+            reason: "networkDisconnected"
+          });
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("name", "connectionFailure"));
+          sinon.assert.calledWithMatch(dispatcher.dispatch,
+            sinon.match.hasOwn("reason", "networkDisconnected"));
+        });
+    });
+
+    describe("streamCreated", function() {
+      var fakeStream;
+
+      beforeEach(function() {
+        fakeStream = {
+          fakeStream: 3
+        };
+      });
+
+      it("should subscribe to the stream", function() {
+        session.trigger("streamCreated", {stream: fakeStream});
+
+        sinon.assert.calledOnce(session.subscribe);
+        sinon.assert.calledWithExactly(session.subscribe,
+          fakeStream, fakeRemoteElement, publisherConfig);
+      });
+
+      it("should dispach a mediaConnected action if both streams are up", function() {
+        driver._publishedLocalStream = true;
+
+        session.trigger("streamCreated", {stream: fakeStream});
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "mediaConnected"));
+      });
+    });
+  });
+});
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -50,19 +50,16 @@ MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3
 # of values.
 ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
 MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_APP_STATIC_INI=1
 MOZ_WEBAPP_RUNTIME=1
 MOZ_MEDIA_NAVIGATOR=1
-if test "$OS_TARGET" = "WINNT" -o "$OS_TARGET" = "Darwin"; then
-  MOZ_FOLD_LIBS=1
-fi
 MOZ_WEBGL_CONFORMANT=1
 # Enable navigator.mozPay
 MOZ_PAY=1
 # Enable activities. These are used for FxOS developers currently.
 MOZ_ACTIVITIES=1
 MOZ_JSDOWNLOADS=1
 MOZ_WEBM_ENCODER=1
 # Enable generational GC on desktop.
--- a/browser/devtools/profiler/panel.js
+++ b/browser/devtools/profiler/panel.js
@@ -30,22 +30,22 @@ exports.ProfilerPanel = ProfilerPanel;
 ProfilerPanel.prototype = {
   /**
    * Open is effectively an asynchronous constructor.
    *
    * @return object
    *         A promise that is resolved when the Profiler completes opening.
    */
   open: Task.async(function*() {
-    let connection = getProfilerConnection(this._toolbox);
-    yield connection.open();
+    this._connection = getProfilerConnection(this._toolbox);
+    yield this._connection.open();
 
     this.panelWin.gToolbox = this._toolbox;
     this.panelWin.gTarget = this.target;
-    this.panelWin.gFront = new ProfilerFront(connection);
+    this.panelWin.gFront = new ProfilerFront(this._connection);
     yield this.panelWin.startupProfiler();
 
     this.isReady = true;
     this.emit("ready");
     return this;
   }),
 
   // DevToolPanel API
@@ -54,12 +54,16 @@ ProfilerPanel.prototype = {
 
   destroy: Task.async(function*() {
     // Make sure this panel is not already destroyed.
     if (this._destroyed) {
       return;
     }
 
     yield this.panelWin.shutdownProfiler();
+
+    // Destroy the connection to ensure packet handlers are removed from client.
+    this._connection.destroy();
+
     this.emit("destroyed");
     this._destroyed = true;
   })
 };
--- a/browser/devtools/profiler/utils/shared.js
+++ b/browser/devtools/profiler/utils/shared.js
@@ -108,37 +108,54 @@ ProfilerConnection.prototype = {
 
     this._connectMiscActors();
     this._connected = true;
 
     Services.obs.notifyObservers(null, "profiler-connection-opened", null);
   }),
 
   /**
+   * Destroys this connection.
+   */
+  destroy: function() {
+    this._disconnectMiscActors();
+    this._connected = false;
+  },
+
+  /**
    * Initializes a connection to miscellaneous actors which are going to be
    * used in tandem with the profiler actor.
    */
   _connectMiscActors: function() {
     // Only initialize the framerate front if the respective actor is available.
     // Older Gecko versions don't have an existing implementation, in which case
     // all the methods we need can be easily mocked.
     if (this._target.form && this._target.form.framerateActor) {
       this._framerate = new FramerateFront(this._target.client, this._target.form);
     } else {
       this._framerate = {
+        destroy: () => {},
         startRecording: () => {},
         stopRecording: () => {},
         cancelRecording: () => {},
         isRecording: () => false,
         getPendingTicks: () => null
       };
     }
   },
 
   /**
+   * Closes the connections to miscellaneous actors.
+   * @see ProfilerConnection.prototype._connectMiscActors
+   */
+  _disconnectMiscActors: function() {
+    this._framerate.destroy();
+  },
+
+  /**
    * Sends the request over the remote debugging protocol to the
    * specified actor.
    *
    * @param string actor
    *        The designated actor. Currently supported: "profiler", "framerate".
    * @param string method
    *        Method to call on the backend.
    * @param any args [optional]
--- a/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.exe.manifest
+++ b/browser/metro/shell/commandexecutehandler/CommandExecuteHandler.exe.manifest
@@ -16,13 +16,14 @@
   <ms_asmv3:security>
     <ms_asmv3:requestedPrivileges>
       <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
     </ms_asmv3:requestedPrivileges>
   </ms_asmv3:security>
 </ms_asmv3:trustInfo>
   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
     <application>
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
       <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
       <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
     </application>
   </compatibility>
 </assembly>
--- a/config/autoconf-js.mk.in
+++ b/config/autoconf-js.mk.in
@@ -1,6 +1,6 @@
 ifndef INCLUDED_AUTOCONF_MK
-INCLUDED_AUTOCONF_MK = 1
+INCLUDED_AUTOCONF_MK = autoconf-js.mk
 include $(DEPTH)/config/emptyvars-js.mk
 @ALLSUBSTS@
 include $(topsrcdir)/config/baseconfig.mk
 endif
--- a/config/autoconf.mk.in
+++ b/config/autoconf.mk.in
@@ -1,6 +1,6 @@
 ifndef INCLUDED_AUTOCONF_MK
-INCLUDED_AUTOCONF_MK = 1
+INCLUDED_AUTOCONF_MK = autoconf.mk
 include $(DEPTH)/config/emptyvars.mk
 @ALLSUBSTS@
 include $(topsrcdir)/config/baseconfig.mk
 endif
--- a/config/expandlibs.py
+++ b/config/expandlibs.py
@@ -108,35 +108,36 @@ class ExpandArgs(list):
         for arg in args:
             self += self._expand(arg)
 
     def _expand(self, arg):
         '''Internal function doing the actual work'''
         (root, ext) = os.path.splitext(arg)
         if ext != conf.LIB_SUFFIX or not os.path.basename(root).startswith(conf.LIB_PREFIX):
             return [relativize(arg)]
-        if len(conf.IMPORT_LIB_SUFFIX):
-            dll = root + conf.IMPORT_LIB_SUFFIX
-        else:
+        if conf.LIB_PREFIX:
             dll = root.replace(conf.LIB_PREFIX, conf.DLL_PREFIX, 1) + conf.DLL_SUFFIX
+        else:
+            dll = root + conf.DLL_SUFFIX
         if os.path.exists(dll):
-            return [relativize(dll)]
-        if os.path.exists(arg):
-            return [relativize(arg)]
+            if conf.IMPORT_LIB_SUFFIX:
+                return [relativize(root + conf.IMPORT_LIB_SUFFIX)]
+            else:
+                return [relativize(dll)]
         return self._expand_desc(arg)
 
     def _expand_desc(self, arg):
         '''Internal function taking care of lib descriptor expansion only'''
         desc = os.path.abspath(arg + conf.LIBS_DESC_SUFFIX)
         if os.path.exists(desc):
             if desc in self._descs:
                 return []
             self._descs.add(desc)
             with open(desc, 'r') as f:
                 desc = LibDescriptor(f.readlines())
             objs = [relativize(o) for o in desc['OBJS']]
             for lib in desc['LIBS']:
                 objs += self._expand(lib)
             return objs
-        return [arg]
+        return [relativize(arg)]
 
 if __name__ == '__main__':
     print " ".join(ExpandArgs(sys.argv[1:]))
--- a/config/external/moz.build
+++ b/config/external/moz.build
@@ -11,16 +11,19 @@ DIRS += [
 ]
 if not CONFIG['MOZ_NATIVE_JPEG']:
     external_dirs += ['media/libjpeg']
 
 if CONFIG['MOZ_UPDATER']:
     if not CONFIG['MOZ_NATIVE_BZ2']:
         external_dirs += ['modules/libbz2']
 
+# There's no "native brotli" yet, but probably in the future...
+external_dirs += ['modules/brotli']
+
 if CONFIG['MOZ_VORBIS']:
     external_dirs += ['media/libvorbis']
 
 if CONFIG['MOZ_TREMOR']:
     external_dirs += ['media/libtremor']
 
 if CONFIG['MOZ_OPUS']:
     external_dirs += ['media/libopus']
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -135,28 +135,32 @@ endif # ENABLE_TESTS
 #
 
 ifndef LIBRARY
 ifdef REAL_LIBRARY
 # Don't build actual static library if a shared library is also built
 ifdef FORCE_SHARED_LIB
 # ... except when we really want one
 ifdef NO_EXPAND_LIBS
-LIBRARY			:= $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
+LIBRARY			:= $(REAL_LIBRARY)
 else
 LIBRARY			:= $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
 endif
 else
 # Only build actual library if it is installed in DIST/lib or SDK
 ifeq (,$(SDK_LIBRARY)$(DIST_INSTALL)$(NO_EXPAND_LIBS))
 LIBRARY			:= $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
 else
+ifdef NO_EXPAND_LIBS
+LIBRARY			:= $(REAL_LIBRARY)
+else
 LIBRARY			:= $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
 endif
 endif
+endif
 endif # REAL_LIBRARY
 endif # LIBRARY
 
 ifndef HOST_LIBRARY
 ifdef HOST_LIBRARY_NAME
 HOST_LIBRARY		:= $(LIB_PREFIX)$(HOST_LIBRARY_NAME).$(LIB_SUFFIX)
 endif
 endif
@@ -563,17 +567,17 @@ endif
 everything::
 	$(MAKE) clean
 	$(MAKE) all
 
 STATIC_LIB_DEP = $(if $(wildcard $(1).$(LIBS_DESC_SUFFIX)),$(1).$(LIBS_DESC_SUFFIX),$(1))
 STATIC_LIBS_DEPS := $(foreach l,$(STATIC_LIBS),$(call STATIC_LIB_DEP,$(l)))
 
 # Dependencies which, if modified, should cause everything to rebuild
-GLOBAL_DEPS += Makefile $(DEPTH)/config/autoconf.mk $(topsrcdir)/config/config.mk
+GLOBAL_DEPS += Makefile $(addprefix $(DEPTH)/config/,$(INCLUDED_AUTOCONF_MK)) $(topsrcdir)/config/config.mk
 
 ##############################################
 ifdef COMPILE_ENVIRONMENT
 OBJ_TARGETS = $(OBJS) $(PROGOBJS) $(HOST_OBJS) $(HOST_PROGOBJS)
 
 compile:: host target
 
 host:: $(HOST_LIBRARY) $(HOST_PROGRAM) $(HOST_SIMPLE_PROGRAMS)
@@ -771,17 +775,18 @@ endif
 
 ifdef DTRACE_PROBE_OBJ
 EXTRA_DEPS += $(DTRACE_PROBE_OBJ)
 OBJS += $(DTRACE_PROBE_OBJ)
 endif
 
 $(filter %.$(LIB_SUFFIX),$(LIBRARY)): $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 	$(REPORT_BUILD)
-	$(RM) $(LIBRARY)
+# Always remove both library and library descriptor
+	$(RM) $(REAL_LIBRARY) $(REAL_LIBRARY).$(LIBS_DESC_SUFFIX)
 	$(EXPAND_AR) $(AR_FLAGS) $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
 
 $(filter-out %.$(LIB_SUFFIX),$(LIBRARY)): $(filter %.$(LIB_SUFFIX),$(LIBRARY)) $(OBJS) $(STATIC_LIBS_DEPS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS)) $(EXTRA_DEPS) $(GLOBAL_DEPS)
 # When we only build a library descriptor, blow out any existing library
 	$(REPORT_BUILD)
 	$(if $(filter %.$(LIB_SUFFIX),$(LIBRARY)),,$(RM) $(REAL_LIBRARY))
 	$(EXPAND_LIBS_GEN) -o $@ $(OBJS) $(STATIC_LIBS) $(filter %.$(LIB_SUFFIX),$(EXTRA_LIBS))
 
--- a/config/tests/unit-expandlibs.py
+++ b/config/tests/unit-expandlibs.py
@@ -181,25 +181,25 @@ class TestExpandArgs(TestExpandInit):
     def test_expand(self):
         '''Test library expansion'''
         # Expanding arguments means libraries with a descriptor are expanded
         # with the descriptor content, and import libraries are used when
         # a library doesn't exist
         args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
         self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 
 
-        # When a library exists at the same time as a descriptor, we just use
-        # the library
+        # When a library exists at the same time as a descriptor, we still use
+        # the descriptor.
         self.touch([self.tmpfile('libx', Lib('x'))])
         args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
-        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + [self.tmpfile('libx', Lib('x'))]) 
+        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
 
         self.touch([self.tmpfile('liby', Lib('y'))])
         args = ExpandArgs(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))])
-        self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
+        self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files)
 
 class TestExpandArgsMore(TestExpandInit):
     def test_makelist(self):
         '''Test grouping object files in lists'''
         # ExpandArgsMore does the same as ExpandArgs
         with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
             self.assertRelEqual(args, ['foo', '-bar'] + self.files + self.liby_files + self.libx_files) 
 
@@ -273,24 +273,25 @@ class TestExpandArgsMore(TestExpandInit)
             lib = os.path.splitext(os.path.basename(args[3]))[0]
             return '%s\n%s\n' % (Obj(lib), Obj(lib + '2'))
         subprocess.check_output = check_output
 
         # ExpandArgsMore does the same as ExpandArgs
         self.touch([self.tmpfile('liby', Lib('y'))])
         for iteration in (1, 2):
             with ExpandArgsMore(['foo', '-bar'] + self.arg_files + [self.tmpfile('liby', Lib('y'))]) as args:
-                self.assertRelEqual(args, ['foo', '-bar'] + self.files + [self.tmpfile('liby', Lib('y'))])
+                files = self.files + self.liby_files + self.libx_files
+
+                self.assertRelEqual(args, ['foo', '-bar'] + files)
 
                 extracted = {}
                 # ExpandArgsMore also has an extra method extracting static libraries
                 # when possible
                 args.extract()
 
-                files = self.files + self.liby_files + self.libx_files
                 # With AR_EXTRACT, it uses the descriptors when there are, and
                 # actually
                 # extracts the remaining libraries
                 extracted_args = []
                 for f in files:
                     if f.endswith(config.LIB_SUFFIX):
                         base = os.path.splitext(os.path.basename(f))[0]
                         # On the first iteration, we test the behavior of
--- a/configure.in
+++ b/configure.in
@@ -3260,16 +3260,22 @@ dnl Using the custom linker on ARMv6 req
 if test -n "$MOZ_LINKER"; then
   if test "$CPU_ARCH" = arm; then
     dnl When building for < ARMv7, we need to ensure 16k alignment of ELF segments
     if test -n "$ARM_ARCH" && test "$ARM_ARCH" -lt 7; then
       LDFLAGS="$LDFLAGS -Wl,-z,max-page-size=0x4000 -Wl,-z,common-page-size=0x4000"
       _SUBDIR_LDFLAGS="$_SUBDIR_LDFLAGS -Wl,-z,max-page-size=0x4000 -Wl,-z,common-page-size=0x4000"
     fi
   fi
+
+dnl gold emits wrong sysv-style elf hash tables when building both sysv and
+dnl style tables. https://sourceware.org/bugzilla/show_bug.cgi?id=13597
+dnl Since the linker only understands the sysv ones, no need to build the
+dnl gnu style tables anyways.
+  LDFLAGS="$LDFLAGS -Wl,--hash-style=sysv"
 fi
 
 dnl The custom linker doesn't support text relocations, but NDK >= r6b
 dnl creates some (http://code.google.com/p/android/issues/detail?id=23203)
 dnl We however want to avoid these text relocations, and this can be done
 dnl by making gcc not link crtbegin and crtend. In the broken NDKs, crtend
 dnl doesn't contain anything at all, beside placeholders for some sections,
 dnl and crtbegin only contains a finalizer function that calls
@@ -4089,16 +4095,25 @@ AC_SUBST(MOZ_BUILD_APP)
 AC_SUBST(MOZ_PHOENIX)
 AC_SUBST(MOZ_XULRUNNER)
 AC_SUBST(MOZ_B2G)
 AC_SUBST(MOZ_MULET)
 AC_SUBST(MOZ_B2G_VERSION)
 
 AC_DEFINE_UNQUOTED(MOZ_BUILD_APP,$MOZ_BUILD_APP)
 
+case "$OS_TARGET" in
+WINNT|Darwin|Android)
+  MOZ_FOLD_LIBS=1
+  ;;
+*)
+  MOZ_FOLD_LIBS=
+  ;;
+esac
+
 dnl ========================================================
 dnl Check Android SDK version depending on mobile target.
 dnl ========================================================
 
 if test -z "$gonkdir" ; then
     # Minimum Android SDK API Level we require.
     case "$MOZ_BUILD_APP" in
     mobile/android)
@@ -6984,23 +6999,22 @@ fi
 if test "${OS_TARGET}" = "Android"; then
   dnl On Android, we use WRAP_LDFLAGS to link everything to mozglue
   :
 elif test "${OS_TARGET}" = "WINNT" -o "${OS_TARGET}" = "Darwin"; then
   dnl On Windows and OSX, we want to link all our binaries against mozglue
   MOZ_GLUE_LDFLAGS='$(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib)'
 else
   dnl On other Unix systems, we only want to link executables against mozglue
-  MOZ_GLUE_PROGRAM_LDFLAGS='$(MKSHLIB_FORCE_ALL) $(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib)'
+  MOZ_GLUE_PROGRAM_LDFLAGS='$(call EXPAND_LIBNAME_PATH,mozglue,$(LIBXUL_DIST)/lib)'
   dnl On other Unix systems, where mozglue is a static library, jemalloc is
   dnl separated for the SDK, so we need to add it here.
   if test "$MOZ_MEMORY" = 1 -o \( "$LIBXUL_SDK" -a -f "$LIBXUL_SDK/lib/${LIB_PREFIX}memory.${LIB_SUFFIX}" \); then
     MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS "'$(call EXPAND_LIBNAME_PATH,memory,$(LIBXUL_DIST)/lib)'
   fi
-  MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS "'$(MKSHLIB_UNFORCE_ALL)'
   if test -n "$GNU_CC"; then
     dnl And we need mozglue symbols to be exported.
     MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS -rdynamic"
   fi
   if test "$MOZ_LINKER" = 1; then
     MOZ_GLUE_PROGRAM_LDFLAGS="$MOZ_GLUE_PROGRAM_LDFLAGS $MOZ_ZLIB_LIBS"
   fi
 fi
@@ -7154,18 +7168,16 @@ else
     if grep -q '__imp__\{0,1\}free' crtdll.obj; then
       MOZ_GLUE_LDFLAGS='-LIBPATH:$(DIST)/lib -NODEFAULTLIB:msvcrt -NODEFAULTLIB:msvcprt -DEFAULTLIB:mozcrt'
       dnl Also pass this to NSPR/NSS
       DLLFLAGS="$DLLFLAGS $MOZ_GLUE_LDFLAGS"
     else
       DLLFLAGS="$DLLFLAGS -LIBPATH:\$(DIST)/lib -DEFAULTLIB:mozglue"
     fi
     rm crtdll.obj
-
-    export DLLFLAGS
     ;;
   *)
     AC_MSG_ERROR([--enable-jemalloc not supported on ${target}])
     ;;
   esac
 fi # MOZ_MEMORY
 AC_SUBST(MOZ_MEMORY)
 AC_SUBST(MOZ_JEMALLOC3)
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -1270,16 +1270,31 @@ private:
 
   nsIScrollableFrame* GetScrollFrame(nsIFrame **aStyledFrame = nullptr,
                                      bool aFlushLayout = true);
 
   // Data members
   EventStates mState;
 };
 
+class RemoveFromBindingManagerRunnable : public nsRunnable
+{
+public:
+  RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
+                                   nsIContent* aContent,
+                                   nsIDocument* aDoc);
+
+  NS_IMETHOD Run();
+private:
+  virtual ~RemoveFromBindingManagerRunnable();
+  nsRefPtr<nsBindingManager> mManager;
+  nsRefPtr<nsIContent> mContent;
+  nsCOMPtr<nsIDocument> mDoc;
+};
+
 class DestinationInsertionPointList : public nsINodeList
 {
 public:
   explicit DestinationInsertionPointList(Element* aElement);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(DestinationInsertionPointList)
 
--- a/content/base/src/Element.cpp
+++ b/content/base/src/Element.cpp
@@ -425,17 +425,17 @@ Element::WrapObject(JSContext *aCx)
     }
   }
 
   nsIDocument* doc;
   if (HasFlag(NODE_FORCE_XBL_BINDINGS)) {
     doc = OwnerDoc();
   }
   else {
-    doc = GetCurrentDoc();
+    doc = GetComposedDoc();
   }
 
   if (!doc) {
     // There's no baseclass that cares about this call so we just
     // return here.
     return obj;
   }
 
@@ -588,17 +588,17 @@ void
 Element::ScrollIntoView()
 {
   ScrollIntoView(true, ScrollOptions());
 }
 
 void
 Element::ScrollIntoView(bool aTop, const ScrollOptions &aOptions)
 {
-  nsIDocument *document = GetCurrentDoc();
+  nsIDocument *document = GetComposedDoc();
   if (!document) {
     return;
   }
 
   // Get the presentation shell
   nsCOMPtr<nsIPresShell> presShell = document->GetShell();
   if (!presShell) {
     return;
@@ -762,17 +762,17 @@ Element::GetClientRects()
 void
 Element::AddToIdTable(nsIAtom* aId)
 {
   NS_ASSERTION(HasID(), "Node doesn't have an ID?");
   if (IsInShadowTree()) {
     ShadowRoot* containingShadow = GetContainingShadow();
     containingShadow->AddToIdTable(this, aId);
   } else {
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetUncomposedDoc();
     if (doc && (!IsInAnonymousSubtree() || doc->IsXUL())) {
       doc->AddToIdTable(this, aId);
     }
   }
 }
 
 void
 Element::RemoveFromIdTable()
@@ -785,17 +785,17 @@ Element::RemoveFromIdTable()
   if (IsInShadowTree()) {
     ShadowRoot* containingShadow = GetContainingShadow();
     // Check for containingShadow because it may have
     // been deleted during unlinking.
     if (containingShadow) {
       containingShadow->RemoveFromIdTable(this, id);
     }
   } else {
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetUncomposedDoc();
     if (doc && (!IsInAnonymousSubtree() || doc->IsXUL())) {
       doc->RemoveFromIdTable(this, id);
     }
   }
 }
 
 already_AddRefed<ShadowRoot>
 Element::CreateShadowRoot(ErrorResult& aError)
@@ -1192,19 +1192,19 @@ Element::GetElementsByClassName(const ns
 nsresult
 Element::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                     nsIContent* aBindingParent,
                     bool aCompileEventHandlers)
 {
   NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
   NS_PRECONDITION((NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc()),
                   "Must have the same owner document");
-  NS_PRECONDITION(!aParent || aDocument == aParent->GetCurrentDoc(),
+  NS_PRECONDITION(!aParent || aDocument == aParent->GetUncomposedDoc(),
                   "aDocument must be current doc of aParent");
-  NS_PRECONDITION(!GetCurrentDoc(), "Already have a document.  Unbind first!");
+  NS_PRECONDITION(!GetUncomposedDoc(), "Already have a document.  Unbind first!");
   // Note that as we recurse into the kids, they'll have a non-null parent.  So
   // only assert if our parent is _changing_ while we have a parent.
   NS_PRECONDITION(!GetParent() || aParent == GetParent(),
                   "Already have a parent.  Unbind first!");
   NS_PRECONDITION(!GetBindingParent() ||
                   aBindingParent == GetBindingParent() ||
                   (!aBindingParent && aParent &&
                    aParent->GetBindingParent() == GetBindingParent()),
@@ -1401,66 +1401,63 @@ Element::BindToTree(nsIDocument* aDocume
                              aCompileEventHandlers);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
   // XXXbz script execution during binding can trigger some of these
   // postcondition asserts....  But we do want that, since things will
   // generally be quite broken when that happens.
-  NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document");
+  NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
   return NS_OK;
 }
 
-class RemoveFromBindingManagerRunnable : public nsRunnable {
-public:
-  RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
-                                   Element* aElement,
-                                   nsIDocument* aDoc):
-    mManager(aManager), mElement(aElement), mDoc(aDoc)
-  {}
-
-  NS_IMETHOD Run()
-  {
-    // It may be the case that the element was removed from the
-    // DOM, causing this runnable to be created, then inserted back
-    // into the document before the this runnable had a chance to
-    // tear down the binding. Only tear down the binding if the element
-    // is still no longer in the DOM. nsXBLService::LoadBinding tears
-    // down the old binding if the element is inserted back into the
-    // DOM and loads a different binding.
-    if (!mElement->IsInDoc()) {
-      mManager->RemovedFromDocumentInternal(mElement, mDoc);
-    }
-
-    return NS_OK;
+RemoveFromBindingManagerRunnable::RemoveFromBindingManagerRunnable(nsBindingManager* aManager,
+                                                                   nsIContent* aContent,
+                                                                   nsIDocument* aDoc):
+  mManager(aManager), mContent(aContent), mDoc(aDoc)
+{}
+
+RemoveFromBindingManagerRunnable::~RemoveFromBindingManagerRunnable() {}
+
+NS_IMETHODIMP
+RemoveFromBindingManagerRunnable::Run()
+{
+  // It may be the case that the element was removed from the
+  // DOM, causing this runnable to be created, then inserted back
+  // into the document before the this runnable had a chance to
+  // tear down the binding. Only tear down the binding if the element
+  // is still no longer in the DOM. nsXBLService::LoadBinding tears
+  // down the old binding if the element is inserted back into the
+  // DOM and loads a different binding.
+  if (!mContent->IsInComposedDoc()) {
+    mManager->RemovedFromDocumentInternal(mContent, mDoc);
   }
 
-private:
-  nsRefPtr<nsBindingManager> mManager;
-  nsRefPtr<Element> mElement;
-  nsCOMPtr<nsIDocument> mDoc;
-};
+  return NS_OK;
+}
+
 
 void
 Element::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  NS_PRECONDITION(aDeep || (!GetCurrentDoc() && !GetBindingParent()),
+  NS_PRECONDITION(aDeep || (!GetUncomposedDoc() && !GetBindingParent()),
                   "Shallow unbind won't clear document and binding parent on "
                   "kids!");
 
   RemoveFromIdTable();
 
   // Make sure to unbind this node before doing the kids
-  nsIDocument *document =
-    HasFlag(NODE_FORCE_XBL_BINDINGS) ? OwnerDoc() : GetCurrentDoc();
+  nsIDocument* document =
+    HasFlag(NODE_FORCE_XBL_BINDINGS) || IsInShadowTree() ?
+      OwnerDoc() : GetUncomposedDoc();
 
   if (aNullParent) {
     if (IsFullScreenAncestor()) {
       // The element being removed is an ancestor of the full-screen element,
       // exit full-screen state.
       nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                       NS_LITERAL_CSTRING("DOM"), OwnerDoc(),
                                       nsContentUtils::eDOM_PROPERTIES,
@@ -1487,17 +1484,19 @@ Element::UnbindFromTree(bool aDeep, bool
 
     // Begin keeping track of our subtree root.
     SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
   }
 
   if (document) {
     // Notify XBL- & nsIAnonymousContentCreator-generated
     // anonymous content that the document is changing.
-    if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+    // Unlike XBL, bindings for web components shadow DOM
+    // do not get uninstalled.
+    if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR) && !GetShadowRoot()) {
       nsContentUtils::AddScriptRunner(
         new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
                                              document));
     }
 
     document->ClearBoxObjectFor(this);
 
     if (GetCustomElementData()) {
@@ -1597,17 +1596,17 @@ nsresult
 Element::SetSMILOverrideStyleRule(css::StyleRule* aStyleRule,
                                            bool aNotify)
 {
   Element::nsDOMSlots *slots = DOMSlots();
 
   slots->mSMILOverrideStyleRule = aStyleRule;
 
   if (aNotify) {
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetComposedDoc();
     // Only need to request a restyle if we're in a document.  (We might not
     // be in a document, if we're clearing animation effects on a target node
     // that's been detached since the previous animation sample.)
     if (doc) {
       nsCOMPtr<nsIPresShell> shell = doc->GetShell();
       if (shell) {
         shell->RestyleForAnimation(this,
           eRestyle_StyleAttribute | eRestyle_ChangeAnimationPhase);
@@ -2015,17 +2014,17 @@ Element::SetAttrAndNotify(int32_t aNames
                           nsAttrValue& aParsedValue,
                           uint8_t aModType,
                           bool aFireMutation,
                           bool aNotify,
                           bool aCallAfterSetAttr)
 {
   nsresult rv;
 
-  nsIDocument* document = GetCurrentDoc();
+  nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   nsMutationGuard::DidMutate();
 
   // Copy aParsedValue for later use since it will be lost when we call
   // SetAndTakeMappedAttr below
   nsAttrValue aValueForAfterSetAttr;
   if (aCallAfterSetAttr) {
@@ -2228,17 +2227,17 @@ Element::UnsetAttr(int32_t aNameSpaceID,
   int32_t index = mAttrsAndChildren.IndexOfAttr(aName, aNameSpaceID);
   if (index < 0) {
     return NS_OK;
   }
 
   nsresult rv = BeforeSetAttr(aNameSpaceID, aName, nullptr, aNotify);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsIDocument *document = GetCurrentDoc();
+  nsIDocument *document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   if (aNotify) {
     nsNodeUtils::AttributeWillChange(this, aNameSpaceID, aName,
                                      nsIDOMMutationEvent::REMOVAL);
   }
 
   bool hasMutationListeners = aNotify &&
@@ -2617,17 +2616,17 @@ Element::PostHandleEventForLinks(EventCh
 
   switch (aVisitor.mEvent->message) {
   case NS_MOUSE_BUTTON_DOWN:
     {
       if (aVisitor.mEvent->AsMouseEvent()->button ==
             WidgetMouseEvent::eLeftButton) {
         // don't make the link grab the focus if there is no link handler
         nsILinkHandler *handler = aVisitor.mPresContext->GetLinkHandler();
-        nsIDocument *document = GetCurrentDoc();
+        nsIDocument *document = GetComposedDoc();
         if (handler && document) {
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm) {
             aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
             nsCOMPtr<nsIDOMElement> elem = do_QueryInterface(this);
             fm->SetFocus(elem, nsIFocusManager::FLAG_BYMOUSE |
                                nsIFocusManager::FLAG_NOSCROLL);
           }
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -198,17 +198,17 @@ nsIContent::GetDesiredIMEState()
   // textarea) must override this method, so, we don't need to worry about
   // that here.
   nsIContent *editableAncestor = GetEditingHost();
 
   // This is in another editable content, use the result of it.
   if (editableAncestor && editableAncestor != this) {
     return editableAncestor->GetDesiredIMEState();
   }
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (!doc) {
     return IMEState(IMEState::DISABLED);
   }
   nsIPresShell* ps = doc->GetShell();
   if (!ps) {
     return IMEState(IMEState::DISABLED);
   }
   nsPresContext* pc = ps->GetPresContext();
@@ -233,20 +233,20 @@ nsIContent::HasIndependentSelection()
 }
 
 dom::Element*
 nsIContent::GetEditingHost()
 {
   // If this isn't editable, return nullptr.
   NS_ENSURE_TRUE(IsEditableInternal(), nullptr);
 
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   NS_ENSURE_TRUE(doc, nullptr);
   // If this is in designMode, we should return <body>
-  if (doc->HasFlag(NODE_IS_EDITABLE)) {
+  if (doc->HasFlag(NODE_IS_EDITABLE) && !IsInShadowTree()) {
     return doc->GetBodyElement();
   }
 
   nsIContent* content = this;
   for (dom::Element* parent = GetParentElement();
        parent && parent->HasFlag(NODE_IS_EDITABLE);
        parent = content->GetParentElement()) {
     content = parent;
@@ -1496,17 +1496,19 @@ FragmentOrElement::RemoveBlackMarkedNode
 bool
 FragmentOrElement::CanSkipInCC(nsINode* aNode)
 {
   // Don't try to optimize anything during shutdown.
   if (nsCCUncollectableMarker::sGeneration == 0) {
     return false;
   }
 
-  nsIDocument* currentDoc = aNode->GetCurrentDoc();
+  //XXXsmaug Need to figure out in which cases Shadow DOM can be optimized out
+  //         from the CC graph.
+  nsIDocument* currentDoc = aNode->GetUncomposedDoc();
   if (currentDoc &&
       nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration())) {
     return !NeedsScriptTraverse(aNode);
   }
 
   // Bail out early if aNode is somewhere in anonymous content,
   // or otherwise unusual.
   if (aNode->UnoptimizableCCNode()) {
@@ -1673,17 +1675,17 @@ bool
 FragmentOrElement::CanSkip(nsINode* aNode, bool aRemovingAllowed)
 {
   // Don't try to optimize anything during shutdown.
   if (nsCCUncollectableMarker::sGeneration == 0) {
     return false;
   }
 
   bool unoptimizable = aNode->UnoptimizableCCNode();
-  nsIDocument* currentDoc = aNode->GetCurrentDoc();
+  nsIDocument* currentDoc = aNode->GetUncomposedDoc();
   if (currentDoc &&
       nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration()) &&
       (!unoptimizable || NodeHasActiveFrame(currentDoc, aNode) ||
        OwnedByBindingManager(currentDoc, aNode))) {
     MarkNodeChildren(aNode);
     return true;
   }
 
@@ -1802,17 +1804,17 @@ bool
 FragmentOrElement::CanSkipThis(nsINode* aNode)
 {
   if (nsCCUncollectableMarker::sGeneration == 0) {
     return false;
   }
   if (aNode->IsBlack()) {
     return true;
   }
-  nsIDocument* c = aNode->GetCurrentDoc();
+  nsIDocument* c = aNode->GetUncomposedDoc();
   return 
     ((c && nsCCUncollectableMarker::InGeneration(c->GetMarkedCCGeneration())) ||
      aNode->InCCBlackTree()) && !NeedsScriptTraverse(aNode);
 }
 
 void
 FragmentOrElement::InitCCCallbacks()
 {
--- a/content/base/src/Link.cpp
+++ b/content/base/src/Link.cpp
@@ -72,34 +72,34 @@ Link::LinkState() const
   // We are a constant method, but we are just lazily doing things and have to
   // track that state.  Cast away that constness!
   Link *self = const_cast<Link *>(this);
 
   Element *element = self->mElement;
 
   // If we have not yet registered for notifications and need to,
   // due to our href changing, register now!
-  if (!mRegistered && mNeedsRegistration && element->IsInDoc()) {
+  if (!mRegistered && mNeedsRegistration && element->IsInComposedDoc()) {
     // Only try and register once.
     self->mNeedsRegistration = false;
 
     nsCOMPtr<nsIURI> hrefURI(GetURI());
 
     // Assume that we are not visited until we are told otherwise.
     self->mLinkState = eLinkState_Unvisited;
 
     // Make sure the href attribute has a valid link (bug 23209).
     // If we have a good href, register with History if available.
     if (mHistory && hrefURI) {
       nsresult rv = mHistory->RegisterVisitedCallback(hrefURI, self);
       if (NS_SUCCEEDED(rv)) {
         self->mRegistered = true;
 
         // And make sure we are in the document's link map.
-        element->GetCurrentDoc()->AddStyleRelevantLink(self);
+        element->GetComposedDoc()->AddStyleRelevantLink(self);
       }
     }
   }
 
   // Otherwise, return our known state.
   if (mLinkState == eLinkState_Visited) {
     return NS_EVENT_STATE_VISITED;
   }
@@ -464,17 +464,17 @@ Link::ResetLinkState(bool aNotify, bool 
   } else {
     defaultState = eLinkState_NotLink;
   }
 
   // If !mNeedsRegstration, then either we've never registered, or we're
   // currently registered; in either case, we should remove ourself
   // from the doc and the history.
   if (!mNeedsRegistration && mLinkState != eLinkState_NotLink) {
-    nsIDocument *doc = mElement->GetCurrentDoc();
+    nsIDocument *doc = mElement->GetComposedDoc();
     if (doc && (mRegistered || mLinkState == eLinkState_Visited)) {
       // Tell the document to forget about this link if we've registered
       // with it before.
       doc->ForgetLink(this);
     }
 
     UnregisterFromHistory();
   }
--- a/content/base/src/nsCCUncollectableMarker.cpp
+++ b/content/base/src/nsCCUncollectableMarker.cpp
@@ -73,17 +73,17 @@ nsCCUncollectableMarker::Init()
   sInited = true;
 
   return NS_OK;
 }
 
 static void
 MarkUserData(void* aNode, nsIAtom* aKey, void* aValue, void* aData)
 {
-  nsIDocument* d = static_cast<nsINode*>(aNode)->GetCurrentDoc();
+  nsIDocument* d = static_cast<nsINode*>(aNode)->GetUncomposedDoc();
   if (d && nsCCUncollectableMarker::InGeneration(d->GetMarkedCCGeneration())) {
     Element::MarkUserData(aNode, aKey, aValue, aData);
   }
 }
 
 static void
 MarkChildMessageManagers(nsIMessageBroadcaster* aMM)
 {
--- a/content/base/src/nsContentList.cpp
+++ b/content/base/src/nsContentList.cpp
@@ -426,20 +426,20 @@ nsContentList::nsContentList(nsINode* aR
   }
   else {
     mMatchAll = false;
   }
   mRootNode->AddMutationObserver(this);
 
   // We only need to flush if we're in an non-HTML document, since the
   // HTML5 parser doesn't need flushing.  Further, if we're not in a
-  // document at all right now (in the GetCurrentDoc() sense), we're
+  // document at all right now (in the GetUncomposedDoc() sense), we're
   // not parser-created and don't need to be flushing stuff under us
   // to get our kids right.
-  nsIDocument* doc = mRootNode->GetCurrentDoc();
+  nsIDocument* doc = mRootNode->GetUncomposedDoc();
   mFlushesNeeded = doc && !doc->IsHTML();
 }
 
 nsContentList::nsContentList(nsINode* aRootNode,
                              nsContentListMatchFunc aFunc,
                              nsContentListDestroyFunc aDestroyFunc,
                              void* aData,
                              bool aDeep,
@@ -459,20 +459,20 @@ nsContentList::nsContentList(nsINode* aR
     mDeep(aDeep),
     mFuncMayDependOnAttr(aFuncMayDependOnAttr)
 {
   NS_ASSERTION(mRootNode, "Must have root");
   mRootNode->AddMutationObserver(this);
 
   // We only need to flush if we're in an non-HTML document, since the
   // HTML5 parser doesn't need flushing.  Further, if we're not in a
-  // document at all right now (in the GetCurrentDoc() sense), we're
+  // document at all right now (in the GetUncomposedDoc() sense), we're
   // not parser-created and don't need to be flushing stuff under us
   // to get our kids right.
-  nsIDocument* doc = mRootNode->GetCurrentDoc();
+  nsIDocument* doc = mRootNode->GetUncomposedDoc();
   mFlushesNeeded = doc && !doc->IsHTML();
 }
 
 nsContentList::~nsContentList()
 {
   RemoveFromHashtable();
   if (mRootNode) {
     mRootNode->RemoveMutationObserver(this);
@@ -502,17 +502,17 @@ nsContentList::Length(bool aDoFlush)
   return mElements.Length();
 }
 
 nsIContent *
 nsContentList::Item(uint32_t aIndex, bool aDoFlush)
 {
   if (mRootNode && aDoFlush && mFlushesNeeded) {
     // XXX sXBL/XBL2 issue
-    nsIDocument* doc = mRootNode->GetCurrentDoc();
+    nsIDocument* doc = mRootNode->GetUncomposedDoc();
     if (doc) {
       // Flush pending content changes Bug 4891.
       doc->FlushPendingNotifications(Flush_ContentAndNotify);
     }
   }
 
   if (mState != LIST_UP_TO_DATE)
     PopulateSelf(std::min(aIndex, UINT32_MAX - 1) + 1);
@@ -999,17 +999,17 @@ nsContentList::RemoveFromHashtable()
   }
 }
 
 void
 nsContentList::BringSelfUpToDate(bool aDoFlush)
 {
   if (mRootNode && aDoFlush && mFlushesNeeded) {
     // XXX sXBL/XBL2 issue
-    nsIDocument* doc = mRootNode->GetCurrentDoc();
+    nsIDocument* doc = mRootNode->GetUncomposedDoc();
     if (doc) {
       // Flush pending content changes Bug 4891.
       doc->FlushPendingNotifications(Flush_ContentAndNotify);
     }
   }
 
   if (mState != LIST_UP_TO_DATE)
     PopulateSelf(uint32_t(-1));
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -1178,17 +1178,17 @@ nsContentSink::StartLayout(bool aIgnoreP
   // frameset document, disable the scroll bars on the views.
 
   mDocument->SetScrollToRef(mDocument->GetDocumentURI());
 }
 
 void
 nsContentSink::NotifyAppend(nsIContent* aContainer, uint32_t aStartIndex)
 {
-  if (aContainer->GetCurrentDoc() != mDocument) {
+  if (aContainer->GetUncomposedDoc() != mDocument) {
     // aContainer is not actually in our document anymore.... Just bail out of
     // here; notifying on our document for this append would be wrong.
     return;
   }
 
   mInNotification++;
   
   {
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -2448,27 +2448,28 @@ nsContentUtils::GenerateStateKey(nsICont
   if (aContent->IsInAnonymousSubtree()) {
     return NS_OK;
   }
 
   if (IsAutocompleteOff(aContent)) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIHTMLDocument> htmlDocument(do_QueryInterface(aContent->GetCurrentDoc()));
+  nsCOMPtr<nsIHTMLDocument> htmlDocument =
+    do_QueryInterface(aContent->GetUncomposedDoc());
 
   KeyAppendInt(partID, aKey);  // first append a partID
   bool generatedUniqueKey = false;
 
   if (htmlDocument) {
     // Flush our content model so it'll be up to date
     // If this becomes unnecessary and the following line is removed,
     // please also remove the corresponding flush operation from
     // nsHtml5TreeBuilderCppSupplement.h. (Look for "See bug 497861." there.)
-    aContent->GetCurrentDoc()->FlushPendingNotifications(Flush_Content);
+    aContent->GetUncomposedDoc()->FlushPendingNotifications(Flush_Content);
 
     nsContentList *htmlForms = htmlDocument->GetForms();
     nsContentList *htmlFormControls = htmlDocument->GetFormControls();
 
     NS_ENSURE_TRUE(htmlForms && htmlFormControls, NS_ERROR_OUT_OF_MEMORY);
 
     // If we have a form control and can calculate form information, use that
     // as the key - it is more reliable than just recording position in the
@@ -2842,17 +2843,17 @@ nsContentUtils::SplitExpatName(const cha
   }
   *aLocalName = NS_NewAtom(Substring(nameStart, nameEnd)).take();
 }
 
 // static
 nsPresContext*
 nsContentUtils::GetContextForContent(const nsIContent* aContent)
 {
-  nsIDocument* doc = aContent->GetCurrentDoc();
+  nsIDocument* doc = aContent->GetComposedDoc();
   if (doc) {
     nsIPresShell *presShell = doc->GetShell();
     if (presShell) {
       return presShell->GetPresContext();
     }
   }
   return nullptr;
 }
@@ -4399,17 +4400,17 @@ nsContentUtils::SetNodeTextContent(nsICo
         }
         nsContentUtils::MaybeFireNodeRemoved(child, aContent, doc);
       }
     }
   }
 
   // Might as well stick a batch around this since we're performing several
   // mutations.
-  mozAutoDocUpdate updateBatch(aContent->GetCurrentDoc(),
+  mozAutoDocUpdate updateBatch(aContent->GetComposedDoc(),
     UPDATE_CONTENT_MODEL, true);
   nsAutoMutationBatch mb;
 
   uint32_t childCount = aContent->GetChildCount();
 
   if (aTryReuse && !aValue.IsEmpty()) {
     uint32_t removeIndex = 0;
 
@@ -6099,17 +6100,19 @@ nsContentUtils::IsFocusedContent(const n
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
 
   return fm && fm->GetFocusedContent() == aContent;
 }
 
 bool
 nsContentUtils::IsSubDocumentTabbable(nsIContent* aContent)
 {
-  nsIDocument* doc = aContent->GetCurrentDoc();
+  //XXXsmaug Shadow DOM spec issue!
+  //         We may need to change this to GetComposedDoc().
+  nsIDocument* doc = aContent->GetUncomposedDoc();
   if (!doc) {
     return false;
   }
 
   // If the subdocument lives in another process, the frame is
   // tabbable.
   if (EventStateManager::IsRemoteTarget(aContent)) {
     return true;
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -6051,17 +6051,18 @@ nsDocument::RegisterElement(JSContext* a
       JS::RootedObject wrapper(aCx);
       if ((wrapper = cache->GetWrapper())) {
         if (!JS_SetPrototype(aCx, wrapper, protoObject)) {
           continue;
         }
       }
 
       EnqueueLifecycleCallback(nsIDocument::eCreated, elem, nullptr, definition);
-      if (elem->GetCurrentDoc()) {
+      //XXXsmaug It is unclear if we should use GetComposedDoc() here.
+      if (elem->GetUncomposedDoc()) {
         // Normally callbacks can not be enqueued until the created
         // callback has been invoked, however, the attached callback
         // in element upgrade is an exception so pretend the created
         // callback has been invoked.
         elem->GetCustomElementData()->mCreatedCallbackInvoked = true;
 
         EnqueueLifecycleCallback(nsIDocument::eAttached, elem, nullptr, definition);
       }
@@ -11522,17 +11523,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   NS_IMETHOD Run()
   {
     nsCOMPtr<Element> e = do_QueryReferent(mElement);
     nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
     if (!e || !d || gPendingPointerLockRequest != this ||
-        e->GetCurrentDoc() != d) {
+        e->GetUncomposedDoc() != d) {
       Handled();
       DispatchPointerLockError(d);
       return NS_OK;
     }
 
     // We're about to enter fullscreen mode.
     nsDocument* doc = static_cast<nsDocument*>(d.get());
     if (doc->mAsyncFullscreenPending ||
@@ -11637,17 +11638,17 @@ NS_IMETHODIMP
 nsPointerLockPermissionRequest::Allow(JS::HandleValue aChoices)
 {
   MOZ_ASSERT(aChoices.isUndefined());
 
   nsCOMPtr<Element> e = do_QueryReferent(mElement);
   nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
   nsDocument* d = static_cast<nsDocument*>(doc.get());
   if (!e || !d || gPendingPointerLockRequest != this ||
-      e->GetCurrentDoc() != d ||
+      e->GetUncomposedDoc() != d ||
       (!mUserInputOrChromeCaller && !d->mIsApprovedForFullscreen)) {
     Handled();
     DispatchPointerLockError(d);
     return NS_OK;
   }
 
   // Mark handled here so that we don't need to call it everywhere below.
   Handled();
@@ -11704,17 +11705,17 @@ nsDocument::Observe(nsISupports *aSubjec
       // Run() method gets called again.
       nsCOMPtr<Element> el =
         do_QueryReferent(gPendingPointerLockRequest->mElement);
       nsCOMPtr<nsIDocument> doc =
         do_QueryReferent(gPendingPointerLockRequest->mDocument);
       bool userInputOrChromeCaller =
         gPendingPointerLockRequest->mUserInputOrChromeCaller;
       gPendingPointerLockRequest->Handled();
-      if (doc == this && el && el->GetCurrentDoc() == doc) {
+      if (doc == this && el && el->GetUncomposedDoc() == doc) {
         nsPointerLockPermissionRequest* clone =
           new nsPointerLockPermissionRequest(el, userInputOrChromeCaller);
         gPendingPointerLockRequest = clone;
         nsCOMPtr<nsIRunnable> r = gPendingPointerLockRequest.get();
         NS_DispatchToMainThread(r);
       }
     }
   } else if (strcmp("app-theme-changed", aTopic) == 0) {
--- a/content/base/src/nsDocumentEncoder.cpp
+++ b/content/base/src/nsDocumentEncoder.cpp
@@ -327,17 +327,17 @@ IsInvisibleBreak(nsINode *aNode) {
   // if a BR node is visible without using the editor.
   Element* elt = aNode->AsElement();
   if (!elt->IsHTML(nsGkAtoms::br) ||
       !aNode->IsEditable()) {
     return false;
   }
 
   // Grab the editor associated with the document
-  nsIDocument *doc = aNode->GetCurrentDoc();
+  nsIDocument *doc = aNode->GetComposedDoc();
   if (doc) {
     nsPIDOMWindow *window = doc->GetWindow();
     if (window) {
       nsIDocShell *docShell = window->GetDocShell();
       if (docShell) {
         nsCOMPtr<nsIEditor> editor;
         docShell->GetEditor(getter_AddRefs(editor));
         nsCOMPtr<nsIHTMLEditor> htmlEditor = do_QueryInterface(editor);
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -187,17 +187,17 @@ nsFrameLoader::~nsFrameLoader()
 }
 
 nsFrameLoader*
 nsFrameLoader::Create(Element* aOwner, bool aNetworkCreated)
 {
   NS_ENSURE_TRUE(aOwner, nullptr);
   nsIDocument* doc = aOwner->OwnerDoc();
   NS_ENSURE_TRUE(!doc->IsResourceDoc() &&
-                 ((!doc->IsLoadedAsData() && aOwner->GetCurrentDoc()) ||
+                 ((!doc->IsLoadedAsData() && aOwner->GetUncomposedDoc()) ||
                    doc->IsStaticDocument()),
                  nullptr);
 
   return new nsFrameLoader(aOwner, aNetworkCreated);
 }
 
 NS_IMETHODIMP
 nsFrameLoader::LoadFrame()
@@ -868,22 +868,22 @@ nsFrameLoader::ShowRemoteFrame(const nsI
     }
   }
 
   // FIXME/bug 589337: Show()/Hide() is pretty expensive for
   // cross-process layers; need to figure out what behavior we really
   // want here.  For now, hack.
   if (!mRemoteBrowserShown) {
     if (!mOwnerContent ||
-        !mOwnerContent->GetCurrentDoc()) {
+        !mOwnerContent->GetUncomposedDoc()) {
       return false;
     }
 
     nsRefPtr<layers::LayerManager> layerManager =
-      nsContentUtils::LayerManagerForDocument(mOwnerContent->GetCurrentDoc());
+      nsContentUtils::LayerManagerForDocument(mOwnerContent->GetUncomposedDoc());
     if (!layerManager) {
       // This is just not going to work.
       return false;
     }
 
     mRemoteBrowser->Show(size);
     mRemoteBrowserShown = true;
 
@@ -1068,18 +1068,18 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
   }
 
   nsCOMPtr<nsIDocument> ourParentDocument =
     ourChildDocument->GetParentDocument();
   nsCOMPtr<nsIDocument> otherParentDocument =
     otherChildDocument->GetParentDocument();
 
   // Make sure to swap docshells between the two frames.
-  nsIDocument* ourDoc = ourContent->GetCurrentDoc();
-  nsIDocument* otherDoc = otherContent->GetCurrentDoc();
+  nsIDocument* ourDoc = ourContent->GetUncomposedDoc();
+  nsIDocument* otherDoc = otherContent->GetUncomposedDoc();
   if (!ourDoc || !otherDoc) {
     // Again, how odd, given that we had docshells
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   NS_ASSERTION(ourDoc == ourParentDocument, "Unexpected parent document");
   NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document");
 
--- a/content/base/src/nsGenericDOMDataNode.cpp
+++ b/content/base/src/nsGenericDOMDataNode.cpp
@@ -292,17 +292,17 @@ nsGenericDOMDataNode::SetTextInternal(ui
 
   uint32_t endOffset = aOffset + aCount;
 
   // Make sure the text fragment can hold the new data.
   if (aLength > aCount && !mText.CanGrowBy(aLength - aCount)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  nsIDocument *document = GetCurrentDoc();
+  nsIDocument *document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, aNotify);
 
   bool haveMutationListeners = aNotify &&
     nsContentUtils::HasMutationListeners(this,
       NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED,
       this);
 
   nsCOMPtr<nsIAtom> oldValue;
@@ -460,19 +460,19 @@ nsGenericDOMDataNode::ToCString(nsAStrin
 nsresult
 nsGenericDOMDataNode::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent,
                                  bool aCompileEventHandlers)
 {
   NS_PRECONDITION(aParent || aDocument, "Must have document if no parent!");
   NS_PRECONDITION(NODE_FROM(aParent, aDocument)->OwnerDoc() == OwnerDoc(),
                   "Must have the same owner document");
-  NS_PRECONDITION(!aParent || aDocument == aParent->GetCurrentDoc(),
+  NS_PRECONDITION(!aParent || aDocument == aParent->GetUncomposedDoc(),
                   "aDocument must be current doc of aParent");
-  NS_PRECONDITION(!GetCurrentDoc() && !IsInDoc(),
+  NS_PRECONDITION(!GetUncomposedDoc() && !IsInDoc(),
                   "Already have a document.  Unbind first!");
   // Note that as we recurse into the kids, they'll have a non-null parent.  So
   // only assert if our parent is _changing_ while we have a parent.
   NS_PRECONDITION(!GetParent() || aParent == GetParent(),
                   "Already have a parent.  Unbind first!");
   NS_PRECONDITION(!GetBindingParent() ||
                   aBindingParent == GetBindingParent() ||
                   (!aBindingParent && aParent &&
@@ -545,38 +545,34 @@ nsGenericDOMDataNode::BindToTree(nsIDocu
     // update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
   nsNodeUtils::ParentChainChanged(this);
 
   UpdateEditableState(false);
 
-  NS_POSTCONDITION(aDocument == GetCurrentDoc(), "Bound to wrong document");
+  NS_POSTCONDITION(aDocument == GetUncomposedDoc(), "Bound to wrong document");
   NS_POSTCONDITION(aParent == GetParent(), "Bound to wrong parent");
   NS_POSTCONDITION(aBindingParent == GetBindingParent(),
                    "Bound to wrong binding parent");
 
   return NS_OK;
 }
 
 void
 nsGenericDOMDataNode::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // Unset frame flags; if we need them again later, they'll get set again.
   UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
              NS_REFRAME_IF_WHITESPACE);
-  
-  nsIDocument *document = GetCurrentDoc();
-  if (document) {
-    // Notify XBL- & nsIAnonymousContentCreator-generated
-    // anonymous content that the document is changing.
-    // This is needed to update the insertion point.
-    document->BindingManager()->RemovedFromDocument(this, document);
-  }
+
+  nsIDocument* document =
+    HasFlag(NODE_FORCE_XBL_BINDINGS) || IsInShadowTree() ?
+      OwnerDoc() : GetUncomposedDoc();
 
   if (aNullParent) {
     if (GetParent()) {
       NS_RELEASE(mParent);
     } else {
       mParent = nullptr;
     }
     SetParentIsContent(false);
@@ -585,16 +581,28 @@ nsGenericDOMDataNode::UnbindFromTree(boo
 
   if (aNullParent || !mParent->IsInShadowTree()) {
     UnsetFlags(NODE_IS_IN_SHADOW_TREE);
 
     // Begin keeping track of our subtree root.
     SetSubtreeRootPointer(aNullParent ? this : mParent->SubtreeRoot());
   }
 
+  if (document && !GetContainingShadow()) {
+    // Notify XBL- & nsIAnonymousContentCreator-generated
+    // anonymous content that the document is changing.
+    // Unlike XBL, bindings for web components shadow DOM
+    // do not get uninstalled.
+    if (HasFlag(NODE_MAY_BE_IN_BINDING_MNGR)) {
+      nsContentUtils::AddScriptRunner(
+        new RemoveFromBindingManagerRunnable(document->BindingManager(), this,
+                                             document));
+    }
+  }
+
   nsDataSlots *slots = GetExistingDataSlots();
   if (slots) {
     slots->mBindingParent = nullptr;
     if (aNullParent || !mParent->IsInShadowTree()) {
       slots->mContainingShadow = nullptr;
     }
   }
 
@@ -850,17 +858,17 @@ nsGenericDOMDataNode::SplitData(uint32_t
 
   uint32_t cutStartOffset = aCloneAfterOriginal ? aOffset : 0;
   uint32_t cutLength = aCloneAfterOriginal ? length - aOffset : aOffset;
   rv = SubstringData(cutStartOffset, cutLength, cutText);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  nsIDocument* document = GetCurrentDoc();
+  nsIDocument* document = GetComposedDoc();
   mozAutoDocUpdate updateBatch(document, UPDATE_CONTENT_MODEL, true);
 
   // Use Clone for creating the new node so that the new node is of same class
   // as this node!
   nsCOMPtr<nsIContent> newContent = CloneDataNode(mNodeInfo, false);
   if (!newContent) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
--- a/content/base/src/nsINode.cpp
+++ b/content/base/src/nsINode.cpp
@@ -194,17 +194,17 @@ nsINode::CreateSlots()
 bool
 nsINode::IsEditableInternal() const
 {
   if (HasFlag(NODE_IS_EDITABLE)) {
     // The node is in an editable contentEditable subtree.
     return true;
   }
 
-  nsIDocument *doc = GetCurrentDoc();
+  nsIDocument *doc = GetUncomposedDoc();
 
   // Check if the node is in a document and the document is in designMode.
   return doc && doc->HasFlag(NODE_IS_EDITABLE);
 }
 
 static nsIContent* GetEditorRootContent(nsIEditor* aEditor)
 {
   nsCOMPtr<nsIDOMElement> rootElement;
@@ -1331,17 +1331,17 @@ nsINode::UnoptimizableCCNode() const
           AsElement()->IsInNamespace(kNameSpaceID_XBL));
 }
 
 /* static */
 bool
 nsINode::Traverse(nsINode *tmp, nsCycleCollectionTraversalCallback &cb)
 {
   if (MOZ_LIKELY(!cb.WantAllTraces())) {
-    nsIDocument *currentDoc = tmp->GetCurrentDoc();
+    nsIDocument *currentDoc = tmp->GetUncomposedDoc();
     if (currentDoc &&
         nsCCUncollectableMarker::InGeneration(currentDoc->GetMarkedCCGeneration())) {
       return false;
     }
 
     if (nsCCUncollectableMarker::sGeneration) {
       // If we're black no need to traverse.
       if (tmp->IsBlack() || tmp->InCCBlackTree()) {
@@ -1493,17 +1493,17 @@ nsINode::doInsertChildAt(nsIContent* aKi
                   "Inserting node that already has parent");
   nsresult rv;
 
   // The id-handling code, and in the future possibly other code, need to
   // react to unexpected attribute changes.
   nsMutationGuard::DidMutate();
 
   // Do this before checking the child-count since this could cause mutations
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   mozAutoDocUpdate updateBatch(GetCrossShadowCurrentDoc(), UPDATE_CONTENT_MODEL, aNotify);
 
   if (OwnerDoc() != aKid->OwnerDoc()) {
     rv = AdoptNodeIntoOwnerDoc(this, aKid);
     NS_ENSURE_SUCCESS(rv, rv);
   } else if (OwnerDoc()->DidDocumentOpen()) {
     rv = CheckForOutdatedParent(this, aKid);
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1923,17 +1923,17 @@ nsINode::ReplaceOrInsertBefore(bool aRep
     nsCOMPtr<nsINode> kungFuDeathGrip = nodeToInsertBefore;
 
     // Removing a child can run script, via XBL destructors.
     nsMutationGuard guard;
 
     // Scope for the mutation batch and scriptblocker, so they go away
     // while kungFuDeathGrip is still alive.
     {
-      mozAutoDocUpdate batch(newContent->GetCurrentDoc(),
+      mozAutoDocUpdate batch(newContent->GetComposedDoc(),
                              UPDATE_CONTENT_MODEL, true);
       nsAutoMutationBatch mb(oldParent, true, true);
       oldParent->RemoveChildAt(removeIndex, true);
       if (nsAutoMutationBatch::GetCurrentBatch() == &mb) {
         mb.RemovalDone();
         mb.SetPrevSibling(oldParent->GetChildAt(removeIndex - 1));
         mb.SetNextSibling(oldParent->GetChildAt(removeIndex));
       }
@@ -1988,30 +1988,30 @@ nsINode::ReplaceOrInsertBefore(bool aRep
     fragChildren.emplace();
 
     // Copy the children into a separate array to avoid having to deal with
     // mutations to the fragment later on here.
     fragChildren->SetCapacity(count);
     for (nsIContent* child = newContent->GetFirstChild();
          child;
          child = child->GetNextSibling()) {
-      NS_ASSERTION(child->GetCurrentDoc() == nullptr,
+      NS_ASSERTION(child->GetComposedDoc() == nullptr,
                    "How did we get a child with a current doc?");
       fragChildren->AppendElement(child);
     }
 
     // Hold a strong ref to nodeToInsertBefore across the removals
     nsCOMPtr<nsINode> kungFuDeathGrip = nodeToInsertBefore;
 
     nsMutationGuard guard;
 
     // Scope for the mutation batch and scriptblocker, so they go away
     // while kungFuDeathGrip is still alive.
     {
-      mozAutoDocUpdate batch(newContent->GetCurrentDoc(),
+      mozAutoDocUpdate batch(newContent->GetComposedDoc(),
                              UPDATE_CONTENT_MODEL, true);
       nsAutoMutationBatch mb(newContent, false, true);
 
       for (uint32_t i = count; i > 0;) {
         newContent->RemoveChildAt(--i, true);
       }
     }
 
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -1058,17 +1058,17 @@ nsImageLoadingContent::GetOurOwnerDoc()
 
 nsIDocument*
 nsImageLoadingContent::GetOurCurrentDoc()
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ENSURE_TRUE(thisContent, nullptr);
 
-  return thisContent->GetCurrentDoc();
+  return thisContent->GetComposedDoc();
 }
 
 nsIFrame*
 nsImageLoadingContent::GetOurPrimaryFrame()
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   return thisContent->GetPrimaryFrame();
--- a/content/base/src/nsMappedAttributeElement.cpp
+++ b/content/base/src/nsMappedAttributeElement.cpp
@@ -15,17 +15,17 @@ nsMappedAttributeElement::WalkContentSty
 }
 
 bool
 nsMappedAttributeElement::SetMappedAttribute(nsIDocument* aDocument,
                                              nsIAtom* aName,
                                              nsAttrValue& aValue,
                                              nsresult* aRetval)
 {
-  NS_PRECONDITION(aDocument == GetCurrentDoc(), "Unexpected document");
+  NS_PRECONDITION(aDocument == GetComposedDoc(), "Unexpected document");
   nsHTMLStyleSheet* sheet = aDocument ?
     aDocument->GetAttributeStyleSheet() : nullptr;
 
   *aRetval = mAttrsAndChildren.SetAndTakeMappedAttr(aName, aValue,
                                                     this, sheet);
   return true;
 }
 
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -204,19 +204,19 @@ CheckPluginStopEvent::Run()
          ", no action", this));
     objLC->mPendingCheckPluginStopEvent = nullptr;
     return NS_OK;
   }
 
   // In an active document, but still no frame. Flush layout to see if we can
   // regain a frame now.
   LOG(("OBJLC [%p]: CheckPluginStopEvent - No frame, flushing layout", this));
-  nsIDocument* currentDoc = content->GetCurrentDoc();
-  if (currentDoc) {
-    currentDoc->FlushPendingNotifications(Flush_Layout);
+  nsIDocument* composedDoc = content->GetComposedDoc();
+  if (composedDoc) {
+    composedDoc->FlushPendingNotifications(Flush_Layout);
     if (objLC->mPendingCheckPluginStopEvent != this) {
       LOG(("OBJLC [%p]: CheckPluginStopEvent - superseded in layout flush",
            this));
       return NS_OK;
     } else if (content->GetPrimaryFrame()) {
       LOG(("OBJLC [%p]: CheckPluginStopEvent - frame gained in layout flush",
            this));
       objLC->mPendingCheckPluginStopEvent = nullptr;
@@ -234,17 +234,17 @@ CheckPluginStopEvent::Run()
 
 /**
  * Helper task for firing simple events
  */
 class nsSimplePluginEvent : public nsRunnable {
 public:
   nsSimplePluginEvent(nsIContent* aTarget, const nsAString &aEvent)
     : mTarget(aTarget)
-    , mDocument(aTarget->GetCurrentDoc())
+    , mDocument(aTarget->GetComposedDoc())
     , mEvent(aEvent)
   {
     MOZ_ASSERT(aTarget && mDocument);
   }
 
   nsSimplePluginEvent(nsIDocument* aTarget, const nsAString& aEvent)
     : mTarget(aTarget)
     , mDocument(aTarget)
@@ -656,17 +656,17 @@ nsObjectLoadingContent::IsSupportedDocum
 
   nsCOMPtr<nsIWebNavigationInfo> info(
     do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID));
   if (!info) {
     return false;
   }
 
   nsCOMPtr<nsIWebNavigation> webNav;
-  nsIDocument* currentDoc = thisContent->GetCurrentDoc();
+  nsIDocument* currentDoc = thisContent->GetComposedDoc();
   if (currentDoc) {
     webNav = do_GetInterface(currentDoc->GetWindow());
   }
 
   uint32_t supported;
   nsresult rv = info->IsTypeSupported(aMimeType, webNav, &supported);
 
   if (NS_FAILED(rv)) {
@@ -725,17 +725,17 @@ nsObjectLoadingContent::UnbindFromTree(b
     QueueCheckPluginStopEvent();
   } else if (mType != eType_Image) {
     // nsImageLoadingContent handles the image case.
     // Reset state and clear pending events
     /// XXX(johns): The implementation for GenericFrame notes that ideally we
     ///             would keep the docshell around, but trash the frameloader
     UnloadObject();
   }
-  nsIDocument* doc = thisContent->GetCurrentDoc();
+  nsIDocument* doc = thisContent->GetComposedDoc();
   if (doc && doc->IsActive()) {
     nsCOMPtr<nsIRunnable> ev = new nsSimplePluginEvent(doc,
                                                        NS_LITERAL_STRING("PluginRemoved"));
     NS_DispatchToCurrentThread(ev);
   }
 }
 
 nsObjectLoadingContent::nsObjectLoadingContent()
@@ -781,17 +781,17 @@ nsObjectLoadingContent::InstantiatePlugi
   }
 
   mInstantiating = true;
   AutoSetInstantiatingToFalse autoInstantiating(this);
 
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent *>(this));
 
-  nsCOMPtr<nsIDocument> doc = thisContent->GetCurrentDoc();
+  nsCOMPtr<nsIDocument> doc = thisContent->GetComposedDoc();
   if (!doc || !InActiveDocument(thisContent)) {
     NS_ERROR("Shouldn't be calling "
              "InstantiatePluginInstance without an active document");
     return NS_ERROR_FAILURE;
   }
 
   // Instantiating an instance can result in script execution, which
   // can destroy this DOM object. Don't allow that for the scope
@@ -1173,18 +1173,18 @@ nsObjectLoadingContent::OnStopRequest(ns
     js::ProfileEntry::Category::NETWORK);
 
   // Handle object not loading error because source was a tracking URL.
   // We make a note of this object node by including it in a dedicated
   // array of blocked tracking nodes under its parent document.
   if (aStatusCode == NS_ERROR_TRACKING_URI) {
     nsCOMPtr<nsIContent> thisNode =
       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
-    if (thisNode) {
-      thisNode->GetCurrentDoc()->AddBlockedTrackingNode(thisNode);
+    if (thisNode && thisNode->IsInComposedDoc()) {
+      thisNode->GetComposedDoc()->AddBlockedTrackingNode(thisNode);
     }
   }
 
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_NOT_AVAILABLE);
 
   if (aRequest != mChannel) {
     return NS_BINDING_ABORTED;
   }
@@ -2655,17 +2655,17 @@ nsObjectLoadingContent::NotifyStateChang
   // manually notify object state changes.
   thisContent->AsElement()->UpdateState(false);
 
   if (!aNotify) {
     // We're done here
     return;
   }
 
-  nsIDocument* doc = thisContent->GetCurrentDoc();
+  nsIDocument* doc = thisContent->GetComposedDoc();
   if (!doc) {
     return; // Nothing to do
   }
 
   EventStates newState = ObjectState();
 
   if (newState != aOldState) {
     // This will trigger frame construction
@@ -3344,17 +3344,17 @@ nsObjectLoadingContent::GetContentDocume
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 
   if (!thisContent->IsInDoc()) {
     return nullptr;
   }
 
-  // XXXbz should this use GetCurrentDoc()?  sXBL/XBL2 issue!
+  // XXXbz should this use GetComposedDoc()?  sXBL/XBL2 issue!
   nsIDocument *sub_doc = thisContent->OwnerDoc()->GetSubDocumentFor(thisContent);
   if (!sub_doc) {
     return nullptr;
   }
 
   // Return null for cross-origin contentDocument.
   if (!nsContentUtils::SubjectPrincipal()->SubsumesConsideringDomain(sub_doc->NodePrincipal())) {
     return nullptr;
--- a/content/base/src/nsRange.cpp
+++ b/content/base/src/nsRange.cpp
@@ -1079,25 +1079,25 @@ nsRange::IsValidBoundary(nsINode* aNode)
       if (root) {
         return root;
       }
     }
   }
 
   // Elements etc. must be in document or in document fragment,
   // text nodes in document, in document fragment or in attribute.
-  nsINode* root = aNode->GetCurrentDoc();
+  nsINode* root = aNode->GetUncomposedDoc();
   if (root) {
     return root;
   }
 
   root = aNode->SubtreeRoot();
 
   NS_ASSERTION(!root->IsNodeOfType(nsINode::eDOCUMENT),
-               "GetCurrentDoc should have returned a doc");
+               "GetUncomposedDoc should have returned a doc");
 
   // We allow this because of backward compatibility.
   return root;
 }
 
 void
 nsRange::SetStart(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
 {
--- a/content/base/src/nsReferencedElement.cpp
+++ b/content/base/src/nsReferencedElement.cpp
@@ -42,17 +42,17 @@ nsReferencedElement::Reset(nsIContent* a
     // assumes UTF-8 and doesn't handle UTF-8 errors.
     // https://bugzilla.mozilla.org/show_bug.cgi?id=951082
     CopyUTF8toUTF16(refPart, ref);
   }
   if (ref.IsEmpty())
     return;
 
   // Get the current document
-  nsIDocument *doc = aFromContent->GetCurrentDoc();
+  nsIDocument *doc = aFromContent->GetComposedDoc();
   if (!doc)
     return;
 
   nsIContent* bindingParent = aFromContent->GetBindingParent();
   if (bindingParent) {
     nsXBLBinding* binding = bindingParent->GetXBLBinding();
     if (binding) {
       bool isEqualExceptRef;
@@ -119,17 +119,17 @@ nsReferencedElement::Reset(nsIContent* a
 
   HaveNewDocument(doc, aWatch, ref);
 }
 
 void
 nsReferencedElement::ResetWithID(nsIContent* aFromContent, const nsString& aID,
                                  bool aWatch)
 {
-  nsIDocument *doc = aFromContent->GetCurrentDoc();
+  nsIDocument *doc = aFromContent->GetComposedDoc();
   if (!doc)
     return;
 
   // XXX Need to take care of XBL/XBL2
 
   if (aWatch) {
     nsCOMPtr<nsIAtom> atom = do_GetAtom(aID);
     if (!atom)
--- a/content/html/content/src/HTMLAnchorElement.cpp
+++ b/content/html/content/src/HTMLAnchorElement.cpp
@@ -145,18 +145,19 @@ HTMLAnchorElement::BindToTree(nsIDocumen
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Prefetch links
-  if (aDocument) {
-    aDocument->RegisterPendingLinkUpdate(this);
+  nsIDocument* doc = GetComposedDoc();
+  if (doc) {
+    doc->RegisterPendingLinkUpdate(this);
     if (nsHTMLDNSPrefetch::IsAllowed(OwnerDoc())) {
       nsHTMLDNSPrefetch::PrefetchLow(this);
     }
   }
 
   return rv;
 }
 
@@ -176,18 +177,20 @@ HTMLAnchorElement::UnbindFromTree(bool a
     // Possible that hostname could have changed since binding, but since this
     // covers common cases, most DNS prefetch requests will be canceled
     nsHTMLDNSPrefetch::CancelPrefetchLow(this, NS_ERROR_ABORT);
   }
   
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
-  
-  nsIDocument* doc = GetCurrentDoc();
+
+  // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
+  // return null.
+  nsIDocument* doc = OwnerDoc();
   if (doc) {
     doc->UnregisterPendingLinkUpdate(this);
   }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 bool
--- a/content/html/content/src/HTMLAreaElement.cpp
+++ b/content/html/content/src/HTMLAreaElement.cpp
@@ -130,33 +130,38 @@ HTMLAreaElement::RelList()
 }
 
 nsresult
 HTMLAreaElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                             nsIContent* aBindingParent,
                             bool aCompileEventHandlers)
 {
   Link::ResetLinkState(false, Link::ElementHasHref());
-  if (aDocument) {
-    aDocument->RegisterPendingLinkUpdate(this);
-  }
-  
-  return nsGenericHTMLElement::BindToTree(aDocument, aParent,
+  nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent,
                                                  aBindingParent,
                                                  aCompileEventHandlers);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsIDocument* doc = GetComposedDoc();
+  if (doc) {
+    doc->RegisterPendingLinkUpdate(this);
+  }
+  return rv;
 }
 
 void
 HTMLAreaElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
-  
-  nsIDocument* doc = GetCurrentDoc();
+
+  // Note, we need to use OwnerDoc() here, since GetComposedDoc() might
+  // return null.
+  nsIDocument* doc = OwnerDoc();
   if (doc) {
     doc->UnregisterPendingLinkUpdate(this);
   }
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 nsresult
--- a/content/html/content/src/HTMLBodyElement.cpp
+++ b/content/html/content/src/HTMLBodyElement.cpp
@@ -396,17 +396,17 @@ HTMLBodyElement::GetAttributeMappingFunc
 }
 
 NS_IMETHODIMP
 HTMLBodyElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker)
 {
   nsGenericHTMLElement::WalkContentStyleRules(aRuleWalker);
 
   if (!mContentStyleRule && IsInDoc()) {
-    // XXXbz should this use OwnerDoc() or GetCurrentDoc()?
+    // XXXbz should this use OwnerDoc() or GetComposedDoc()?
     // sXBL/XBL2 issue!
     mContentStyleRule = new BodyRule(this);
   }
   if (aRuleWalker && mContentStyleRule) {
     aRuleWalker->Forward(mContentStyleRule);
   }
   return NS_OK;
 }
--- a/content/html/content/src/HTMLFormControlsCollection.cpp
+++ b/content/html/content/src/HTMLFormControlsCollection.cpp
@@ -108,17 +108,17 @@ HTMLFormControlsCollection::Clear()
 
   mNameLookupTable.Clear();
 }
 
 void
 HTMLFormControlsCollection::FlushPendingNotifications()
 {
   if (mForm) {
-    nsIDocument* doc = mForm->GetCurrentDoc();
+    nsIDocument* doc = mForm->GetUncomposedDoc();
     if (doc) {
       doc->FlushPendingNotifications(Flush_Content);
     }
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLFormControlsCollection)
 
--- a/content/html/content/src/HTMLFormElement.cpp
+++ b/content/html/content/src/HTMLFormElement.cpp
@@ -441,17 +441,17 @@ CollectOrphans(nsINode* aRemovalRoot,
     }
 #endif /* DEBUG */
   }
 }
 
 void
 HTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetCurrentDoc());
+  nsCOMPtr<nsIHTMLDocument> oldDocument = do_QueryInterface(GetUncomposedDoc());
 
   // Mark all of our controls as maybe being orphans
   MarkOrphans(mControls->mElements);
   MarkOrphans(mControls->mNotInElements);
   MarkOrphans(mImageElements);
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
@@ -578,17 +578,17 @@ HTMLFormElement::PostHandleEvent(EventCh
   return NS_OK;
 }
 
 nsresult
 HTMLFormElement::DoSubmitOrReset(WidgetEvent* aEvent,
                                  int32_t aMessage)
 {
   // Make sure the presentation is up-to-date
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (doc) {
     doc->FlushPendingNotifications(Flush_ContentAndNotify);
   }
 
   // JBK Don't get form frames anymore - bug 34297
 
   // Submit or Reset the form
   if (NS_FORM_RESET == aMessage) {
@@ -628,17 +628,17 @@ HTMLFormElement::DoReset()
   if (NS_FAILED(rv)) {                                                        \
     ForgetCurrentSubmission();                                                \
     return rv;                                                                \
   }
 
 nsresult
 HTMLFormElement::DoSubmit(WidgetEvent* aEvent)
 {
-  NS_ASSERTION(GetCurrentDoc(), "Should never get here without a current doc");
+  NS_ASSERTION(GetComposedDoc(), "Should never get here without a current doc");
 
   if (mIsSubmitting) {
     NS_WARNING("Preventing double form submission");
     // XXX Should this return an error?
     return NS_OK;
   }
 
   // Mark us as submitting so that we don't try to submit again
@@ -736,17 +736,17 @@ HTMLFormElement::SubmitSubmission(nsForm
   NS_ENSURE_SUBMIT_SUCCESS(rv);
 
   if (!actionURI) {
     mIsSubmitting = false;
     return NS_OK;
   }
 
   // If there is no link handler, then we won't actually be able to submit.
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   nsCOMPtr<nsISupports> container = doc ? doc->GetContainer() : nullptr;
   nsCOMPtr<nsILinkHandler> linkHandler(do_QueryInterface(container));
   if (!linkHandler || IsEditable()) {
     mIsSubmitting = false;
     return NS_OK;
   }
 
   // javascript URIs are not really submissions; they just call a function.
@@ -1796,17 +1796,17 @@ HTMLFormElement::CheckValidFormSubmissio
    */
 
   NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::novalidate),
                "We shouldn't be there if novalidate is set!");
 
   // Don't do validation for a form submit done by a sandboxed document that
   // doesn't have 'allow-forms', the submit will have been blocked and the
   // HTML5 spec says we shouldn't validate in this case.
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (doc && (doc->GetSandboxFlags() & SANDBOXED_FORMS)) {
     return true;
   }
 
   // When .submit() is called aEvent = nullptr so we can rely on that to know if
   // we have to check the validity of the form.
   nsCOMPtr<nsIObserverService> service =
     mozilla::services::GetObserverService();
--- a/content/html/content/src/HTMLInputElement.cpp
+++ b/content/html/content/src/HTMLInputElement.cpp
@@ -1853,17 +1853,18 @@ nsGenericHTMLElement*
 HTMLInputElement::GetList() const
 {
   nsAutoString dataListId;
   GetAttr(kNameSpaceID_None, nsGkAtoms::list, dataListId);
   if (dataListId.IsEmpty()) {
     return nullptr;
   }
 
-  nsIDocument* doc = GetCurrentDoc();
+  //XXXsmaug How should this all work in case input element is in Shadow DOM.
+  nsIDocument* doc = GetUncomposedDoc();
   if (!doc) {
     return nullptr;
   }
 
   Element* element = doc->GetElementById(dataListId);
   if (!element || !element->IsHTML(nsGkAtoms::datalist)) {
     return nullptr;
   }
@@ -2257,18 +2258,18 @@ NS_IMETHODIMP
 HTMLInputElement::StepUp(int32_t n, uint8_t optional_argc)
 {
   return ApplyStep(optional_argc ? n : 1);
 }
 
 void
 HTMLInputElement::FlushFrames()
 {
-  if (GetCurrentDoc()) {
-    GetCurrentDoc()->FlushPendingNotifications(Flush_Frames);
+  if (GetComposedDoc()) {
+    GetComposedDoc()->FlushPendingNotifications(Flush_Frames);
   }
 }
 
 void
 HTMLInputElement::MozGetFileNameArray(nsTArray< nsString >& aArray)
 {
   for (uint32_t i = 0; i < mFiles.Length(); i++) {
     nsString str;
@@ -3024,17 +3025,18 @@ HTMLInputElement::GetRadioGroupContainer
   if (name.IsEmpty()) {
     return nullptr;
   }
 
   if (mForm) {
     return mForm;
   }
 
-  return static_cast<nsDocument*>(GetCurrentDoc());
+  //XXXsmaug It isn't clear how this should work in Shadow DOM.
+  return static_cast<nsDocument*>(GetUncomposedDoc());
 }
 
 already_AddRefed<nsIDOMHTMLInputElement>
 HTMLInputElement::GetSelectedRadioButton()
 {
   nsIRadioGroupContainer* container = GetRadioGroupContainer();
   if (!container) {
     return nullptr;
@@ -3959,17 +3961,17 @@ HTMLInputElement::PostHandleEvent(EventC
           // see if we should select the contents of the textbox. This happens
           // for text and password fields when the field was focused by the
           // keyboard or a navigation, the platform allows it, and it wasn't
           // just because we raised a window.
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm && IsSingleLineTextControl(false) &&
               !aVisitor.mEvent->AsFocusEvent()->fromRaise &&
               SelectTextFieldOnFocus()) {
-            nsIDocument* document = GetCurrentDoc();
+            nsIDocument* document = GetComposedDoc();
             if (document) {
               uint32_t lastFocusMethod;
               fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
               if (lastFocusMethod &
                   (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
                 nsRefPtr<nsPresContext> presContext =
                   GetPresContext(eForComposedDoc);
                 if (DispatchSelectEvent(presContext)) {
--- a/content/html/content/src/HTMLLabelElement.cpp
+++ b/content/html/content/src/HTMLLabelElement.cpp
@@ -241,17 +241,20 @@ HTMLLabelElement::GetLabeledElement() co
   if (!GetAttr(kNameSpaceID_None, nsGkAtoms::_for, elementId)) {
     // No @for, so we are a label for our first form control element.
     // Do a depth-first traversal to look for the first form control element.
     return GetFirstLabelableDescendant();
   }
 
   // We have a @for. The id has to be linked to an element in the same document
   // and this element should be a labelable form control.
-  nsIDocument* doc = GetCurrentDoc();
+  //XXXsmaug It is unclear how this should work in case the element is in
+  //         Shadow DOM.
+  //         See https://www.w3.org/Bugs/Public/show_bug.cgi?id=26365.
+  nsIDocument* doc = GetUncomposedDoc();
   if (!doc) {
     return nullptr;
   }
 
   Element* element = doc->GetElementById(elementId);
   if (element && element->IsLabelable()) {
     return static_cast<nsGenericHTMLElement*>(element);
   }
--- a/content/html/content/src/HTMLLinkElement.cpp
+++ b/content/html/content/src/HTMLLinkElement.cpp
@@ -172,28 +172,25 @@ HTMLLinkElement::LinkRemoved()
 
 void
 HTMLLinkElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
-  // Once we have XPCOMGC we shouldn't need to call UnbindFromTree during Unlink
-  // and so this messy event dispatch can go away.
-  nsCOMPtr<nsIDocument> oldDoc = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
 
   // Check for a ShadowRoot because link elements are inert in a
   // ShadowRoot.
   ShadowRoot* oldShadowRoot = GetBindingParent() ?
     GetBindingParent()->GetShadowRoot() : nullptr;
 
-  if (oldDoc && !oldShadowRoot) {
-    oldDoc->UnregisterPendingLinkUpdate(this);
-  }
+  OwnerDoc()->UnregisterPendingLinkUpdate(this);
+
   CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMLinkRemoved"));
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   UpdateStyleSheetInternal(oldDoc, oldShadowRoot);
   UpdateImport();
 }
 
 bool
@@ -246,17 +243,17 @@ HTMLLinkElement::CreateAndDispatchEvent(
   // sink isn't expecting it.
   asyncDispatcher->PostDOMEvent();
 }
 
 void
 HTMLLinkElement::UpdateImport()
 {
   // 1. link node should be attached to the document.
-  nsCOMPtr<nsIDocument> doc = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> doc = GetUncomposedDoc();
   if (!doc) {
     // We might have been just removed from the document, so
     // let's remove ourself from the list of link nodes of
     // the import and reset mImportLoader.
     if (mImportLoader) {
       mImportLoader->RemoveLinkElement(this);
       mImportLoader = nullptr;
     }
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -2838,32 +2838,36 @@ void HTMLMediaElement::SetupSrcMediaStre
   ChangeDelayLoadStatus(false);
   GetSrcMediaStream()->AddAudioOutput(this);
   GetSrcMediaStream()->SetAudioOutputVolume(this, float(mMuted ? 0.0 : mVolume));
   VideoFrameContainer* container = GetVideoFrameContainer();
   if (container) {
     GetSrcMediaStream()->AddVideoOutput(container);
   }
 
+  // Note: we must call DisconnectTrackListListeners(...)  before dropping
+  // mSrcStream
   mSrcStream->ConstructMediaTracks(AudioTracks(), VideoTracks());
 
   ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
   DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
   ChangeNetworkState(nsIDOMHTMLMediaElement::NETWORK_IDLE);
   AddRemoveSelfReference();
   // FirstFrameLoaded() will be called when the stream has current data.
 }
 
 void HTMLMediaElement::EndSrcMediaStreamPlayback()
 {
   MediaStream* stream = GetSrcMediaStream();
   if (stream) {
     stream->RemoveListener(mSrcStreamListener);
   }
+  mSrcStream->DisconnectTrackListListeners(AudioTracks(), VideoTracks());
+
   // Kill its reference to this element
   mSrcStreamListener->Forget();
   mSrcStreamListener = nullptr;
   if (stream) {
     stream->RemoveAudioOutput(this);
   }
   VideoFrameContainer* container = GetVideoFrameContainer();
   if (container) {
@@ -3090,24 +3094,16 @@ void HTMLMediaElement::UpdateReadyStateF
 {
   if (mReadyState < nsIDOMHTMLMediaElement::HAVE_METADATA) {
     // aNextFrame might have a next frame because the decoder can advance
     // on its own thread before MetadataLoaded gets a chance to run.
     // The arrival of more data can't change us out of this readyState.
     return;
   }
 
-  // Section 2.4.3.1 of the Media Source Extensions spec requires
-  // changing to HAVE_METADATA when seeking into an unbuffered
-  // range.
-  if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_WAIT_FOR_MSE_DATA) {
-    ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
-    return;
-  }
-
   if (aNextFrame == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
     ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_METADATA);
     return;
   }
 
   if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
     // The decoder has signaled that the download has been suspended by the
     // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
--- a/content/html/content/src/HTMLMenuElement.cpp
+++ b/content/html/content/src/HTMLMenuElement.cpp
@@ -65,17 +65,17 @@ NS_IMPL_ENUM_ATTR_DEFAULT_VALUE(HTMLMenu
 NS_IMPL_STRING_ATTR(HTMLMenuElement, Label, label)
 
 
 NS_IMETHODIMP
 HTMLMenuElement::SendShowEvent()
 {
   NS_ENSURE_TRUE(nsContentUtils::IsCallerChrome(), NS_ERROR_DOM_SECURITY_ERR);
 
-  nsCOMPtr<nsIDocument> document = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> document = GetComposedDoc();
   if (!document) {
     return NS_ERROR_FAILURE;
   }
 
   WidgetEvent event(true, NS_SHOW_EVENT);
   event.mFlags.mBubbles = false;
   event.mFlags.mCancelable = false;
 
--- a/content/html/content/src/HTMLMetaElement.cpp
+++ b/content/html/content/src/HTMLMetaElement.cpp
@@ -49,17 +49,17 @@ HTMLMetaElement::SetItemValueText(const 
 
 
 nsresult
 HTMLMetaElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                               const nsAttrValue* aValue, bool aNotify)
 {
   if (aNameSpaceID == kNameSpaceID_None) {
     if (aName == nsGkAtoms::content) {
-      nsIDocument *document = GetCurrentDoc();
+      nsIDocument *document = GetUncomposedDoc();
       CreateAndDispatchEvent(document, NS_LITERAL_STRING("DOMMetaChanged"));
     }
   }
 
   return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue,
                                             aNotify);
 }
 
@@ -81,17 +81,17 @@ HTMLMetaElement::BindToTree(nsIDocument*
   }
   CreateAndDispatchEvent(aDocument, NS_LITERAL_STRING("DOMMetaAdded"));
   return rv;
 }
 
 void
 HTMLMetaElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  nsCOMPtr<nsIDocument> oldDoc = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
   CreateAndDispatchEvent(oldDoc, NS_LITERAL_STRING("DOMMetaRemoved"));
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 HTMLMetaElement::CreateAndDispatchEvent(nsIDocument* aDoc,
                                         const nsAString& aEventName)
 {
--- a/content/html/content/src/HTMLObjectElement.cpp
+++ b/content/html/content/src/HTMLObjectElement.cpp
@@ -195,33 +195,33 @@ HTMLObjectElement::UnsetAttr(int32_t aNa
   }
 
   return NS_OK;
 }
 
 bool
 HTMLObjectElement::IsFocusableForTabIndex()
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
     return false;
   }
 
   return IsEditableRoot() ||
          (Type() == eType_Document &&
           nsContentUtils::IsSubDocumentTabbable(this));
 }
 
 bool
 HTMLObjectElement::IsHTMLFocusable(bool aWithMouse,
                                    bool *aIsFocusable, int32_t *aTabIndex)
 {
   // TODO: this should probably be managed directly by IsHTMLFocusable.
   // See bug 597242.
-  nsIDocument *doc = GetCurrentDoc();
+  nsIDocument *doc = GetComposedDoc();
   if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) {
     if (aTabIndex) {
       GetTabIndex(aTabIndex);
     }
 
     *aIsFocusable = false;
 
     return false;
--- a/content/html/content/src/HTMLPropertiesCollection.cpp
+++ b/content/html/content/src/HTMLPropertiesCollection.cpp
@@ -38,17 +38,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(HTMLPropertiesCollection)
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 HTMLPropertiesCollection::HTMLPropertiesCollection(nsGenericHTMLElement* aRoot)
   : mRoot(aRoot)
-  , mDoc(aRoot->GetCurrentDoc())
+  , mDoc(aRoot->GetUncomposedDoc())
   , mIsDirty(true)
 {
   SetIsDOMBinding();
   mNames = new PropertyStringList(this);
   if (mDoc) {
     mDoc->AddMutationObserver(this);
   }
 }
@@ -243,17 +243,17 @@ GetElementByIdForConnectedSubtree(nsICon
   } while(aContent);
 
   return nullptr;
 }
 
 void
 HTMLPropertiesCollection::CrawlProperties()
 {
-  nsIDocument* doc = mRoot->GetCurrentDoc();
+  nsIDocument* doc = mRoot->GetUncomposedDoc();
 
   const nsAttrValue* attr = mRoot->GetParsedAttr(nsGkAtoms::itemref);
   if (attr) {
     for (uint32_t i = 0; i < attr->GetAtomCount(); i++) {
       nsIAtom* ref = attr->AtomAt(i);
       Element* element;
       if (doc) {
         element = doc->GetElementById(nsDependentAtomString(ref));
@@ -301,17 +301,17 @@ HTMLPropertiesCollection::GetSupportedNa
 {
   EnsureFresh();
   mNames->CopyList(aNames);
 }
 
 PropertyNodeList::PropertyNodeList(HTMLPropertiesCollection* aCollection,
                                    nsIContent* aParent, const nsAString& aName)
   : mName(aName),
-    mDoc(aParent->GetCurrentDoc()),
+    mDoc(aParent->GetUncomposedDoc()),
     mCollection(aCollection),
     mParent(aParent),
     mIsDirty(true)
 {
   SetIsDOMBinding();
   if (mDoc) {
     mDoc->AddMutationObserver(this);
   }
--- a/content/html/content/src/HTMLSharedElement.cpp
+++ b/content/html/content/src/HTMLSharedElement.cpp
@@ -222,19 +222,19 @@ HTMLSharedElement::SetAttr(int32_t aName
   // If the href attribute of a <base> tag is changing, we may need to update
   // the document's base URI, which will cause all the links on the page to be
   // re-resolved given the new base.  If the target attribute is changing, we
   // similarly need to change the base target.
   if (mNodeInfo->Equals(nsGkAtoms::base) &&
       aNameSpaceID == kNameSpaceID_None &&
       IsInDoc()) {
     if (aName == nsGkAtoms::href) {
-      SetBaseURIUsingFirstBaseWithHref(GetCurrentDoc(), this);
+      SetBaseURIUsingFirstBaseWithHref(GetUncomposedDoc(), this);
     } else if (aName == nsGkAtoms::target) {
-      SetBaseTargetUsingFirstBaseWithTarget(GetCurrentDoc(), this);
+      SetBaseTargetUsingFirstBaseWithTarget(GetUncomposedDoc(), this);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 HTMLSharedElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aName,
@@ -245,19 +245,19 @@ HTMLSharedElement::UnsetAttr(int32_t aNa
 
   // If we're the first <base> with an href and our href attribute is being
   // unset, then we're no longer the first <base> with an href, and we need to
   // find the new one.  Similar for target.
   if (mNodeInfo->Equals(nsGkAtoms::base) &&
       aNameSpaceID == kNameSpaceID_None &&
       IsInDoc()) {
     if (aName == nsGkAtoms::href) {
-      SetBaseURIUsingFirstBaseWithHref(GetCurrentDoc(), nullptr);
+      SetBaseURIUsingFirstBaseWithHref(GetUncomposedDoc(), nullptr);
     } else if (aName == nsGkAtoms::target) {
-      SetBaseTargetUsingFirstBaseWithTarget(GetCurrentDoc(), nullptr);
+      SetBaseTargetUsingFirstBaseWithTarget(GetUncomposedDoc(), nullptr);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 HTMLSharedElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
@@ -282,17 +282,17 @@ HTMLSharedElement::BindToTree(nsIDocumen
   }
 
   return NS_OK;
 }
 
 void
 HTMLSharedElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
 
   nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
 
   // If we're removing a <base> from a document, we may need to update the
   // document's base URI and base target
   if (doc && mNodeInfo->Equals(nsGkAtoms::base)) {
     if (HasAttr(kNameSpaceID_None, nsGkAtoms::href)) {
       SetBaseURIUsingFirstBaseWithHref(doc, nullptr);
--- a/content/html/content/src/HTMLTableElement.cpp
+++ b/content/html/content/src/HTMLTableElement.cpp
@@ -891,17 +891,17 @@ HTMLTableElement::GetAttributesMappedFor
   return nullptr;
 }
 
 void
 HTMLTableElement::BuildInheritedAttributes()
 {
   NS_ASSERTION(mTableInheritedAttributes == TABLE_ATTRS_DIRTY,
                "potential leak, plus waste of work");
-  nsIDocument *document = GetCurrentDoc();
+  nsIDocument *document = GetComposedDoc();
   nsHTMLStyleSheet* sheet = document ?
                               document->GetAttributeStyleSheet() : nullptr;
   nsRefPtr<nsMappedAttributes> newAttrs;
   if (sheet) {
     const nsAttrValue* value = mAttrsAndChildren.GetAttr(nsGkAtoms::cellpadding);
     if (value) {
       nsRefPtr<nsMappedAttributes> modifiableMapped = new
       nsMappedAttributes(sheet, MapInheritedTableAttributesIntoRule);
--- a/content/html/content/src/HTMLTitleElement.cpp
+++ b/content/html/content/src/HTMLTitleElement.cpp
@@ -122,16 +122,16 @@ HTMLTitleElement::DoneAddingChildren(boo
   if (!aHaveNotified) {
     SendTitleChangeEvent(false);
   }
 }
 
 void
 HTMLTitleElement::SendTitleChangeEvent(bool aBound)
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   if (doc) {
     doc->NotifyPossibleTitleChange(aBound);
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/html/content/src/nsFormSubmission.cpp
+++ b/content/html/content/src/nsFormSubmission.cpp
@@ -815,17 +815,17 @@ GetEnumAttr(nsGenericHTMLElement* aConte
 }
 
 nsresult
 GetSubmissionFromForm(nsGenericHTMLElement* aForm,
                       nsGenericHTMLElement* aOriginatingElement,
                       nsFormSubmission** aFormSubmission)
 {
   // Get all the information necessary to encode the form data
-  NS_ASSERTION(aForm->GetCurrentDoc(),
+  NS_ASSERTION(aForm->GetComposedDoc(),
                "Should have doc if we're building submission!");
 
   // Get encoding type (default: urlencoded)
   int32_t enctype = NS_FORM_ENCTYPE_URLENCODED;
   if (aOriginatingElement &&
       aOriginatingElement->HasAttr(kNameSpaceID_None, nsGkAtoms::formenctype)) {
     GetEnumAttr(aOriginatingElement, nsGkAtoms::formenctype, &enctype);
   } else {
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -575,17 +575,18 @@ nsGenericHTMLElement::UnbindFromTree(boo
     if (properties) {
       properties->SetDocument(nullptr);
     }
   }
 
   RemoveFromNameTable();
 
   if (GetContentEditableValue() == eTrue) {
-    nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetCurrentDoc());
+    //XXXsmaug Fix this for Shadow DOM, bug 1066965.
+    nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetUncomposedDoc());
     if (htmlDocument) {
       htmlDocument->ChangeContentEditableCount(this, -1);
     }
   }
 
   nsStyledElement::UnbindFromTree(aDeep, aNullParent);
 }
 
@@ -775,17 +776,17 @@ nsGenericHTMLElement::GetEventListenerMa
 #undef EVENT
        )
       ) {
     nsPIDOMWindow *win;
 
     // If we have a document, and it has a window, add the event
     // listener on the window (the inner window). If not, proceed as
     // normal.
-    // XXXbz sXBL/XBL2 issue: should we instead use GetCurrentDoc() here,
+    // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here,
     // override BindToTree for those classes and munge event listeners there?
     nsIDocument *document = OwnerDoc();
 
     *aDefer = false;
     if ((win = document->GetInnerWindow())) {
       nsCOMPtr<EventTarget> piTarget(do_QueryInterface(win));
 
       return piTarget->GetOrCreateListenerManager();
@@ -1767,17 +1768,18 @@ nsGenericHTMLElement::GetURIListAttr(nsI
 }
 
 HTMLMenuElement*
 nsGenericHTMLElement::GetContextMenu() const
 {
   nsAutoString value;
   GetHTMLAttr(nsGkAtoms::contextmenu, value);
   if (!value.IsEmpty()) {
-    nsIDocument* doc = GetCurrentDoc();
+    //XXXsmaug How should this work in Shadow DOM?
+    nsIDocument* doc = GetUncomposedDoc();
     if (doc) {
       return HTMLMenuElement::FromContentOrNull(doc->GetElementById(value));
     }
   }
   return nullptr;
 }
 
 NS_IMETHODIMP
@@ -2025,17 +2027,17 @@ nsGenericHTMLFormElement::BindToTree(nsI
   }
 
   // If @form is set, the element *has* to be in a document, otherwise it
   // wouldn't be possible to find an element with the corresponding id.
   // If @form isn't set, the element *has* to have a parent, otherwise it
   // wouldn't be possible to find a form ancestor.
   // We should not call UpdateFormOwner if none of these conditions are
   // fulfilled.
-  if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? !!GetCurrentDoc()
+  if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? !!GetUncomposedDoc()
                                                   : !!aParent) {
     UpdateFormOwner(true, nullptr);
   }
 
   // Set parent fieldset which should be used for the disabled state.
   UpdateFieldSet(false);
 
   return NS_OK;
@@ -2177,17 +2179,18 @@ nsGenericHTMLFormElement::AfterSetAttr(i
       // Go ahead and notify on that change.
       // Note: no need to notify on CanBeDisabled(), since type attr
       // changes can't affect that.
       UpdateState(aNotify);
     }
 
     if (aName == nsGkAtoms::form) {
       // We need a new form id observer.
-      nsIDocument* doc = GetCurrentDoc();
+      //XXXsmaug How should this work in Shadow DOM?
+      nsIDocument* doc = GetUncomposedDoc();
       if (doc) {
         Element* formIdElement = nullptr;
         if (aValue && !aValue->IsEmptyString()) {
           formIdElement = AddFormIdObserver();
         }
 
         // Because we have a new @form value (or no more @form), we have to
         // update our form owner.
@@ -2343,18 +2346,18 @@ nsGenericHTMLFormElement::FocusState()
   }
 
   return eInactiveWindow;
 }
 
 Element*
 nsGenericHTMLFormElement::AddFormIdObserver()
 {
-  NS_ASSERTION(GetCurrentDoc(), "When adding a form id observer, "
-                                "we should be in a document!");
+  NS_ASSERTION(GetUncomposedDoc(), "When adding a form id observer, "
+                                   "we should be in a document!");
 
   nsAutoString formId;
   nsIDocument* doc = OwnerDoc();
   GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId);
   NS_ASSERTION(!formId.IsEmpty(),
                "@form value should not be the empty string!");
   nsCOMPtr<nsIAtom> atom = do_GetAtom(formId);
 
@@ -2364,18 +2367,18 @@ nsGenericHTMLFormElement::AddFormIdObser
 void
 nsGenericHTMLFormElement::RemoveFormIdObserver()
 {
   /**
    * We are using OwnerDoc() because we don't really care about having the
    * element actually being in the tree. If it is not and @form value changes,
    * this method will be called for nothing but removing an observer which does
    * not exist doesn't cost so much (no entry in the hash table) so having a
-   * boolean for GetCurrentDoc()/GetOwnerDoc() would make everything look more
-   * complex for nothing.
+   * boolean for GetUncomposedDoc()/GetOwnerDoc() would make everything look
+   * more complex for nothing.
    */
 
   nsIDocument* doc = OwnerDoc();
 
   // At this point, we may not have a document anymore. In that case, we can't
   // remove the observer. The document did that for us.
   if (!doc) {
     return;
@@ -2444,20 +2447,20 @@ nsGenericHTMLFormElement::UpdateFormOwne
         Element* element = nullptr;
 
         if (aBindToTree) {
           element = AddFormIdObserver();
         } else {
           element = aFormIdElement;
         }
 
-        NS_ASSERTION(GetCurrentDoc(), "The element should be in a document "
-                                      "when UpdateFormOwner is called!");
-        NS_ASSERTION(!GetCurrentDoc() ||
-                     element == GetCurrentDoc()->GetElementById(formId),
+        NS_ASSERTION(GetUncomposedDoc(), "The element should be in a document "
+                                         "when UpdateFormOwner is called!");
+        NS_ASSERTION(!GetUncomposedDoc() ||
+                     element == GetUncomposedDoc()->GetElementById(formId),
                      "element should be equals to the current element "
                      "associated with the id in @form!");
 
         if (element && element->IsHTML(nsGkAtoms::form)) {
           mForm = static_cast<HTMLFormElement*>(element);
         }
       }
      } else {
@@ -2760,17 +2763,17 @@ nsGenericHTMLElement::IsCurrentBodyEleme
 {
   // TODO Bug 698498: Should this handle the case where GetBody returns a
   //                  frameset?
   if (!IsHTML(nsGkAtoms::body)) {
     return false;
   }
 
   nsCOMPtr<nsIDOMHTMLDocument> htmlDocument =
-    do_QueryInterface(GetCurrentDoc());
+    do_QueryInterface(GetUncomposedDoc());
   if (!htmlDocument) {
     return false;
   }
 
   nsCOMPtr<nsIDOMHTMLElement> htmlElement;
   htmlDocument->GetBody(getter_AddRefs(htmlElement));
   return htmlElement == static_cast<HTMLBodyElement*>(this);
 }
@@ -2817,17 +2820,17 @@ nsGenericHTMLElement::RecompileScriptEve
         GetAttr(kNameSpaceID_None, attr, value);
         SetEventHandler(attr, value, true);
     }
 }
 
 bool
 nsGenericHTMLElement::IsEditableRoot() const
 {
-  nsIDocument *document = GetCurrentDoc();
+  nsIDocument *document = GetComposedDoc();
   if (!document) {
     return false;
   }
 
   if (document->HasFlag(NODE_IS_EDITABLE)) {
     return false;
   }
 
@@ -2863,17 +2866,18 @@ MakeContentDescendantsEditable(nsIConten
       MakeContentDescendantsEditable(child, aDocument);
     }
   }
 }
 
 void
 nsGenericHTMLElement::ChangeEditableState(int32_t aChange)
 {
-  nsIDocument* document = GetCurrentDoc();
+  //XXXsmaug Fix this for Shadow DOM, bug 1066965.
+  nsIDocument* document = GetUncomposedDoc();
   if (!document) {
     return;
   }
 
   if (aChange != 0) {
     nsCOMPtr<nsIHTMLDocument> htmlDocument =
       do_QueryInterface(document);
     if (htmlDocument) {
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -1051,17 +1051,17 @@ HTMLContentSink::CloseHeadContext()
   }
 }
 
 void
 HTMLContentSink::NotifyInsert(nsIContent* aContent,
                               nsIContent* aChildContent,
                               int32_t aIndexInContainer)
 {
-  if (aContent && aContent->GetCurrentDoc() != mDocument) {
+  if (aContent && aContent->GetUncomposedDoc() != mDocument) {
     // aContent is not actually in our document anymore.... Just bail out of
     // here; notifying on our document for this insert would be wrong.
     return;
   }
 
   mInNotification++;
 
   {
--- a/content/html/document/src/nsHTMLDocument.cpp
+++ b/content/html/document/src/nsHTMLDocument.cpp
@@ -1114,26 +1114,26 @@ nsHTMLDocument::Applets()
   }
   return mApplets;
 }
 
 bool
 nsHTMLDocument::MatchLinks(nsIContent *aContent, int32_t aNamespaceID,
                            nsIAtom* aAtom, void* aData)
 {
-  nsIDocument* doc = aContent->GetCurrentDoc();
+  nsIDocument* doc = aContent->GetUncomposedDoc();
 
   if (doc) {
     NS_ASSERTION(aContent->IsInDoc(),
                  "This method should never be called on content nodes that "
                  "are not in a document!");
 #ifdef DEBUG
     {
       nsCOMPtr<nsIHTMLDocument> htmldoc =
-        do_QueryInterface(aContent->GetCurrentDoc());
+        do_QueryInterface(aContent->GetUncomposedDoc());
       NS_ASSERTION(htmldoc,
                    "Huh, how did this happen? This should only be used with "
                    "HTML documents!");
     }
 #endif
 
     mozilla::dom::NodeInfo *ni = aContent->NodeInfo();
 
@@ -1168,17 +1168,17 @@ nsHTMLDocument::MatchAnchors(nsIContent 
                              nsIAtom* aAtom, void* aData)
 {
   NS_ASSERTION(aContent->IsInDoc(),
                "This method should never be called on content nodes that "
                "are not in a document!");
 #ifdef DEBUG
   {
     nsCOMPtr<nsIHTMLDocument> htmldoc =
-      do_QueryInterface(aContent->GetCurrentDoc());
+      do_QueryInterface(aContent->GetUncomposedDoc());
     NS_ASSERTION(htmldoc,
                  "Huh, how did this happen? This should only be used with "
                  "HTML documents!");
   }
 #endif
 
   if (aContent->NodeInfo()->Equals(nsGkAtoms::a, kNameSpaceID_XHTML)) {
     return aContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name);
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -448,16 +448,29 @@ DOMMediaStream::ConstructMediaTracks(Aud
     // enable, the one that is listed first in the element's videoTracks object
     // must be selected.
     int index = firstEnabledVideo >= 0 ? firstEnabledVideo : 0;
     (*aVideoTrackList)[index]->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
   }
 }
 
 void
+DOMMediaStream::DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
+                                             const VideoTrackList* aVideoTrackList)
+{
+  for (auto i = mMediaTrackListListeners.Length(); i > 0; ) { // unsigned!
+    --i; // 0 ... Length()-1 range
+    if (mMediaTrackListListeners[i].mMediaTrackList == aAudioTrackList ||
+        mMediaTrackListListeners[i].mMediaTrackList == aVideoTrackList) {
+      mMediaTrackListListeners.RemoveElementAt(i);
+    }
+  }
+}
+
+void
 DOMMediaStream::NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack)
 {
   for (uint32_t i = 0; i < mMediaTrackListListeners.Length(); ++i) {
     if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
       nsRefPtr<AudioTrack> track = CreateAudioTrack(t);
       mMediaTrackListListeners[i].NotifyMediaTrackCreated(track);
     } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
       nsRefPtr<VideoTrack> track = CreateVideoTrack(t);
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -237,16 +237,22 @@ public:
   /**
    * If loading and playing a MediaStream in a media element, for each
    * MediaStreamTrack in the MediaStream, create a corresponding AudioTrack or
    * VideoTrack during the phase of resource fetching.
    */
   void ConstructMediaTracks(AudioTrackList* aAudioTrackList,
                             VideoTrackList* aVideoTrackList);
 
+  /**
+   * MUST call this before the AudioTrackList or VideoTrackList go away
+   */
+  void DisconnectTrackListListeners(const AudioTrackList* aAudioTrackList,
+                                    const VideoTrackList* aVideoTrackList);
+
   virtual void NotifyMediaStreamTrackCreated(MediaStreamTrack* aTrack);
 
   virtual void NotifyMediaStreamTrackEnded(MediaStreamTrack* aTrack);
 
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
--- a/content/media/MediaDecoderOwner.h
+++ b/content/media/MediaDecoderOwner.h
@@ -109,19 +109,16 @@ public:
     NEXT_FRAME_AVAILABLE,
     // The next frame of audio/video is unavailable because the decoder
     // is paused while it buffers up data
     NEXT_FRAME_UNAVAILABLE_BUFFERING,
     // The next frame of audio/video is unavailable for the decoder is seeking.
     NEXT_FRAME_UNAVAILABLE_SEEKING,
     // The next frame of audio/video is unavailable for some other reasons
     NEXT_FRAME_UNAVAILABLE,
-    // The next frame is unavailable due to waiting for more Media Source
-    // Extensions data to become available.
-    NEXT_FRAME_WAIT_FOR_MSE_DATA,
     // Sentinel value
     NEXT_FRAME_UNINITIALIZED
   };
 
   // Called by the decoder when some data has been downloaded or
   // buffering/seeking has ended. aNextFrameAvailable is true when
   // the data for the next frame is available. This method will
   // decide whether to set the ready state to HAVE_CURRENT_DATA,
--- a/content/media/MediaTrackList.h
+++ b/content/media/MediaTrackList.h
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_MediaTrackList_h
 #define mozilla_dom_MediaTrackList_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 
 namespace mozilla {
+class DOMMediaStream;
+
 namespace dom {
 
 class HTMLMediaElement;
 class MediaTrack;
 class AudioTrackList;
 class VideoTrackList;
 class AudioTrack;
 class VideoTrack;
@@ -23,16 +25,18 @@ class MediaTrackList;
 /**
  * This is for the media resource to notify its audio track and video track,
  * when a media-resource-specific track has ended, or whether it has enabled or
  * not. All notification methods are called from the main thread.
  */
 class MediaTrackListListener
 {
 public:
+  friend class mozilla::DOMMediaStream;
+
   explicit MediaTrackListListener(MediaTrackList* aMediaTrackList)
     : mMediaTrackList(aMediaTrackList) {};
 
   ~MediaTrackListListener()
   {
     mMediaTrackList = nullptr;
   };
 
--- a/content/media/mediasource/MediaSourceReader.cpp
+++ b/content/media/mediasource/MediaSourceReader.cpp
@@ -366,35 +366,16 @@ MediaSourceReader::OnTrackBufferConfigur
   }
   if (aInfo.HasVideo() && !mVideoTrack) {
     MSE_DEBUG("MediaSourceReader(%p)::OnTrackBufferConfigured %p video", this, aTrackBuffer);
     mVideoTrack = aTrackBuffer;
   }
   mDecoder->NotifyWaitingForResourcesStatusChanged();
 }
 
-class ChangeToHaveMetadata : public nsRunnable {
-public:
-  explicit ChangeToHaveMetadata(AbstractMediaDecoder* aDecoder) :
-    mDecoder(aDecoder)
-  {
-  }
-
-  NS_IMETHOD Run() MOZ_OVERRIDE MOZ_FINAL {
-    auto owner = mDecoder->GetOwner();
-    if (owner) {
-      owner->UpdateReadyStateForData(MediaDecoderOwner::NEXT_FRAME_WAIT_FOR_MSE_DATA);
-    }
-    return NS_OK;
-  }
-
-private:
-  nsRefPtr<AbstractMediaDecoder> mDecoder;
-};
-
 void
 MediaSourceReader::WaitForTimeRange(int64_t aTime)
 {
   MSE_DEBUG("MediaSourceReader(%p)::WaitForTimeRange(%lld)", this, aTime);
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   // Loop until we have the requested time range in the active TrackBuffers.
   // Ideally, this wait loop would use an async request and callback
@@ -429,21 +410,16 @@ MediaSourceReader::Seek(int64_t aTime, i
   for (uint32_t i = 0; i < mTrackBuffers.Length(); ++i) {
     mTrackBuffers[i]->ResetDecode();
   }
 
   // Decoding discontinuity upon seek, reset last times to seek target.
   mLastAudioTime = aTime;
   mLastVideoTime = aTime;
 
-  if (!TrackBuffersContainTime(aTime)) {
-    MSE_DEBUG("MediaSourceReader(%p)::Seek no active buffer contains target=%lld", this, aTime);
-    NS_DispatchToMainThread(new ChangeToHaveMetadata(mDecoder));
-  }
-
   WaitForTimeRange(aTime);
 
   if (IsShutdown()) {
     return NS_ERROR_FAILURE;
   }
 
   if (mAudioTrack) {
     mAudioIsSeeking = true;
--- a/content/media/mediasource/TrackBuffer.cpp
+++ b/content/media/mediasource/TrackBuffer.cpp
@@ -48,17 +48,17 @@ TrackBuffer::TrackBuffer(MediaSourceDeco
 
 TrackBuffer::~TrackBuffer()
 {
   MOZ_COUNT_DTOR(TrackBuffer);
 }
 
 class ReleaseDecoderTask : public nsRunnable {
 public:
-  explicit ReleaseDecoderTask(nsRefPtr<SourceBufferDecoder> aDecoder)
+  explicit ReleaseDecoderTask(SourceBufferDecoder* aDecoder)
   {
     mDecoders.AppendElement(aDecoder);
   }
 
   explicit ReleaseDecoderTask(nsTArray<nsRefPtr<SourceBufferDecoder>>& aDecoders)
   {
     mDecoders.SwapElements(aDecoders);
   }
@@ -232,50 +232,53 @@ TrackBuffer::NewDecoder()
 
   mLastStartTimestamp = 0;
   mLastEndTimestamp = 0;
 
   return QueueInitializeDecoder(decoder);
 }
 
 bool
-TrackBuffer::QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
+TrackBuffer::QueueInitializeDecoder(SourceBufferDecoder* aDecoder)
 {
   RefPtr<nsIRunnable> task =
-    NS_NewRunnableMethodWithArg<nsRefPtr<SourceBufferDecoder>>(this,
-                                                               &TrackBuffer::InitializeDecoder,
-                                                               aDecoder);
+    NS_NewRunnableMethodWithArg<SourceBufferDecoder*>(this,
+                                                      &TrackBuffer::InitializeDecoder,
+                                                      aDecoder);
   aDecoder->SetTaskQueue(mTaskQueue);
   if (NS_FAILED(mTaskQueue->Dispatch(task))) {
     MSE_DEBUG("MediaSourceReader(%p): Failed to enqueue decoder initialization task", this);
+    RemoveDecoder(aDecoder);
     return false;
   }
   return true;
 }
 
 void
-TrackBuffer::InitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
+TrackBuffer::InitializeDecoder(SourceBufferDecoder* aDecoder)
 {
+  MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+
   // ReadMetadata may block the thread waiting on data, so it must not be
   // called with the monitor held.
   mParentDecoder->GetReentrantMonitor().AssertNotCurrentThreadIn();
 
   MediaDecoderReader* reader = aDecoder->GetReader();
   MSE_DEBUG("TrackBuffer(%p): Initializing subdecoder %p reader %p",
-            this, aDecoder.get(), reader);
+            this, aDecoder, reader);
 
   MediaInfo mi;
   nsAutoPtr<MetadataTags> tags; // TODO: Handle metadata.
   nsresult rv = reader->ReadMetadata(&mi, getter_Transfers(tags));
+  aDecoder->SetTaskQueue(nullptr);
   reader->SetIdle();
   if (NS_FAILED(rv) || (!mi.HasVideo() && !mi.HasAudio())) {
     // XXX: Need to signal error back to owning SourceBuffer.
     MSE_DEBUG("TrackBuffer(%p): Reader %p failed to initialize rv=%x audio=%d video=%d",
               this, reader, rv, mi.HasAudio(), mi.HasVideo());
-    aDecoder->SetTaskQueue(nullptr);
     RemoveDecoder(aDecoder);
     return;
   }
 
   if (mi.HasVideo()) {
     MSE_DEBUG("TrackBuffer(%p): Reader %p video resolution=%dx%d",
               this, reader, mi.mVideo.mDisplay.width, mi.mVideo.mDisplay.height);
   }
@@ -309,20 +312,19 @@ TrackBuffer::ValidateTrackFormats(const 
     MSE_DEBUG("TrackBuffer(%p)::ValidateTrackFormats audio format mismatch", this);
     return false;
   }
 
   return true;
 }
 
 bool
-TrackBuffer::RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
+TrackBuffer::RegisterDecoder(SourceBufferDecoder* aDecoder)
 {
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  aDecoder->SetTaskQueue(nullptr);
   const MediaInfo& info = aDecoder->GetReader()->GetMediaInfo();
   // Initialize the track info since this is the first decoder.
   if (mInitializedDecoders.IsEmpty()) {
     mInfo = info;
     mParentDecoder->OnTrackBufferConfigured(this, mInfo);
   }
   if (!ValidateTrackFormats(info)) {
     MSE_DEBUG("TrackBuffer(%p)::RegisterDecoder with mismatched audio/video tracks", this);
@@ -423,17 +425,24 @@ TrackBuffer::Dump(const char* aPath)
     PR_MkDir(buf, 0700);
 
     mDecoders[i]->GetResource()->Dump(buf);
   }
 }
 #endif
 
 void
-TrackBuffer::RemoveDecoder(nsRefPtr<SourceBufferDecoder> aDecoder)
+TrackBuffer::RemoveDecoder(SourceBufferDecoder* aDecoder)
 {
-  ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
-  MOZ_ASSERT(!mInitializedDecoders.Contains(aDecoder));
-  mDecoders.RemoveElement(aDecoder);
-  NS_DispatchToMainThread(new ReleaseDecoderTask(aDecoder));
+  RefPtr<nsIRunnable> task = new ReleaseDecoderTask(aDecoder);
+  {
+    ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
+    MOZ_ASSERT(!mInitializedDecoders.Contains(aDecoder));
+    mDecoders.RemoveElement(aDecoder);
+    if (mCurrentDecoder == aDecoder) {
+      DiscardDecoder();
+    }
+  }
+  // At this point, task should be holding the only reference to aDecoder.
+  NS_DispatchToMainThread(task);
 }
 
 } // namespace mozilla
--- a/content/media/mediasource/TrackBuffer.h
+++ b/content/media/mediasource/TrackBuffer.h
@@ -85,37 +85,37 @@ private:
   // initialized until it is added to mDecoders.
   bool NewDecoder();
 
   // Helper for AppendData, ensures NotifyDataArrived is called whenever
   // data is appended to the current decoder's SourceBufferResource.
   bool AppendDataToCurrentResource(const uint8_t* aData, uint32_t aLength);
 
   // Queue execution of InitializeDecoder on mTaskQueue.
-  bool QueueInitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
+  bool QueueInitializeDecoder(SourceBufferDecoder* aDecoder);
 
   // Runs decoder initialization including calling ReadMetadata.  Runs as an
   // event on the decode thread pool.
-  void InitializeDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
+  void InitializeDecoder(SourceBufferDecoder* aDecoder);
 
   // Adds a successfully initialized decoder to mDecoders and (if it's the
   // first decoder initialized), initializes mHasAudio/mHasVideo.  Called
   // from the decode thread pool.  Return true if the decoder was
   // successfully registered.
-  bool RegisterDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
+  bool RegisterDecoder(SourceBufferDecoder* aDecoder);
 
   // Returns true if aInfo is considered a supported or the same format as
   // the TrackBuffer was initialized as.
   bool ValidateTrackFormats(const MediaInfo& aInfo);
 
   // Remove aDecoder from mDecoders and dispatch an event to the main thread
   // to clean up the decoder.  If aDecoder was added to
   // mInitializedDecoders, it must have been removed before calling this
   // function.
-  void RemoveDecoder(nsRefPtr<SourceBufferDecoder> aDecoder);
+  void RemoveDecoder(SourceBufferDecoder* aDecoder);
 
   nsAutoPtr<ContainerParser> mParser;
 
   // A task queue using the shared media thread pool.  Used exclusively to
   // initialize (i.e. call ReadMetadata on) decoders as they are created via
   // NewDecoder.
   RefPtr<MediaTaskQueue> mTaskQueue;
 
--- a/content/media/mediasource/test/mochitest.ini
+++ b/content/media/mediasource/test/mochitest.ini
@@ -1,17 +1,18 @@
 [DEFAULT]
 skip-if = e10s || buildapp == 'b2g' # b2g( ReferenceError: MediaSource is not defined)
 support-files =
   mediasource.js
   seek.webm seek.webm^headers^
   seek_lowres.webm seek_lowres.webm^headers^
 
+[test_MediaSource.html]
+[test_MediaSource_disabled.html]
 [test_BufferedSeek.html]
 [test_FrameSelection.html]
-[test_MediaSource.html]
-[test_MediaSource_disabled.html]
+[test_HaveMetadataUnbufferedSeek.html]
 [test_SeekableAfterEndOfStream.html]
 [test_SeekableAfterEndOfStreamSplit.html]
 [test_SeekableBeforeEndOfStream.html]
 [test_SeekableBeforeEndOfStreamSplit.html]
 [test_SplitAppendDelay.html]
 [test_SplitAppend.html]
--- a/content/media/mediasource/test/test_BufferedSeek.html
+++ b/content/media/mediasource/test/test_BufferedSeek.html
@@ -33,22 +33,22 @@ runWithMSE(function (ms, v) {
         v.currentTime = target;
       }
     });
 
     var wasSeeking = false;
 
     v.addEventListener("seeking", function () {
       wasSeeking = true;
-      is(v.currentTime, target, "Video currentTime not at target");
+      is(v.currentTime, target, "Video currentTime at target");
     });
 
     v.addEventListener("seeked", function () {
       ok(wasSeeking, "Received expected seeking and seeked events");
-      is(v.currentTime, target, "Video currentTime not at target");
+      is(v.currentTime, target, "Video currentTime at target");
       SimpleTest.finish();
     });
   });
 });
 
 </script>
 </pre>
 </body>
--- a/content/media/mediasource/test/test_FrameSelection.html
+++ b/content/media/mediasource/test/test_FrameSelection.html
@@ -22,19 +22,19 @@ runWithMSE(function (ms, v) {
     });
 
     var targets = [{ currentTime: 3, videoWidth: 160, videoHeight: 120 },
                    { currentTime: 2, videoWidth: 160, videoHeight: 120 },
                    { currentTime: 0, videoWidth: 320, videoHeight: 240 }];
     var target;
 
     v.addEventListener("loadedmetadata", function () {
-      is(v.currentTime, 0, "currentTime has incorrect initial value");
-      is(v.videoWidth, 320, "videoWidth has incorrect initial value");
-      is(v.videoHeight, 240, "videoHeight has incorrect initial value");
+      is(v.currentTime, 0, "currentTime has correct initial value");
+      is(v.videoWidth, 320, "videoWidth has correct initial value");
+      is(v.videoHeight, 240, "videoHeight has correct initial value");
 
       fetchWithXHR("seek_lowres.webm", function (arrayBuffer) {
         // Append initialization segment.
         sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 438));
         var first = true;
         sb.addEventListener("updateend", function () {
           if (first) {
             // Append media segment covering range [2, 4].
@@ -45,20 +45,20 @@ runWithMSE(function (ms, v) {
             target = targets.shift();
             v.currentTime = target.currentTime;
           }
         });
       });
     });
 
     v.addEventListener("seeked", function () {
-      is(v.currentTime, target.currentTime, "Video currentTime not at target");
+      is(v.currentTime, target.currentTime, "Video currentTime at target");
 
-      is(v.videoWidth, target.videoWidth, "videoWidth has incorrect final value");
-      is(v.videoHeight, target.videoHeight, "videoHeight has incorrect final value");
+      is(v.videoWidth, target.videoWidth, "videoWidth has correct final value");
+      is(v.videoHeight, target.videoHeight, "videoHeight has correct final value");
 
       target = targets.shift();
       if (target) {
         v.currentTime = target.currentTime;
       } else {
         SimpleTest.finish();
       }
     });
new file mode 100644
--- /dev/null
+++ b/content/media/mediasource/test/test_HaveMetadataUnbufferedSeek.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>MSE: seekable attribute before end of stream</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="mediasource.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+runWithMSE(function (ms, v) {
+  ms.addEventListener("sourceopen", function () {
+    var sb = ms.addSourceBuffer("video/webm");
+
+    fetchWithXHR("seek.webm", function (arrayBuffer) {
+      sb.appendBuffer(new Uint8Array(arrayBuffer, 0, 67833));
+    });
+
+    var target = 2;
+
+    v.addEventListener("canplay", function oncanplay() {
+      v.removeEventListener("canplay", oncanplay);
+      ok(v.readyState >= v.HAVE_FUTURE_DATA, "readyState is >= FUTURE_DATA");
+      v.currentTime = target;
+    });
+
+    v.addEventListener("seeking", function () {
+      is(v.readyState, v.HAVE_METADATA, "readyState is HAVE_METADATA");
+      fetchWithXHR("seek.webm", function (arrayBuffer) {
+        sb.appendBuffer(new Uint8Array(arrayBuffer, 67833));
+      });
+    });
+
+    v.addEventListener("seeked", function () {
+      ok(v.readyState >= v.HAVE_FUTURE_DATA, "readyState is >= FUTURE_DATA");
+      SimpleTest.finish();
+    });
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/svg/content/src/DOMSVGPathSeg.cpp
+++ b/content/svg/content/src/DOMSVGPathSeg.cpp
@@ -175,17 +175,16 @@ DOMSVGPathSeg::IndexIsValid()
       return;                                                                 \
     }                                                                         \
     if (HasOwner()) {                                                         \
       if (InternalItem()[1+index] == float(a##propName)) {                    \
         return;                                                               \
       }                                                                       \
       AutoChangePathSegNotifier notifier(this);                               \
       InternalItem()[1+index] = float(a##propName);                           \
-      InvalidateCachedList();                                                 \
     } else {                                                                  \
       mArgs[index] = float(a##propName);                                      \
     }                                                                         \
   }
 
 // For float, the normal type of arguments
 #define IMPL_FLOAT_PROP(segName, propName, index) \
   IMPL_PROP_WITH_TYPE(segName, propName, index, float)
--- a/content/svg/content/src/DOMSVGPathSeg.h
+++ b/content/svg/content/src/DOMSVGPathSeg.h
@@ -204,20 +204,16 @@ protected:
    *
    * To simplify the code we just have this one method for obtaining both
    * baseVal and animVal internal items. This means that animVal items don't
    * get const protection, but then our setter methods guard against changing
    * animVal items.
    */
   float* InternalItem();
 
-  void InvalidateCachedList() {
-    mList->InternalList().mCachedPath = nullptr;
-  }
-
   virtual float* PtrToMemberArgs() = 0;
 
 #ifdef DEBUG
   bool IndexIsValid();
 #endif
 
   nsRefPtr<DOMSVGPathSegList> mList;
 
--- a/content/svg/content/src/DOMSVGPathSegList.cpp
+++ b/content/svg/content/src/DOMSVGPathSegList.cpp
@@ -378,17 +378,16 @@ DOMSVGPathSegList::InsertItemBefore(DOMS
   AutoChangePathSegListNotifier notifier(this);
   // Now that we know we're inserting, keep animVal list in sync as necessary.
   MaybeInsertNullInAnimValListAt(aIndex, internalIndex, argCount);
 
   float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
   domItem->ToSVGPathSegEncodedData(segAsRaw);
 
   InternalList().mData.InsertElementsAt(internalIndex, segAsRaw, 1 + argCount);
-  InternalList().mCachedPath = nullptr;
   mItems.InsertElementAt(aIndex, ItemProxy(domItem.get(), internalIndex));
 
   // This MUST come after the insertion into InternalList(), or else under the
   // insertion into InternalList() the values read from domItem would be bad
   // data from InternalList() itself!:
   domItem->InsertingIntoList(this, aIndex, IsAnimValList());
 
   UpdateListIndicesFromIndex(aIndex + 1, argCount + 1);
@@ -435,17 +434,16 @@ DOMSVGPathSegList::ReplaceItem(DOMSVGPat
   int32_t newArgCount = SVGPathSegUtils::ArgCountForType(domItem->Type());
 
   float segAsRaw[1 + NS_SVG_PATH_SEG_MAX_ARGS];
   domItem->ToSVGPathSegEncodedData(segAsRaw);
 
   bool ok = !!InternalList().mData.ReplaceElementsAt(
                   internalIndex, 1 + oldArgCount,
                   segAsRaw, 1 + newArgCount);
-  InternalList().mCachedPath = nullptr;
   if (!ok) {
     aError.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
   ItemAt(aIndex) = domItem;
 
   // This MUST come after the ToSVGPathSegEncodedData call, otherwise that call
   // would end up reading bad data from InternalList()!
@@ -490,17 +488,16 @@ DOMSVGPathSegList::RemoveItem(uint32_t a
   int32_t argCount = SVGPathSegUtils::ArgCountForType(segType);
 
   // Now that we know we're removing, keep animVal list in sync as necessary.
   // Do this *before* touching InternalList() so the removed item can get its
   // internal value.
   MaybeRemoveItemFromAnimValListAt(aIndex, argCount);
 
   InternalList().mData.RemoveElementsAt(internalIndex, 1 + argCount);
-  InternalList().mCachedPath = nullptr;
   mItems.RemoveElementAt(aIndex);
 
   UpdateListIndicesFromIndex(aIndex, -(argCount + 1));
 
   return result.forget();
 }
 
 already_AddRefed<DOMSVGPathSeg>
--- a/content/svg/content/src/SVGAElement.cpp
+++ b/content/svg/content/src/SVGAElement.cpp
@@ -129,31 +129,34 @@ SVGAElement::BindToTree(nsIDocument *aDo
 {
   Link::ResetLinkState(false, Link::ElementHasHref());
 
   nsresult rv = SVGAElementBase::BindToTree(aDocument, aParent,
                                             aBindingParent,
                                             aCompileEventHandlers);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (aDocument) {
-    aDocument->RegisterPendingLinkUpdate(this);
+  nsIDocument* doc = GetComposedDoc();
+  if (doc) {
+    doc->RegisterPendingLinkUpdate(this);
   }
 
   return NS_OK;
 }
 
 void
 SVGAElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // If this link is ever reinserted into a document, it might
   // be under a different xml:base, so forget the cached state now.
   Link::ResetLinkState(false, Link::ElementHasHref());
 
-  nsIDocument* doc = GetCurrentDoc();
+  // Note, we need to use OwnerDoc() here since GetComposedDoc() may
+  // return null already at this point.
+  nsIDocument* doc = OwnerDoc();
   if (doc) {
     doc->UnregisterPendingLinkUpdate(this);
   }
 
   SVGAElementBase::UnbindFromTree(aDeep, aNullParent);
 }
 
 already_AddRefed<nsIURI>
--- a/content/svg/content/src/SVGAnimationElement.cpp
+++ b/content/svg/content/src/SVGAnimationElement.cpp
@@ -216,17 +216,17 @@ SVGAnimationElement::BindToTree(nsIDocum
     }
     const nsAttrValue* href = mAttrsAndChildren.GetAttr(nsGkAtoms::href,
                                                         kNameSpaceID_XLink);
     if (href) {
       nsAutoString hrefStr;
       href->ToString(hrefStr);
 
       // Pass in |aParent| instead of |this| -- first argument is only used
-      // for a call to GetCurrentDoc(), and |this| might not have a current
+      // for a call to GetComposedDoc(), and |this| might not have a current
       // document yet.
       UpdateHrefTarget(aParent, hrefStr);
     }
 
     mTimedElement.BindToTree(aParent);
   }
 
   AnimationNeedsResample();
--- a/content/svg/content/src/SVGCircleElement.cpp
+++ b/content/svg/content/src/SVGCircleElement.cpp
@@ -86,17 +86,15 @@ SVGCircleElement::BuildPath(PathBuilder*
 {
   float x, y, r;
   GetAnimatedLengthValues(&x, &y, &r, nullptr);
 
   if (r <= 0.0f) {
     return nullptr;
   }
 
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
+  aBuilder->Arc(Point(x, y), r, 0, Float(2*M_PI));
 
-  pathBuilder->Arc(Point(x, y), r, 0, Float(2*M_PI));
-
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGCircleElement.h
+++ b/content/svg/content/src/SVGCircleElement.h
@@ -25,17 +25,17 @@ protected:
   friend nsresult (::NS_NewSVGCircleElement(nsIContent **aResult,
                                             already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
 public:
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
-  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> Cx();
   already_AddRefed<SVGAnimatedLength> Cy();
   already_AddRefed<SVGAnimatedLength> R();
 
--- a/content/svg/content/src/SVGContentUtils.cpp
+++ b/content/svg/content/src/SVGContentUtils.cpp
@@ -421,17 +421,17 @@ GetCTMInternal(nsSVGElement *aElement, b
   }
   if (ancestor->IsSVG()) {
     return
       gfx::ToMatrix(matrix) * GetCTMInternal(static_cast<nsSVGElement*>(ancestor), true, true);
   }
 
   // XXX this does not take into account CSS transform, or that the non-SVG
   // content that we've hit may itself be inside an SVG foreignObject higher up
-  nsIDocument* currentDoc = aElement->GetCurrentDoc();
+  nsIDocument* currentDoc = aElement->GetComposedDoc();
   float x = 0.0f, y = 0.0f;
   if (currentDoc && element->NodeInfo()->Equals(nsGkAtoms::svg, kNameSpaceID_SVG)) {
     nsIPresShell *presShell = currentDoc->GetShell();
     if (presShell) {
       nsIFrame* frame = element->GetPrimaryFrame();
       nsIFrame* ancestorFrame = presShell->GetRootFrame();
       if (frame && ancestorFrame) {
         nsPoint point = frame->GetOffsetTo(ancestorFrame);
--- a/content/svg/content/src/SVGEllipseElement.cpp
+++ b/content/svg/content/src/SVGEllipseElement.cpp
@@ -97,17 +97,15 @@ SVGEllipseElement::BuildPath(PathBuilder
 {
   float x, y, rx, ry;
   GetAnimatedLengthValues(&x, &y, &rx, &ry, nullptr);
 
   if (rx <= 0.0f || ry <= 0.0f) {
     return nullptr;
   }
 
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
+  EllipseToBezier(aBuilder, Point(x, y), Size(rx, ry));
 
-  EllipseToBezier(pathBuilder.get(), Point(x, y), Size(rx, ry));
-
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGEllipseElement.h
+++ b/content/svg/content/src/SVGEllipseElement.h
@@ -25,17 +25,17 @@ protected:
   friend nsresult (::NS_NewSVGEllipseElement(nsIContent **aResult,
                                              already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo));
 
 public:
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
-  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> Cx();
   already_AddRefed<SVGAnimatedLength> Cy();
   already_AddRefed<SVGAnimatedLength> Rx();
   already_AddRefed<SVGAnimatedLength> Ry();
--- a/content/svg/content/src/SVGImageElement.cpp
+++ b/content/svg/content/src/SVGImageElement.cpp
@@ -234,26 +234,24 @@ SVGImageElement::BuildPath(PathBuilder* 
 
   float x, y, width, height;
   GetAnimatedLengthValues(&x, &y, &width, &height, nullptr);
 
   if (width <= 0 || height <= 0) {
     return nullptr;
   }
 
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
+  Rect r(x, y, width, height);
+  aBuilder->MoveTo(r.TopLeft());
+  aBuilder->LineTo(r.TopRight());
+  aBuilder->LineTo(r.BottomRight());
+  aBuilder->LineTo(r.BottomLeft());
+  aBuilder->Close();
 
-  Rect r(x, y, width, height);
-  pathBuilder->MoveTo(r.TopLeft());
-  pathBuilder->LineTo(r.TopRight());
-  pathBuilder->LineTo(r.BottomRight());
-  pathBuilder->LineTo(r.BottomLeft());
-  pathBuilder->Close();
-
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
 
 //----------------------------------------------------------------------
 // nsSVGElement methods
 
 /* virtual */ bool
 SVGImageElement::HasValidDimensions() const
 {
--- a/content/svg/content/src/SVGImageElement.h
+++ b/content/svg/content/src/SVGImageElement.h
@@ -48,17 +48,17 @@ public:
                               bool aCompileEventHandlers) MOZ_OVERRIDE;
   virtual void UnbindFromTree(bool aDeep, bool aNullParent) MOZ_OVERRIDE;
 
   virtual EventStates IntrinsicState() const MOZ_OVERRIDE;
 
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* name) const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
-  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   nsresult CopyInnerTo(mozilla::dom::Element* aDest);
 
--- a/content/svg/content/src/SVGLineElement.cpp
+++ b/content/svg/content/src/SVGLineElement.cpp
@@ -104,21 +104,19 @@ SVGLineElement::GetMarkPoints(nsTArray<n
 
   aMarks->AppendElement(nsSVGMark(x1, y1, angle, nsSVGMark::eStart));
   aMarks->AppendElement(nsSVGMark(x2, y2, angle, nsSVGMark::eEnd));
 }
 
 TemporaryRef<Path>
 SVGLineElement::BuildPath(PathBuilder* aBuilder)
 {
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
-
   float x1, y1, x2, y2;
   GetAnimatedLengthValues(&x1, &y1, &x2, &y2, nullptr);
 
-  pathBuilder->MoveTo(Point(x1, y1));
-  pathBuilder->LineTo(Point(x2, y2));
+  aBuilder->MoveTo(Point(x1, y1));
+  aBuilder->LineTo(Point(x2, y2));
 
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGLineElement.h
+++ b/content/svg/content/src/SVGLineElement.h
@@ -27,17 +27,17 @@ protected:
 
 public:
   // nsIContent interface
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* name) const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
   virtual bool IsMarkable() MOZ_OVERRIDE { return true; }
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks) MOZ_OVERRIDE;
-  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const;
 
   // WebIDL
   already_AddRefed<SVGAnimatedLength> X1();
   already_AddRefed<SVGAnimatedLength> Y1();
   already_AddRefed<SVGAnimatedLength> X2();
   already_AddRefed<SVGAnimatedLength> Y2();
--- a/content/svg/content/src/SVGMPathElement.cpp
+++ b/content/svg/content/src/SVGMPathElement.cpp
@@ -208,17 +208,17 @@ SVGMPathElement::UpdateHrefTarget(nsICon
 
   // Stop observing old target (if any)
   if (mHrefTarget.get()) {
     mHrefTarget.get()->RemoveMutationObserver(this);
   }
 
   if (aParent) {
     // Pass in |aParent| instead of |this| -- first argument is only used
-    // for a call to GetCurrentDoc(), and |this| might not have a current
+    // for a call to GetComposedDoc(), and |this| might not have a current
     // document yet (if our caller is BindToTree).
     mHrefTarget.Reset(aParent, targetURI);
   } else {
     // if we don't have a parent, then there's no animateMotion element
     // depending on our target, so there's no point tracking it right now.
     mHrefTarget.Unlink();
   }
 
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
+++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
@@ -222,17 +222,17 @@ SVGMotionSMILAnimationFunction::
   if (pathElem) {
     const SVGPathData &path = pathElem->GetAnimPathSegList()->GetAnimValue();
     // Path data must contain of at least one path segment (if the path data
     // doesn't begin with a valid "M", then it's invalid).
     if (path.Length()) {
       bool ok =
         path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
       if (ok && mPathVertices.Length()) {
-        mPath = pathElem->GetPathForLengthOrPositionMeasuring();
+        mPath = pathElem->GetOrBuildPathForMeasuring();
       }
     }
   }
 }
 
 void
 SVGMotionSMILAnimationFunction::RebuildPathAndVerticesFromPathAttr()
 {
@@ -247,17 +247,17 @@ SVGMotionSMILAnimationFunction::RebuildP
   // accept all segments up to the first invalid token. Instead we must
   // explicitly check that the parse produces at least one path segment (if
   // the path data doesn't begin with a valid "M", then it's invalid).
   pathParser.Parse();
   if (!path.Length()) {
     return;
   }
 
-  mPath = path.ToPathForLengthOrPositionMeasuring();
+  mPath = path.BuildPathForMeasuring();
   bool ok = path.GetDistancesFromOriginToEndsOfVisibleSegments(&mPathVertices);
   if (!ok || !mPathVertices.Length()) {
     mPath = nullptr;
   }
 }
 
 // Helper to regenerate our path representation & its list of vertices
 void
--- a/content/svg/content/src/SVGPathData.cpp
+++ b/content/svg/content/src/SVGPathData.cpp
@@ -32,17 +32,16 @@ static bool IsMoveto(uint16_t aSegType)
 
 nsresult
 SVGPathData::CopyFrom(const SVGPathData& rhs)
 {
   if (!mData.SetCapacity(rhs.mData.Length())) {
     // Yes, we do want fallible alloc here
     return NS_ERROR_OUT_OF_MEMORY;
   }
-  mCachedPath = nullptr;
   mData = rhs.mData;
   return NS_OK;
 }
 
 void
 SVGPathData::GetValueAsString(nsAString& aValue) const
 {
   // we need this function in DidChangePathSegList
@@ -67,30 +66,28 @@ SVGPathData::GetValueAsString(nsAString&
 
 nsresult
 SVGPathData::SetValueFromString(const nsAString& aValue)
 {
   // We don't use a temp variable since the spec says to parse everything up to
   // the first error. We still return any error though so that callers know if
   // there's a problem.
 
-  mCachedPath = nullptr;
   nsSVGPathDataParser pathParser(aValue, this);
   return pathParser.Parse() ? NS_OK : NS_ERROR_DOM_SYNTAX_ERR;
 }
 
 nsresult
 SVGPathData::AppendSeg(uint32_t aType, ...)
 {
   uint32_t oldLength = mData.Length();
   uint32_t newLength = oldLength + 1 + SVGPathSegUtils::ArgCountForType(aType);
   if (!mData.SetLength(newLength)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
-  mCachedPath = nullptr;
 
   mData[oldLength] = SVGPathSegUtils::EncodeType(aType);
   va_list args;
   va_start(args, aType);
   for (uint32_t i = oldLength + 1; i < newLength; ++i) {
     // NOTE! 'float' is promoted to 'double' when passed through '...'!
     mData[i] = float(va_arg(args, double));
   }
@@ -505,35 +502,31 @@ SVGPathData::BuildPath(PathBuilder* buil
                     "prevSegType should be left at the final segType");
 
   MAYBE_APPROXIMATE_ZERO_LENGTH_SUBPATH_SQUARE_CAPS_TO_DT;
 
   return builder->Finish();
 }
 
 TemporaryRef<Path>
-SVGPathData::ToPathForLengthOrPositionMeasuring() const
+SVGPathData::BuildPathForMeasuring() const
 {
   // Since the path that we return will not be used for painting it doesn't
   // matter what we pass to CreatePathBuilder as aFillRule. Hawever, we do want
   // to pass something other than NS_STYLE_STROKE_LINECAP_SQUARE as
   // aStrokeLineCap to avoid the insertion of extra little lines (by
   // ApproximateZeroLengthSubpathSquareCaps), in which case the value that we
   // pass as aStrokeWidth doesn't matter (since it's only used to determine the
   // length of those extra little lines).
 
-  if (!mCachedPath) {
-    RefPtr<DrawTarget> drawTarget =
-      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-    RefPtr<PathBuilder> builder =
-      drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
-    mCachedPath = BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
-  }
-
-  return mCachedPath;
+  RefPtr<DrawTarget> drawTarget =
+    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+  RefPtr<PathBuilder> builder =
+    drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
+  return BuildPath(builder, NS_STYLE_STROKE_LINECAP_BUTT, 0);
 }
 
 static double
 AngleOfVector(const Point& aVector)
 {
   // C99 says about atan2 "A domain error may occur if both arguments are
   // zero" and "On a domain error, the function returns an implementation-
   // defined value". In the case of atan2 the implementation-defined value
--- a/content/svg/content/src/SVGPathData.h
+++ b/content/svg/content/src/SVGPathData.h
@@ -158,17 +158,17 @@ public:
    */
   bool GetDistancesFromOriginToEndsOfVisibleSegments(FallibleTArray<double> *aArray) const;
 
   /**
    * This returns a path without the extra little line segments that
    * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
    * See the comment for that function for more info on that.
    */
-  TemporaryRef<Path> ToPathForLengthOrPositionMeasuring() const;
+  TemporaryRef<Path> BuildPathForMeasuring() const;
 
   TemporaryRef<Path> BuildPath(PathBuilder* aBuilder,
                                uint8_t aCapStyle,
                                Float aStrokeWidth) const;
 
   const_iterator begin() const { return mData.Elements(); }
   const_iterator end() const { return mData.Elements() + mData.Length(); }
 
@@ -188,51 +188,47 @@ protected:
 
   /**
    * This may fail on OOM if the internal capacity needs to be increased, in
    * which case the list will be left unmodified.
    */
   nsresult CopyFrom(const SVGPathData& rhs);
 
   float& operator[](uint32_t aIndex) {
-    mCachedPath = nullptr;
     return mData[aIndex];
   }
 
   /**
    * This may fail (return false) on OOM if the internal capacity is being
    * increased, in which case the list will be left unmodified.
    */
   bool SetLength(uint32_t aLength) {
-    mCachedPath = nullptr;
     return mData.SetLength(aLength);
   }
 
   nsresult SetValueFromString(const nsAString& aValue);
 
   void Clear() {
-    mCachedPath = nullptr;
     mData.Clear();
   }
 
   // Our DOM wrappers have direct access to our mData, so they directly
   // manipulate it rather than us implementing:
   //
   // * InsertItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
   // * ReplaceItem(uint32_t aDataIndex, uint32_t aType, const float *aArgs);
   // * RemoveItem(uint32_t aDataIndex);
   // * bool AppendItem(uint32_t aType, const float *aArgs);
 
   nsresult AppendSeg(uint32_t aType, ...); // variable number of float args
 
-  iterator begin() { mCachedPath = nullptr; return mData.Elements(); }
-  iterator end() { mCachedPath = nullptr; return mData.Elements() + mData.Length(); }
+  iterator begin() { return mData.Elements(); }
+  iterator end() { return mData.Elements() + mData.Length(); }
 
   FallibleTArray<float> mData;
-  mutable RefPtr<gfx::Path> mCachedPath;
 };
 
 
 /**
  * This SVGPathData subclass is for SVGPathSegListSMILType which needs to
  * have write access to the lists it works with.
  *
  * Instances of this class do not have DOM wrappers that need to be kept in
--- a/content/svg/content/src/SVGPathElement.cpp
+++ b/content/svg/content/src/SVGPathElement.cpp
@@ -65,24 +65,24 @@ already_AddRefed<SVGAnimatedNumber>
 SVGPathElement::PathLength()
 {
   return mPathLength.ToDOMAnimatedNumber(this);
 }
 
 float
 SVGPathElement::GetTotalLength()
 {
-  RefPtr<Path> flat = GetPathForLengthOrPositionMeasuring();
+  RefPtr<Path> flat = GetOrBuildPathForMeasuring();
   return flat ? flat->ComputeLength() : 0.f;
 }
 
 already_AddRefed<nsISVGPoint>
 SVGPathElement::GetPointAtLength(float distance, ErrorResult& rv)
 {
-  RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
+  RefPtr<Path> path = GetOrBuildPathForMeasuring();
   if (!path) {
     rv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   float totalLength = path->ComputeLength();
   if (mPathLength.IsExplicitlySet()) {
     float pathLength = mPathLength.GetAnimValue();
@@ -300,19 +300,19 @@ SVGPathElement::IsAttributeMapped(const 
     sMarkersMap
   };
 
   return FindAttributeDependence(name, map) ||
     SVGPathElementBase::IsAttributeMapped(name);
 }
 
 TemporaryRef<Path>
-SVGPathElement::GetPathForLengthOrPositionMeasuring()
+SVGPathElement::GetOrBuildPathForMeasuring()
 {
-  return mD.GetAnimValue().ToPathForLengthOrPositionMeasuring();
+  return mD.GetAnimValue().BuildPathForMeasuring();
 }
 
 //----------------------------------------------------------------------
 // nsSVGPathGeometryElement methods
 
 bool
 SVGPathElement::AttributeDefinesGeometry(const nsIAtom *aName)
 {
@@ -335,17 +335,17 @@ SVGPathElement::GetMarkPoints(nsTArray<n
 float
 SVGPathElement::GetPathLengthScale(PathLengthScaleForType aFor)
 {
   NS_ABORT_IF_FALSE(aFor == eForTextPath || aFor == eForStroking,
                     "Unknown enum");
   if (mPathLength.IsExplicitlySet()) {
     float authorsPathLengthEstimate = mPathLength.GetAnimValue();
     if (authorsPathLengthEstimate > 0) {
-      RefPtr<Path> path = GetPathForLengthOrPositionMeasuring();
+      RefPtr<Path> path = GetOrBuildPathForMeasuring();
       if (!path) {
         // The path is empty or invalid so its length must be zero and
         // we know that 0 / authorsPathLengthEstimate = 0.
         return 0.0;
       }
       if (aFor == eForTextPath) {
         // For textPath, a transform on the referenced path affects the
         // textPath layout, so when calculating the actual path length
@@ -385,30 +385,13 @@ SVGPathElement::BuildPath(PathBuilder* a
     // reason we do not check for eStyleSVGPaintType_None or check the stroke
     // opacity here.
     if (style->mStrokeLinecap != NS_STYLE_STROKE_LINECAP_BUTT) {
       strokeLineCap = style->mStrokeLinecap;
       strokeWidth = SVGContentUtils::GetStrokeWidth(this, styleContext, nullptr);
     }
   }
 
-  RefPtr<PathBuilder> builder;
-  if (aBuilder) {
-    builder = aBuilder;
-  } else {
-    RefPtr<DrawTarget> drawTarget =
-      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-    // The fill rule that we pass must be the current computed value of our
-    // CSS 'fill-rule' property if the path that we return will be used for
-    // painting or hit-testing. For all other uses (bounds calculatons, length
-    // measurement, position-at-offset calculations) the fill rule that we pass
-    // doesn't matter. As a result we can just pass the current computed value
-    // regardless of who's calling us, or what they're going to do with the
-    // path that we return.
-    RefPtr<PathBuilder> builder =
-      drawTarget->CreatePathBuilder(GetFillRule());
-  }
-
-  return mD.GetAnimValue().BuildPath(builder, strokeLineCap, strokeWidth);
+  return mD.GetAnimValue().BuildPath(aBuilder, strokeLineCap, strokeWidth);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGPathElement.h
+++ b/content/svg/content/src/SVGPathElement.h
@@ -45,25 +45,24 @@ public:
 
   // nsSVGSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
   virtual bool AttributeDefinesGeometry(const nsIAtom *aName) MOZ_OVERRIDE;
   virtual bool IsMarkable() MOZ_OVERRIDE;
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks) MOZ_OVERRIDE;
-  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   /**
    * This returns a path without the extra little line segments that
    * ApproximateZeroLengthSubpathSquareCaps can insert if we have square-caps.
    * See the comment for that function for more info on that.
    */
-  virtual TemporaryRef<Path>
-    GetPathForLengthOrPositionMeasuring() MOZ_OVERRIDE;
+  virtual TemporaryRef<Path> GetOrBuildPathForMeasuring() MOZ_OVERRIDE;
 
   // nsIContent interface
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE;
 
   virtual SVGAnimatedPathSegList* GetAnimPathSegList() MOZ_OVERRIDE {
     return &mD;
   }
 
--- a/content/svg/content/src/SVGRectElement.cpp
+++ b/content/svg/content/src/SVGRectElement.cpp
@@ -113,29 +113,27 @@ SVGRectElement::BuildPath(PathBuilder* a
 {
   float x, y, width, height, rx, ry;
   GetAnimatedLengthValues(&x, &y, &width, &height, &rx, &ry, nullptr);
 
   if (width <= 0 || height <= 0) {
     return nullptr;
   }
 
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
-
   rx = std::max(rx, 0.0f);
   ry = std::max(ry, 0.0f);
 
   if (rx == 0 && ry == 0) {
     // Optimization for the no rounded corners case.
     Rect r(x, y, width, height);
-    pathBuilder->MoveTo(r.TopLeft());
-    pathBuilder->LineTo(r.TopRight());
-    pathBuilder->LineTo(r.BottomRight());
-    pathBuilder->LineTo(r.BottomLeft());
-    pathBuilder->Close();
+    aBuilder->MoveTo(r.TopLeft());
+    aBuilder->LineTo(r.TopRight());
+    aBuilder->LineTo(r.BottomRight());
+    aBuilder->LineTo(r.BottomLeft());
+    aBuilder->Close();
   } else {
     // If either the 'rx' or the 'ry' attribute isn't set, then we have to
     // set it to the value of the other:
     bool hasRx = mLengthAttributes[ATTR_RX].IsExplicitlySet();
     bool hasRy = mLengthAttributes[ATTR_RY].IsExplicitlySet();
     MOZ_ASSERT(hasRx || hasRy);
 
     if (hasRx && !hasRy) {
@@ -145,16 +143,16 @@ SVGRectElement::BuildPath(PathBuilder* a
     }
 
     // Clamp rx and ry to half the rect's width and height respectively:
     rx = std::min(rx, width / 2);
     ry = std::min(ry, height / 2);
 
     Size cornerRadii(rx, ry);
     Size radii[] = { cornerRadii, cornerRadii, cornerRadii, cornerRadii };
-    AppendRoundedRectToPath(pathBuilder, Rect(x, y, width, height), radii);
+    AppendRoundedRectToPath(aBuilder, Rect(x, y, width, height), radii);
   }
 
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGSVGElement.cpp
+++ b/content/svg/content/src/SVGSVGElement.cpp
@@ -302,17 +302,17 @@ void
 SVGSVGElement::UnsuspendRedrawAll()
 {
   // no-op
 }
 
 void
 SVGSVGElement::ForceRedraw(ErrorResult& rv)
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (!doc) {
     rv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   doc->FlushPendingNotifications(Flush_Display);
 }
 
@@ -509,17 +509,17 @@ SVGSVGElement::SetCurrentScaleTranslate(
   // their own last change.
   mPreviousScale = mCurrentScale;
   mPreviousTranslate = mCurrentTranslate;
   
   mCurrentScale = s;
   mCurrentTranslate = SVGPoint(x, y);
 
   // now dispatch the appropriate event if we are the root element
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   if (doc) {
     nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
     if (presShell && IsRoot()) {
       nsEventStatus status = nsEventStatus_eIgnore;
       if (mPreviousScale != mCurrentScale) {
         InternalSVGZoomEvent svgZoomEvent(true, NS_SVG_ZOOM);
         presShell->HandleDOMEventWithTarget(this, &svgZoomEvent, &status);
       } else {
@@ -826,17 +826,18 @@ SVGSVGElement::HasPreserveAspectRatio()
   return HasAttr(kNameSpaceID_None, nsGkAtoms::preserveAspectRatio) ||
     mPreserveAspectRatio.IsAnimated();
 }
 
 SVGViewElement*
 SVGSVGElement::GetCurrentViewElement() const
 {
   if (mCurrentViewID) {
-    nsIDocument* doc = GetCurrentDoc();
+    //XXXsmaug It is unclear how this should work in case we're in Shadow DOM.
+    nsIDocument* doc = GetUncomposedDoc();
     if (doc) {
       Element *element = doc->GetElementById(*mCurrentViewID);
       if (element && element->IsSVG(nsGkAtoms::view)) {
         return static_cast<SVGViewElement*>(element);
       }
     }
   }
   return nullptr;
@@ -869,17 +870,17 @@ SVGSVGElement::GetViewBoxWithSynthesis(
   // No viewBox attribute, so we shouldn't auto-scale. This is equivalent
   // to having a viewBox that exactly matches our viewport size.
   return nsSVGViewBoxRect(0, 0, aViewportWidth, aViewportHeight);
 }
 
 SVGPreserveAspectRatio
 SVGSVGElement::GetPreserveAspectRatioWithOverride() const
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   if (doc && doc->IsBeingUsedAsImage()) {
     const SVGPreserveAspectRatio *pAROverridePtr = GetPreserveAspectRatioProperty();
     if (pAROverridePtr) {
       return *pAROverridePtr;
     }
   }
 
   SVGViewElement* viewElement = GetCurrentViewElement();
@@ -1032,17 +1033,17 @@ SVGSVGElement::HasViewBoxRect() const
 }
 
 bool
 SVGSVGElement::ShouldSynthesizeViewBox() const
 {
   NS_ABORT_IF_FALSE(!HasViewBoxRect(),
                     "Should only be called if we lack a viewBox");
 
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   return doc &&
     doc->IsBeingUsedAsImage() &&
     !mIsPaintingSVGImageElement &&
     !GetParent();
 }
 
 bool
 SVGSVGElement::SetPreserveAspectRatioProperty(const SVGPreserveAspectRatio& aPAR)
@@ -1081,17 +1082,17 @@ SVGSVGElement::ClearPreserveAspectRatioP
   return valPtr;
 }
 
 void
 SVGSVGElement::
   SetImageOverridePreserveAspectRatio(const SVGPreserveAspectRatio& aPAR)
 {
 #ifdef DEBUG
-  NS_ABORT_IF_FALSE(GetCurrentDoc()->IsBeingUsedAsImage(),
+  NS_ABORT_IF_FALSE(OwnerDoc()->IsBeingUsedAsImage(),
                     "should only override preserveAspectRatio in images");
 #endif
 
   bool hasViewBoxRect = HasViewBoxRect();
   if (!hasViewBoxRect && ShouldSynthesizeViewBox()) {
     // My non-<svg:image> clients will have been painting me with a synthesized
     // viewBox, but my <svg:image> client that's about to paint me now does NOT
     // want that.  Need to tell ourselves to flush our transform.
@@ -1111,17 +1112,17 @@ SVGSVGElement::
     mImageNeedsTransformInvalidation = true;
   }
 }
 
 void
 SVGSVGElement::ClearImageOverridePreserveAspectRatio()
 {
 #ifdef DEBUG
-  NS_ABORT_IF_FALSE(GetCurrentDoc()->IsBeingUsedAsImage(),
+  NS_ABORT_IF_FALSE(OwnerDoc()->IsBeingUsedAsImage(),
                     "should only override image preserveAspectRatio in images");
 #endif
 
   mIsPaintingSVGImageElement = false;
   if (!HasViewBoxRect() && ShouldSynthesizeViewBox()) {
     // My non-<svg:image> clients will want to paint me with a synthesized
     // viewBox, but my <svg:image> client that just painted me did NOT
     // use that.  Need to tell ourselves to flush our transform.
@@ -1132,17 +1133,17 @@ SVGSVGElement::ClearImageOverridePreserv
     mImageNeedsTransformInvalidation = true;
   }
 }
 
 void
 SVGSVGElement::FlushImageTransformInvalidation()
 {
   NS_ABORT_IF_FALSE(!GetParent(), "Should only be called on root node");
-  NS_ABORT_IF_FALSE(GetCurrentDoc()->IsBeingUsedAsImage(),
+  NS_ABORT_IF_FALSE(OwnerDoc()->IsBeingUsedAsImage(),
                     "Should only be called on image documents");
 
   if (mImageNeedsTransformInvalidation) {
     InvalidateTransformNotifyFrame();
     mImageNeedsTransformInvalidation = false;
   }
 }
 
--- a/content/svg/content/src/SVGStyleElement.cpp
+++ b/content/svg/content/src/SVGStyleElement.cpp
@@ -80,17 +80,17 @@ SVGStyleElement::BindToTree(nsIDocument*
   nsContentUtils::AddScriptRunner(NS_NewRunnableMethod(this, update));
 
   return rv;
 }
 
 void
 SVGStyleElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
-  nsCOMPtr<nsIDocument> oldDoc = GetCurrentDoc();
+  nsCOMPtr<nsIDocument> oldDoc = GetUncomposedDoc();
   ShadowRoot* oldShadow = GetContainingShadow();
   SVGStyleElementBase::UnbindFromTree(aDeep, aNullParent);
   UpdateStyleSheetInternal(oldDoc, oldShadow);
 }
 
 nsresult
 SVGStyleElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName,
                          nsIAtom* aPrefix, const nsAString& aValue,
--- a/content/svg/content/src/SVGTitleElement.cpp
+++ b/content/svg/content/src/SVGTitleElement.cpp
@@ -106,17 +106,17 @@ SVGTitleElement::DoneAddingChildren(bool
   if (!aHaveNotified) {
     SendTitleChangeEvent(false);
   }
 }
 
 void
 SVGTitleElement::SendTitleChangeEvent(bool aBound)
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetUncomposedDoc();
   if (doc) {
     doc->NotifyPossibleTitleChange(aBound);
   }
 }
 
 //----------------------------------------------------------------------
 // nsIDOMNode methods
 
--- a/content/svg/content/src/SVGTransformableElement.cpp
+++ b/content/svg/content/src/SVGTransformableElement.cpp
@@ -220,30 +220,30 @@ SVGTransformableElement::GetBBox(const S
     }
     return NS_NewSVGRect(this, ToRect(nsSVGUtils::GetBBox(frame, aFlags)));
   }
 }
 
 already_AddRefed<SVGMatrix>
 SVGTransformableElement::GetCTM()
 {
-  nsIDocument* currentDoc = GetCurrentDoc();
+  nsIDocument* currentDoc = GetComposedDoc();
   if (currentDoc) {
     // Flush all pending notifications so that our frames are up to date
     currentDoc->FlushPendingNotifications(Flush_Layout);
   }
   gfx::Matrix m = SVGContentUtils::GetCTM(this, false);
   nsRefPtr<SVGMatrix> mat = m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
   return mat.forget();
 }
 
 already_AddRefed<SVGMatrix>
 SVGTransformableElement::GetScreenCTM()
 {
-  nsIDocument* currentDoc = GetCurrentDoc();
+  nsIDocument* currentDoc = GetComposedDoc();
   if (currentDoc) {
     // Flush all pending notifications so that our frames are up to date
     currentDoc->FlushPendingNotifications(Flush_Layout);
   }
   gfx::Matrix m = SVGContentUtils::GetCTM(this, true);
   nsRefPtr<SVGMatrix> mat = m.IsSingular() ? nullptr : new SVGMatrix(ThebesMatrix(m));
   return mat.forget();
 }
--- a/content/svg/content/src/SVGUseElement.cpp
+++ b/content/svg/content/src/SVGUseElement.cpp
@@ -264,17 +264,17 @@ SVGUseElement::CreateAnonymousContent()
                      getter_AddRefs(newnode));
 
   nsCOMPtr<nsIContent> newcontent = do_QueryInterface(newnode);
 
   if (!newcontent)
     return nullptr;
 
   if (newcontent->IsSVG(nsGkAtoms::symbol)) {
-    nsIDocument *document = GetCurrentDoc();
+    nsIDocument *document = GetComposedDoc();
     if (!document)
       return nullptr;
 
     nsNodeInfoManager *nodeInfoManager = document->NodeInfoManager();
     if (!nodeInfoManager)
       return nullptr;
 
     nsRefPtr<mozilla::dom::NodeInfo> nodeInfo;
@@ -387,25 +387,25 @@ SVGUseElement::LookupHref()
   nsAutoString href;
   mStringAttributes[HREF].GetAnimValue(href, this);
   if (href.IsEmpty())
     return;
 
   nsCOMPtr<nsIURI> targetURI;
   nsCOMPtr<nsIURI> baseURI = mOriginal ? mOriginal->GetBaseURI() : GetBaseURI();
   nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href,
-                                            GetCurrentDoc(), baseURI);
+                                            GetComposedDoc(), baseURI);
 
   mSource.Reset(this, targetURI);
 }
 
 void
 SVGUseElement::TriggerReclone()
 {
-  nsIDocument *doc = GetCurrentDoc();
+  nsIDocument *doc = GetComposedDoc();
   if (!doc)
     return;
   nsIPresShell *presShell = doc->GetShell();
   if (!presShell)
     return;
   presShell->PostRecreateFramesFor(this);
 }
 
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -10,16 +10,17 @@
 #include "nsSVGElement.h"
 
 #include "mozilla/dom/SVGSVGElement.h"
 #include "mozilla/dom/SVGTests.h"
 #include "nsContentUtils.h"
 #include "nsICSSDeclaration.h"
 #include "nsIDocument.h"
 #include "nsIDOMMutationEvent.h"
+#include "nsSVGPathGeometryElement.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "nsError.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
 #include "mozilla/css/StyleRule.h"
 #include "nsRuleWalker.h"
 #include "mozilla/css/Declaration.h"
 #include "nsCSSProps.h"
@@ -1600,16 +1601,18 @@ nsSVGElement::DidChangeLength(uint8_t aA
 
   DidChangeValue(*info.mLengthInfo[aAttrEnum].mName, aEmptyOrOldValue,
                  newValue);
 }
 
 void
 nsSVGElement::DidAnimateLength(uint8_t aAttrEnum)
 {
+  ClearAnyCachedPath();
+
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     LengthAttributesInfo info = GetLengthInfo();
     frame->AttributeChanged(kNameSpaceID_None,
                             *info.mLengthInfo[aAttrEnum].mName,
                             nsIDOMMutationEvent::MODIFICATION);
   }
@@ -1845,16 +1848,18 @@ nsSVGElement::DidChangePointList(const n
 }
 
 void
 nsSVGElement::DidAnimatePointList()
 {
   NS_ABORT_IF_FALSE(GetPointListAttrName(),
                     "Animating non-existent path data?");
 
+  ClearAnyCachedPath();
+
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             GetPointListAttrName(),
                             nsIDOMMutationEvent::MODIFICATION);
   }
 }
@@ -1880,16 +1885,18 @@ nsSVGElement::DidChangePathSegList(const
 }
 
 void
 nsSVGElement::DidAnimatePathSegList()
 {
   NS_ABORT_IF_FALSE(GetPathDataAttrName(),
                     "Animating non-existent path data?");
 
+  ClearAnyCachedPath();
+
   nsIFrame* frame = GetPrimaryFrame();
 
   if (frame) {
     frame->AttributeChanged(kNameSpaceID_None,
                             GetPathDataAttrName(),
                             nsIDOMMutationEvent::MODIFICATION);
   }
 }
@@ -2700,22 +2707,22 @@ nsSVGElement::GetAnimatedAttr(int32_t aN
   }
 
   return nullptr;
 }
 
 void
 nsSVGElement::AnimationNeedsResample()
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (doc && doc->HasAnimationController()) {
     doc->GetAnimationController()->SetResampleNeeded();
   }
 }
 
 void
 nsSVGElement::FlushAnimations()
 {
-  nsIDocument* doc = GetCurrentDoc();
+  nsIDocument* doc = GetComposedDoc();
   if (doc && doc->HasAnimationController()) {
     doc->GetAnimationController()->FlushResampleRequests();
   }
 }
--- a/content/svg/content/src/nsSVGElement.h
+++ b/content/svg/content/src/nsSVGElement.h
@@ -28,16 +28,17 @@
 class nsSVGAngle;
 class nsSVGBoolean;
 class nsSVGEnum;
 class nsSVGInteger;
 class nsSVGIntegerPair;
 class nsSVGLength2;
 class nsSVGNumber2;
 class nsSVGNumberPair;
+class nsSVGPathGeometryElement;
 class nsSVGString;
 class nsSVGViewBox;
 
 namespace mozilla {
 namespace dom {
 class CSSValue;
 class SVGSVGElement;
 
@@ -308,16 +309,17 @@ public:
   const nsAttrValue* GetAnimatedClassName() const
   {
     if (!mClassAttribute.IsAnimated()) {
       return nullptr;
     }
     return mClassAnimAttr;
   }
 
+  virtual void ClearAnyCachedPath() {}
   virtual nsIDOMNode* AsDOMNode() MOZ_FINAL MOZ_OVERRIDE { return this; }
   virtual bool IsTransformable() { return false; }
 
   // WebIDL
   mozilla::dom::SVGSVGElement* GetOwnerSVGElement();
   nsSVGElement* GetViewportElement();
   already_AddRefed<mozilla::dom::SVGAnimatedString> ClassName();
 protected:
--- a/content/svg/content/src/nsSVGFeatures.cpp
+++ b/content/svg/content/src/nsSVGFeatures.cpp
@@ -20,17 +20,17 @@
 using namespace mozilla;
 
 /*static*/ bool
 nsSVGFeatures::HasFeature(nsISupports* aObject, const nsAString& aFeature)
 {
   if (aFeature.EqualsLiteral("http://www.w3.org/TR/SVG11/feature#Script")) {
     nsCOMPtr<nsIContent> content(do_QueryInterface(aObject));
     if (content) {
-      nsIDocument *doc = content->GetCurrentDoc();
+      nsIDocument* doc = content->GetUncomposedDoc();
       if (doc && doc->IsResourceDoc()) {
         // no scripting in SVG images or external resource documents
         return false;
       }
     }
     return Preferences::GetBool("javascript.enabled", false);
   }
 #define SVG_SUPPORTED_FEATURE(str) if (aFeature.EqualsLiteral(str)) return true;
--- a/content/svg/content/src/nsSVGPathGeometryElement.cpp
+++ b/content/svg/content/src/nsSVGPathGeometryElement.cpp
@@ -3,30 +3,44 @@
  * 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 "nsSVGPathGeometryElement.h"
 
 #include "gfxPlatform.h"
 #include "mozilla/gfx/2D.h"
 #include "nsComputedDOMStyle.h"
+#include "nsSVGUtils.h"
 #include "nsSVGLength2.h"
 #include "SVGContentUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
 //----------------------------------------------------------------------
 // Implementation
 
 nsSVGPathGeometryElement::nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsSVGPathGeometryElementBase(aNodeInfo)
 {
 }
 
+nsresult
+nsSVGPathGeometryElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                       const nsAttrValue* aValue, bool aNotify)
+{
+  if (mCachedPath &&
+      aNamespaceID == kNameSpaceID_None &&
+      AttributeDefinesGeometry(aName)) {
+    mCachedPath = nullptr;
+  }
+  return nsSVGPathGeometryElementBase::AfterSetAttr(aNamespaceID, aName,
+                                                    aValue, aNotify);
+}
+
 bool
 nsSVGPathGeometryElement::AttributeDefinesGeometry(const nsIAtom *aName)
 {
   // Check for nsSVGLength2 attribute
   LengthAttributesInfo info = GetLengthInfo();
   for (uint32_t i = 0; i < info.mLengthCount; i++) {
     if (aName == *info.mLengthInfo[i].mName) {
       return true;
@@ -56,39 +70,42 @@ nsSVGPathGeometryElement::IsMarkable()
 }
 
 void
 nsSVGPathGeometryElement::GetMarkPoints(nsTArray<nsSVGMark> *aMarks)
 {
 }
 
 TemporaryRef<Path>
-nsSVGPathGeometryElement::GetPathForLengthOrPositionMeasuring()
+nsSVGPathGeometryElement::GetOrBuildPath(const DrawTarget& aDrawTarget,
+                                         FillRule aFillRule)
 {
-  return nullptr;
+  // We only cache the path if it matches the backend used for screen painting:
+  bool cacheable  = aDrawTarget.GetBackendType() ==
+                      gfxPlatform::GetPlatform()->GetContentBackend();
+
+  // Checking for and returning mCachedPath before checking the pref means
+  // that the pref is only live on page reload (or app restart for SVG in
+  // chrome). The benefit is that we avoid causing a CPU memory cache miss by
+  // looking at the global variable that the pref's stored in.
+  if (cacheable && mCachedPath) {
+    return mCachedPath;
+  }
+  RefPtr<PathBuilder> builder = aDrawTarget.CreatePathBuilder(aFillRule);
+  RefPtr<Path> path = BuildPath(builder);
+  if (cacheable && NS_SVGPathCachingEnabled()) {
+    mCachedPath = path;
+  }
+  return path.forget();
 }
 
-TemporaryRef<PathBuilder>
-nsSVGPathGeometryElement::CreatePathBuilder()
+TemporaryRef<Path>
+nsSVGPathGeometryElement::GetOrBuildPathForMeasuring()
 {
-  RefPtr<DrawTarget> drawTarget =
-    gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
-  NS_ASSERTION(gfxPlatform::GetPlatform()->
-                 SupportsAzureContentForDrawTarget(drawTarget),
-               "Should support Moz2D content drawing");
-
-  // The fill rule that we pass to CreatePathBuilder must be the current
-  // computed value of our CSS 'fill-rule' property if the path that we return
-  // will be used for painting or hit-testing. For all other uses (bounds
-  // calculatons, length measurement, position-at-offset calculations) the fill
-  // rule that we pass doesn't matter. As a result we can just pass the current
-  // computed value regardless of who's calling us, or what they're going to do
-  // with the path that we return.
-
-  return drawTarget->CreatePathBuilder(GetFillRule());
+  return nullptr;
 }
 
 FillRule
 nsSVGPathGeometryElement::GetFillRule()
 {
   FillRule fillRule = FillRule::FILL_WINDING; // Equivalent to NS_STYLE_FILL_RULE_NONZERO
 
   nsRefPtr<nsStyleContext> styleContext =
--- a/content/svg/content/src/nsSVGPathGeometryElement.h
+++ b/content/svg/content/src/nsSVGPathGeometryElement.h
@@ -26,24 +26,36 @@ struct nsSVGMark {
     x(aX), y(aY), angle(aAngle), type(aType) {}
 };
 
 typedef mozilla::dom::SVGGraphicsElement nsSVGPathGeometryElementBase;
 
 class nsSVGPathGeometryElement : public nsSVGPathGeometryElementBase
 {
 protected:
+  typedef mozilla::gfx::DrawTarget DrawTarget;
   typedef mozilla::gfx::FillRule FillRule;
   typedef mozilla::gfx::Float Float;
   typedef mozilla::gfx::Path Path;
   typedef mozilla::gfx::PathBuilder PathBuilder;
 
 public:
   explicit nsSVGPathGeometryElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo);
 
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName,
+                                const nsAttrValue* aValue, bool aNotify) MOZ_OVERRIDE;
+
+  /**
+   * Causes this element to discard any Path object that GetOrBuildPath may
+   * have cached.
+   */
+  virtual void ClearAnyCachedPath() MOZ_OVERRIDE MOZ_FINAL {
+    mCachedPath = nullptr;
+  }
+
   virtual bool AttributeDefinesGeometry(const nsIAtom *aName);
 
   /**
    * Returns true if this element's geometry depends on the width or height of its
    * coordinate context (typically the viewport established by its nearest <svg>
    * ancestor). In other words, returns true if one of the attributes for which
    * AttributeDefinesGeometry returns true has a percentage value.
    *
@@ -52,28 +64,49 @@ public:
    */
   bool GeometryDependsOnCoordCtx();
 
   virtual bool IsMarkable();
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks);
 
   /**
    * Returns a Path that can be used to paint, hit-test or calculate bounds for
+   * this element. May return nullptr if there is no [valid] path. The path
+   * that is created may be cached and returned on subsequent calls.
+   */
+  virtual mozilla::TemporaryRef<Path> GetOrBuildPath(const DrawTarget& aDrawTarget,
+                                                     FillRule fillRule);
+
+  /**
+   * The same as GetOrBuildPath, but bypasses the cache (neither returns any
+   * previously cached Path, nor caches the Path that in does return).
    * this element. May return nullptr if there is no [valid] path.
    */
-  virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) = 0;
-
-  virtual mozilla::TemporaryRef<Path> GetPathForLengthOrPositionMeasuring();
+  virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) = 0;
 
   /**
-   * Returns a PathBuilder object created using the current computed value of
-   * the CSS property 'fill-rule' for this element.
+   * Returns a Path that can be used to measure the length of this elements
+   * path, or to find the position at a given distance along it.
+   *
+   * This is currently equivalent to calling GetOrBuildPath, but it may not be
+   * in the future. The reason for this function to be separate from
+   * GetOrBuildPath is because SVGPathData::BuildPath inserts small lines into
+   * the path if zero length subpaths are encountered, in order to implement
+   * the SVG specifications requirements that zero length subpaths should
+   * render circles/squares if stroke-linecap is round/square, respectively.
+   * In principle these inserted lines could interfere with path measurement,
+   * so we keep callers that are looking to do measurement separate in case we
+   * run into problems with the inserted lines negatively affecting measuring
+   * for content.
    */
-  mozilla::TemporaryRef<PathBuilder> CreatePathBuilder();
+  virtual mozilla::TemporaryRef<Path> GetOrBuildPathForMeasuring();
 
   /**
    * Returns the current computed value of the CSS property 'fill-rule' for
    * this element.
    */
   FillRule GetFillRule();
+
+protected:
+  mutable mozilla::RefPtr<Path> mCachedPath;
 };
 
 #endif
--- a/content/svg/content/src/nsSVGPolyElement.cpp
+++ b/content/svg/content/src/nsSVGPolyElement.cpp
@@ -124,17 +124,15 @@ TemporaryRef<Path>
 nsSVGPolyElement::BuildPath(PathBuilder* aBuilder)
 {
   const SVGPointList &points = mPoints.GetAnimValue();
 
   if (points.IsEmpty()) {
     return nullptr;
   }
 
-  RefPtr<PathBuilder> pathBuilder = aBuilder ? aBuilder : CreatePathBuilder();
-
-  pathBuilder->MoveTo(points[0]);
+  aBuilder->MoveTo(points[0]);
   for (uint32_t i = 1; i < points.Length(); ++i) {
-    pathBuilder->LineTo(points[i]);
+    aBuilder->LineTo(points[i]);
   }
 
-  return pathBuilder->Finish();
+  return aBuilder->Finish();
 }
--- a/content/svg/content/src/nsSVGPolyElement.h
+++ b/content/svg/content/src/nsSVGPolyElement.h
@@ -40,17 +40,17 @@ public:
 
   // nsSVGElement methods:
   virtual bool HasValidDimensions() const MOZ_OVERRIDE;
 
   // nsSVGPathGeometryElement methods:
   virtual bool AttributeDefinesGeometry(const nsIAtom *aName) MOZ_OVERRIDE;
   virtual bool IsMarkable() MOZ_OVERRIDE { return true; }
   virtual void GetMarkPoints(nsTArray<nsSVGMark> *aMarks) MOZ_OVERRIDE;
-  virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder = nullptr) MOZ_OVERRIDE;
+  virtual mozilla::TemporaryRef<Path> BuildPath(PathBuilder* aBuilder) MOZ_OVERRIDE;
 
   // WebIDL
   already_AddRefed<mozilla::DOMSVGPointList> Points();
   already_AddRefed<mozilla::DOMSVGPointList> AnimatedPoints();
 
 protected:
   SVGAnimatedPointList mPoints;
 };
--- a/content/xul/content/src/nsXULElement.cpp
+++ b/content/xul/content/src/nsXULElement.cpp
@@ -495,17 +495,17 @@ nsXULElement::GetElementsByAttributeNS(c
                           nameSpaceId);
 
     return list.forget();
 }
 
 EventListenerManager*
 nsXULElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer)
 {
-    // XXXbz sXBL/XBL2 issue: should we instead use GetCurrentDoc()
+    // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc()
     // here, override BindToTree for those classes and munge event
     // listeners there?
     nsIDocument* doc = OwnerDoc();
 
     nsPIDOMWindow *window;
     Element *root = doc->GetRootElement();
     if ((!root || root == this) && !mNodeInfo->Equals(nsGkAtoms::overlay) &&
         (window = doc->GetInnerWindow())) {
@@ -632,18 +632,20 @@ nsXULElement::PerformAccesskey(bool aKey
     nsCOMPtr<nsIContent> content(this);
 
     if (Tag() == nsGkAtoms::label) {
         nsCOMPtr<nsIDOMElement> element;
 
         nsAutoString control;
         GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
         if (!control.IsEmpty()) {
+            //XXXsmaug Should we use ShadowRoot::GetElementById in case
+            //         content is in Shadow DOM?
             nsCOMPtr<nsIDOMDocument> domDocument =
-                do_QueryInterface(content->GetCurrentDoc());
+                do_QueryInterface(content->GetUncomposedDoc());
             if (domDocument)
                 domDocument->GetElementById(control, getter_AddRefs(element));
         }
         // here we'll either change |content| to the element referenced by
         // |element|, or clear it.
         content = do_QueryInterface(element);
 
         if (!content)
@@ -998,31 +1000,31 @@ nsXULElement::RemoveChildAt(uint32_t aIn
             if (xulCurItem)
                 controlElement->SetCurrentItem(xulCurItem);
         } else {
             controlElement->SetCurrentItem(nullptr);
         }
     }
 
     nsIDocument* doc;
-    if (fireSelectionHandler && (doc = GetCurrentDoc())) {
+    if (fireSelectionHandler && (doc = GetComposedDoc())) {
       nsContentUtils::DispatchTrustedEvent(doc,
                                            static_cast<nsIContent*>(this),
                                            NS_LITERAL_STRING("select"),
                                            false,
                                            true);
     }
 }
 
 void
 nsXULElement::UnregisterAccessKey(const nsAString& aOldValue)
 {
     // If someone changes the accesskey, unregister the old one
     //
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetComposedDoc();
     if (doc && !aOldValue.IsEmpty()) {
         nsIPresShell *shell = doc->GetShell();
 
         if (shell) {
             nsIContent *content = this;
 
             // find out what type of content node this is
             if (mNodeInfo->Equals(nsGkAtoms::label)) {
@@ -1093,17 +1095,17 @@ nsXULElement::AfterSetAttr(int32_t aName
                     SetEventHandler(aName, aValue->GetStringValue(), true);
                 } else {
                     nsAutoString body;
                     aValue->ToString(body);
                     SetEventHandler(aName, body, true);
                 }
             }
     
-            nsIDocument *document = GetCurrentDoc();
+            nsIDocument* document = GetUncomposedDoc();
 
             // Hide chrome if needed
             if (mNodeInfo->Equals(nsGkAtoms::window)) {
                 if (aName == nsGkAtoms::hidechrome) {
                     HideWindowChrome(
                       aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters));
                 }
                 else if (aName == nsGkAtoms::chromemargin) {
@@ -1168,17 +1170,17 @@ nsXULElement::AfterSetAttr(int32_t aName
                 if (aName == nsGkAtoms::hidechrome) {
                     HideWindowChrome(false);
                 }
                 else if (aName == nsGkAtoms::chromemargin) {
                     ResetChromeMargins();
                 }
             }
     
-            nsIDocument* doc = GetCurrentDoc();
+            nsIDocument* doc = GetUncomposedDoc();
             if (doc && doc->GetRootElement() == this) {
                 if ((aName == nsGkAtoms::activetitlebarcolor ||
                      aName == nsGkAtoms::inactivetitlebarcolor)) {
                     // Use 0, 0, 0, 0 as the "none" color.
                     SetTitlebarColor(NS_RGBA(0, 0, 0, 0), aName == nsGkAtoms::activetitlebarcolor);
                 }
                 else if (aName == nsGkAtoms::localedir) {
                     // if the localedir changed on the root element, reset the document direction
@@ -1304,17 +1306,17 @@ nsXULElement::PreHandleEvent(EventChainP
         if (xulEvent && GetAttr(kNameSpaceID_None, nsGkAtoms::command, command) &&
             !command.IsEmpty()) {
             // Stop building the event target chain for the original event.
             // We don't want it to propagate to any DOM nodes.
             aVisitor.mCanHandle = false;
             aVisitor.mAutomaticChromeDispatch = false;
 
             // XXX sXBL/XBL2 issue! Owner or current document?
-            nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetCurrentDoc()));
+            nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetUncomposedDoc()));
             NS_ENSURE_STATE(domDoc);
             nsCOMPtr<nsIDOMElement> commandElt;
             domDoc->GetElementById(command, getter_AddRefs(commandElt));
             nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt));
             if (commandContent) {
                 // Create a new command event to dispatch to the element
                 // pointed to by the command attribute.  The new event's
                 // sourceEvent will be the original command event that we're
@@ -1408,17 +1410,17 @@ nsXULElement::GetBuilder(nsIXULTemplateB
     *aBuilder = GetBuilder().take();
     return NS_OK;
 }
 
 already_AddRefed<nsIXULTemplateBuilder>
 nsXULElement::GetBuilder()
 {
     // XXX sXBL/XBL2 issue! Owner or current document?
-    nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(GetCurrentDoc());
+    nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(GetUncomposedDoc());
     if (!xuldoc) {
         return nullptr;
     }
 
     nsCOMPtr<nsIXULTemplateBuilder> builder;
     xuldoc->GetTemplateBuilderFor(this, getter_AddRefs(builder));
     return builder.forget();
 }
@@ -1698,17 +1700,17 @@ nsXULElement::Blur()
 }
 
 void
 nsXULElement::Blur(ErrorResult& rv)
 {
     if (!ShouldBlur(this))
       return;
 
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetComposedDoc();
     if (!doc)
       return;
 
     nsIDOMWindow* win = doc->GetWindow();
     nsIFocusManager* fm = nsFocusManager::GetFocusManager();
     if (win && fm) {
       rv = fm->ClearFocus(win);
     }
@@ -1727,17 +1729,17 @@ nsXULElement::Click(ErrorResult& rv)
 }
 
 nsresult
 nsXULElement::ClickWithInputSource(uint16_t aInputSource)
 {
     if (BoolAttrIsTrue(nsGkAtoms::disabled))
         return NS_OK;
 
-    nsCOMPtr<nsIDocument> doc = GetCurrentDoc(); // Strong just in case
+    nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // Strong just in case
     if (doc) {
         nsCOMPtr<nsIPresShell> shell = doc->GetShell();
         if (shell) {
             // strong ref to PresContext so events don't destroy it
             nsRefPtr<nsPresContext> context = shell->GetPresContext();
 
             bool isCallerChrome = nsContentUtils::IsCallerChrome();
 
@@ -1769,17 +1771,17 @@ nsXULElement::ClickWithInputSource(uint1
 
     // oncommand is fired when an element is clicked...
     return DoCommand();
 }
 
 NS_IMETHODIMP
 nsXULElement::DoCommand()
 {
-    nsCOMPtr<nsIDocument> doc = GetCurrentDoc(); // strong just in case
+    nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // strong just in case
     if (doc) {
         nsContentUtils::DispatchXULCommand(this, true);
     }
 
     return NS_OK;
 }
 
 nsIContent *
@@ -1881,17 +1883,17 @@ nsXULElement::MakeHeavyweight(nsXULProto
         NS_ENSURE_SUCCESS(rv, rv);
     }
     return NS_OK;
 }
 
 nsresult
 nsXULElement::HideWindowChrome(bool aShouldHide)
 {
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetUncomposedDoc();
     if (!doc || doc->GetRootElement() != this)
       return NS_ERROR_UNEXPECTED;
 
     // only top level chrome documents can hide the window chrome
     if (!doc->IsRootDisplayDocument())
       return NS_OK;
 
     nsIPresShell *shell = doc->GetShell();
@@ -1913,17 +1915,17 @@ nsXULElement::HideWindowChrome(bool aSho
     }
 
     return NS_OK;
 }
 
 nsIWidget*
 nsXULElement::GetWindowWidget()
 {
-    nsIDocument* doc = GetCurrentDoc();
+    nsIDocument* doc = GetComposedDoc();
 
     // only top level chrome documents can set the titlebar color
     if (doc && doc->IsRootDisplayDocument()) {
         nsCOMPtr<nsISupports> container = doc->GetContainer();
         nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
         if (baseWindow) {
             nsCOMPtr<nsIWidget> mainWidget;
             baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
--- a/content/xul/document/src/XULDocument.cpp
+++ b/content/xul/document/src/XULDocument.cpp
@@ -1702,17 +1702,17 @@ XULDocument::AddElementToDocumentPost(El
     }
 
     return NS_OK;
 }
 
 NS_IMETHODIMP
 XULDocument::AddSubtreeToDocument(nsIContent* aContent)
 {
-    NS_ASSERTION(aContent->GetCurrentDoc() == this, "Element not in doc!");
+    NS_ASSERTION(aContent->GetUncomposedDoc() == this, "Element not in doc!");
     // From here on we only care about elements.
     if (!aContent->IsElement()) {
         return NS_OK;
     }
 
     Element* aElement = aContent->AsElement();
 
     // Do pre-order addition magic
@@ -3733,17 +3733,17 @@ XULDocument::CheckTemplateBuilderHookup(
 /* static */ nsresult
 XULDocument::CreateTemplateBuilder(nsIContent* aElement)
 {
     // Check if need to construct a tree builder or content builder.
     bool isTreeBuilder = false;
 
     // return successful if the element is not is a document, as an inline
     // script could have removed it
-    nsIDocument *document = aElement->GetCurrentDoc();
+    nsIDocument* document = aElement->GetUncomposedDoc();
     NS_ENSURE_TRUE(document, NS_OK);
 
     int32_t nameSpaceID;
     nsIAtom* baseTag = document->BindingManager()->
       ResolveTag(aElement, &nameSpaceID);
 
     if ((nameSpaceID == kNameSpaceID_XUL) && (baseTag == nsGkAtoms::tree)) {
         // By default, we build content for a tree and then we attach
@@ -3873,17 +3873,17 @@ XULDocument::OverlayForwardReference::Re
         if (!target)
             return eResolve_Later;
 
         rv = Merge(target, mOverlay, notify);
         if (NS_FAILED(rv)) return eResolve_Error;
     }
 
     // Check if 'target' is still in our document --- it might not be!
-    if (!notify && target->GetCurrentDoc() == mDocument) {
+    if (!notify && target->GetUncomposedDoc() == mDocument) {
         // Add child and any descendants to the element map
         // XXX this is bogus, the content in 'target' might already be
         // in the document
         rv = mDocument->AddSubtreeToDocument(target);
         if (NS_FAILED(rv)) return eResolve_Error;
     }
 
 #ifdef PR_LOGGING
--- a/content/xul/templates/src/nsXULContentBuilder.cpp
+++ b/content/xul/templates/src/nsXULContentBuilder.cpp
@@ -1052,17 +1052,17 @@ nsXULContentBuilder::CreateContainerCont
         if (tag && tag != aElement->Tag())
             continue;
 
         CreateContainerContentsForQuerySet(aElement, aResult, aNotify, queryset,
                                            &container, &newIndexInContainer);
     }
 
     if (aNotifyAtEnd && container) {
-        MOZ_AUTO_DOC_UPDATE(container->GetCurrentDoc(), UPDATE_CONTENT_MODEL,
+        MOZ_AUTO_DOC_UPDATE(container->GetUncomposedDoc(), UPDATE_CONTENT_MODEL,
                             true);
         nsNodeUtils::ContentAppended(container,
                                      container->GetChildAt(newIndexInContainer),
                                      newIndexInContainer);
     }
 
     NS_IF_RELEASE(container);
 
--- a/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
+++ b/content/xul/templates/src/nsXULTemplateQueryProcessorXML.cpp
@@ -149,17 +149,17 @@ nsXULTemplateQueryProcessorXML::GetDatas
     nsAutoCString uriStr;
     rv = uri->GetSpec(uriStr);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIContent> root = do_QueryInterface(aRootNode);
     if (!root)
         return NS_ERROR_UNEXPECTED;
 
-    nsCOMPtr<nsIDocument> doc = root->GetCurrentDoc();
+    nsCOMPtr<nsIDocument> doc = root->GetUncomposedDoc();
     if (!doc)
         return NS_ERROR_UNEXPECTED;
 
     nsIPrincipal *docPrincipal = doc->NodePrincipal();
 
     bool hasHadScriptObject = true;
     nsIScriptGlobalObject* scriptObject =
       doc->GetScriptHandlingObject(hasHadScriptObject);
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -182,16 +182,17 @@
 #include "prlog.h"
 #include "prenv.h"
 #include "prprf.h"
 
 #include "mozilla/dom/MessageChannel.h"
 #include "mozilla/dom/MessagePort.h"
 #include "mozilla/dom/MessagePortBinding.h"
 #include "mozilla/dom/indexedDB/IDBFactory.h"
+#include "mozilla/dom/Promise.h"
 
 #include "mozilla/dom/StructuredCloneTags.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/GamepadService.h"
 #endif
 
 #include "nsRefreshDriver.h"
@@ -6365,16 +6366,24 @@ NS_IMETHODIMP
 nsGlobalWindow::Confirm(const nsAString& aString, bool* aReturn)
 {
   ErrorResult rv;
   *aReturn = Confirm(aString, rv);
 
   return rv.ErrorCode();
 }
 
+already_AddRefed<Promise>
+nsGlobalWindow::Fetch(const RequestOrScalarValueString& aInput,
+                      const RequestInit& aInit, ErrorResult& aRv)
+{
+  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  return nullptr;
+}
+
 void
 nsGlobalWindow::Prompt(const nsAString& aMessage, const nsAString& aInitial,
                        nsAString& aReturn, ErrorResult& aError)
 {
   // XXX This method is very similar to nsGlobalWindow::AlertOrConfirm, make
   // sure any modifications here don't need to happen over there!
   FORWARD_TO_OUTER_OR_THROW(Prompt, (aMessage, aInitial, aReturn, aError),
                             aError, );
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -33,18 +33,20 @@
 #include "nsIScriptObjectPrincipal.h"
 #include "nsITimer.h"
 #include "nsIDOMModalContentWindow.h"
 #include "mozilla/EventListenerManager.h"
 #include "nsIPrincipal.h"
 #include "nsSize.h"
 #include "mozFlushType.h"
 #include "prclist.h"
+#include "mozilla/dom/RequestBinding.h"
 #include "mozilla/dom/StorageEvent.h"
 #include "mozilla/dom/StorageEventBinding.h"
+#include "mozilla/dom/UnionTypes.h"
 #include "nsFrameMessageManager.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIIdleObserver.h"
 #include "nsIDocument.h"
 #include "mozilla/dom/EventTarget.h"
 #include "mozilla/dom/WindowBinding.h"
@@ -102,16 +104,17 @@ class BarProp;
 class Console;
 class External;
 class Function;
 class Gamepad;
 class MediaQueryList;
 class MozSelfSupport;
 class Navigator;
 class OwningExternalOrWindowProxy;
+class Promise;
 class Selection;
 class SpeechSynthesis;
 class WakeLock;
 namespace indexedDB {
 class IDBFactory;
 } // namespace indexedDB
 } // namespace dom
 } // namespace mozilla
@@ -845,16 +848,19 @@ public:
 protected:
   bool AlertOrConfirm(bool aAlert, const nsAString& aMessage,
                       mozilla::ErrorResult& aError);
 
 public:
   void Alert(mozilla::ErrorResult& aError);
   void Alert(const nsAString& aMessage, mozilla::ErrorResult& aError);
   bool Confirm(const nsAString& aMessage, mozilla::ErrorResult& aError);
+  already_AddRefed<mozilla::dom::Promise> Fetch(const mozilla::dom::RequestOrScalarValueString& aInput,
+                                                const mozilla::dom::RequestInit& aInit,
+                                                mozilla::ErrorResult& aRv);
   void Prompt(const nsAString& aMessage, const nsAString& aInitial,
               nsAString& aReturn, mozilla::ErrorResult& aError);
   void Print(mozilla::ErrorResult& aError);
   void ShowModalDialog(JSContext* aCx, const nsAString& aUrl,
                        JS::Handle<JS::Value> aArgument,
                        const nsAString& aOptions,
                        JS::MutableHandle<JS::Value> aRetval,
                        mozilla::ErrorResult& aError);
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -878,16 +878,24 @@ DOMInterfaces = {
         '__stringifier': 'ToString'
     }
 },
 
 'Rect': {
     'nativeType': 'nsDOMCSSRect',
 },
 
+'Request': {
+    'binaryNames': { 'headers': 'headers_' },
+},
+
+'Response': {
+    'binaryNames': { 'headers': 'headers_' },
+},
+
 'RGBColor': {
     'nativeType': 'nsDOMCSSRGBColor',
 },
 
 'Screen': {
     'nativeType': 'nsScreen',
 },
 
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1804,17 +1804,18 @@ bool WebGLContext::TexImageFromVideoElem
     bool dimensionsMatch = info.Width() == srcImage->GetSize().width &&
                            info.Height() == srcImage->GetSize().height;
     if (!dimensionsMatch) {
         // we need to allocation
         gl->fTexImage2D(texImageTarget.get(), level, internalformat, srcImage->GetSize().width, srcImage->GetSize().height, 0, format, type, nullptr);
     }
     bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage.get(), srcImage->GetSize(), tex->GLName(), texImageTarget.get(), mPixelStoreFlipY);
     if (ok) {
-        tex->SetImageInfo(texImageTarget, level, srcImage->GetSize().width, srcImage->GetSize().height, format, type, WebGLImageDataStatus::InitializedImageData);
+        tex->SetImageInfo(texImageTarget, level, srcImage->GetSize().width, srcImage->GetSize().height, internalformat, type,
+                          WebGLImageDataStatus::InitializedImageData);
         tex->Bind(TexImageTargetToTexTarget(texImageTarget));
     }
     srcImage = nullptr;
     container->UnlockCurrentImage();
     return ok;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -12,16 +12,17 @@
 #include "mozilla/LinkedList.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
 #include "GLDefs.h"
 #include "WebGLActiveInfo.h"
 #include "WebGLObjectModel.h"
 #include "WebGLRenderbuffer.h"
+#include "WebGLTexture.h"
 #include "WebGLStrongTypes.h"
 #include <stdarg.h>
 
 #include "nsTArray.h"
 #include "nsCycleCollectionNoteChild.h"
 
 #include "nsIDOMWebGLRenderingContext.h"
 #include "nsICanvasRenderingContextInternal.h"
@@ -216,16 +217,17 @@ public:
 
     /**
      * Return displayable name for GLenum.
      * This version is like gl::GLenumToStr but with out the GL_ prefix to
      * keep consistency with how errors are reported from WebGL.
      */
     static const char *EnumName(GLenum glenum);
 
+    bool IsCompressedTextureFormat(GLenum format);
     bool IsTextureFormatCompressed(TexInternalFormat format);
 
     void DummyFramebufferOperation(const char *info);
 
     WebGLTexture* activeBoundTextureForTarget(const TexTarget texTarget) const {
         return texTarget == LOCAL_GL_TEXTURE_2D ? mBound2DTextures[mActiveTexture]
                                                 : mBoundCubeMapTextures[mActiveTexture];
     }
@@ -538,19 +540,22 @@ public:
                        const Nullable<dom::ArrayBufferView> &pixels,
                        ErrorResult& rv);
     void TexSubImage2D(GLenum target, GLint level,
                        GLint xoffset, GLint yoffset, GLenum format,
                        GLenum type, dom::ImageData* pixels, ErrorResult& rv);
     // Allow whatever element types the bindings are willing to pass
     // us in TexSubImage2D
     template<class ElementType>
-    void TexSubImage2D(GLenum rawTexImageTarget, GLint level,
-                       GLint xoffset, GLint yoffset, GLenum format,
-                       GLenum type, ElementType& elt, ErrorResult& rv)
+    void TexSubImage2D(GLenum rawTexImageTarget,
+                       GLint level,
+                       GLint xoffset, GLint yoffset,
+                       GLenum format,
+                       GLenum type,
+                       ElementType& elt, ErrorResult& rv)
     {
         if (IsContextLost())
             return;
 
         if (!ValidateTexImageTarget(2, rawTexImageTarget, WebGLTexImageFunc::TexSubImage))
             return ErrorInvalidEnumInfo("texSubImage2D: target", rawTexImageTarget);
 
         const TexImageTarget texImageTarget(rawTexImageTarget);
@@ -560,32 +565,41 @@ public:
 
         if (level < 0)
             return ErrorInvalidValue("texSubImage2D: level is negative");
 
         const int32_t maxLevel = MaxTextureLevelForTexImageTarget(texImageTarget);
         if (level > maxLevel)
             return ErrorInvalidValue("texSubImage2D: level %d is too large, max is %d", level, maxLevel);
 
+        WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
+        if (!tex) {
+            return ErrorInvalidOperation("texSubImage2D: no texture bound on active texture unit");
+        }
+        const WebGLTexture::ImageInfo &imageInfo = tex->ImageInfoAt(texImageTarget, level);
+        const TexInternalFormat internalformat = imageInfo.InternalFormat();
+
         // Trying to handle the video by GPU directly first
-        if (TexImageFromVideoElement(texImageTarget, level, format, format, type, elt)) {
+        if (TexImageFromVideoElement(texImageTarget, level,
+                                     internalformat.get(), format, type, elt))
+        {
             return;
         }
 
         RefPtr<gfx::DataSourceSurface> data;
         WebGLTexelFormat srcFormat;
         nsLayoutUtils::SurfaceFromElementResult res = SurfaceFromElement(elt);
         rv = SurfaceFromElementResultToImageSurface(res, data,
                                                     &srcFormat);
         if (rv.Failed() || !data)
             return;
 
         gfx::IntSize size = data->GetSize();
         uint32_t byteLength = data->Stride() * size.height;
-        return TexSubImage2D_base(texImageTarget, level, xoffset, yoffset,
+        return TexSubImage2D_base(texImageTarget.get(), level, xoffset, yoffset,
                                   size.width, size.height,
                                   data->Stride(), format, type,
                                   data->GetData(), byteLength,
                                   -1, srcFormat, mPixelStorePremultiplyAlpha);
 
     }
 
     void Uniform1i(WebGLUniformLocation* location, GLint x);
@@ -1068,17 +1082,17 @@ protected:
 
     virtual bool IsWebGL2() const = 0;
 
     bool InitWebGL2();
 
 
     // -------------------------------------------------------------------------
     // Validation functions (implemented in WebGLContextValidate.cpp)
-    GLenum BaseTexFormat(GLenum internalFormat) const;
+    TexInternalFormat BaseTexFormat(TexInternalFormat internalFormat) const;
 
     bool CreateOffscreenGL(bool forceEnabled);
     bool InitAndValidateGL();
     bool ResizeBackbuffer(uint32_t width, uint32_t height);
     bool ValidateBlendEquationEnum(GLenum cap, const char *info);
     bool ValidateBlendFuncDstEnum(GLenum mode, const char *info);
     bool ValidateBlendFuncSrcEnum(GLenum mode, const char *info);
     bool ValidateBlendFuncEnumsCompatibility(GLenum sfactor, GLenum dfactor, const char *info);
@@ -1090,63 +1104,74 @@ protected:
     bool ValidateDrawModeEnum(GLenum mode, const char *info);
     bool ValidateAttribIndex(GLuint index, const char *info);
     bool ValidateStencilParamsForDrawCall();
 
     bool ValidateGLSLVariableName(const nsAString& name, const char *info);
     bool ValidateGLSLCharacter(char16_t c);
     bool ValidateGLSLString(const nsAString& string, const char *info);
 
-    bool ValidateCopyTexImage(GLenum format, WebGLTexImageFunc func);
+    bool ValidateCopyTexImage(GLenum internalformat,
+                              WebGLTexImageFunc func);
     bool ValidateTexImage(GLuint dims, TexImageTarget texImageTarget,
                           GLint level, GLenum internalFormat,
                           GLint xoffset, GLint yoffset, GLint zoffset,
                           GLint width, GLint height, GLint depth,
                           GLint border, GLenum format, GLenum type,
                           WebGLTexImageFunc func);
     bool ValidateTexImageTarget(GLuint dims, GLenum target, WebGLTexImageFunc func);
-    bool ValidateTexImageFormat(GLenum format, WebGLTexImageFunc func);
+    bool ValidateTexImageFormat(GLenum internalformat,
+                                WebGLTexImageFunc func);
     bool ValidateTexImageType(GLenum type, WebGLTexImageFunc func);
     bool ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTexImageFunc func);
+    bool ValidateCompTexImageInternalFormat(GLenum format,
+                                            WebGLTexImageFunc func);
+    bool ValidateCopyTexImageInternalFormat(GLenum format,
+                                            WebGLTexImageFunc func);
     bool ValidateTexImageSize(TexImageTarget target, GLint level,
                               GLint width, GLint height, GLint depth,
                               WebGLTexImageFunc func);
     bool ValidateTexSubImageSize(GLint x, GLint y, GLint z,
                                  GLsizei width, GLsizei height, GLsizei depth,
                                  GLsizei baseWidth, GLsizei baseHeight, GLsizei baseDepth,
                                  WebGLTexImageFunc func);
 
-    bool ValidateCompTexImageSize(GLint level, GLenum format,
+    bool ValidateCompTexImageSize(GLint level,
+                                  GLenum internalformat,
                                   GLint xoffset, GLint yoffset,
                                   GLsizei width, GLsizei height,
                                   GLsizei levelWidth, GLsizei levelHeight,
                                   WebGLTexImageFunc func);
-    bool ValidateCompTexImageDataSize(GLint level, GLenum format,
+    bool ValidateCompTexImageDataSize(GLint level,
+                                      GLenum internalformat,
                                       GLsizei width, GLsizei height,
                                       uint32_t byteLength, WebGLTexImageFunc func);
 
-
     static uint32_t GetBitsPerTexel(TexInternalFormat format, TexType type);
 
     void Invalidate();
     void DestroyResourcesAndContext();
 
     void MakeContextCurrent() const;
 
     // helpers
-    void TexImage2D_base(TexImageTarget target, GLint level, GLenum internalformat,
+    void TexImage2D_base(TexImageTarget target,
+                         GLint level,
+                         GLenum internalformat,
                          GLsizei width, GLsizei height, GLsizei srcStrideOrZero, GLint border,
-                         GLenum format, GLenum type,
+                         GLenum format,
+                         GLenum type,
                          void *data, uint32_t byteLength,
                          int jsArrayType,
                          WebGLTexelFormat srcFormat, bool srcPremultiplied);
     void TexSubImage2D_base(TexImageTarget target, GLint level,
                             GLint xoffset, GLint yoffset,
                             GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
-                            GLenum format, GLenum type,
+                            GLenum format,
+                            GLenum type,
                             void *pixels, uint32_t byteLength,
                             int jsArrayType,
                             WebGLTexelFormat srcFormat, bool srcPremultiplied);
     void TexParameter_base(GLenum target, GLenum pname,
                            GLint *intParamPtr, GLfloat *floatParamPtr);
 
     void ConvertImage(size_t width, size_t height, size_t srcStride, size_t dstStride,
                       const uint8_t* src, uint8_t *dst,
@@ -1222,22 +1247,22 @@ protected:
     GLenum CheckedBufferData(GLenum target,
                              GLsizeiptr size,
                              const GLvoid *data,
                              GLenum usage);
     /** like glTexImage2D but if the call may change the texture size, checks any GL error generated
      * by this glTexImage2D call and returns it */
     GLenum CheckedTexImage2D(TexImageTarget texImageTarget,
                              GLint level,
-                             GLenum internalFormat,
+                             TexInternalFormat internalFormat,
                              GLsizei width,
                              GLsizei height,
                              GLint border,
-                             GLenum format,
-                             GLenum type,
+                             TexFormat format,
+                             TexType type,
                              const GLvoid *data);
 
     void ForceLoseContext(bool simulateLosing = false);
     void ForceRestoreContext();
 
     nsTArray<WebGLRefPtr<WebGLTexture> > mBound2DTextures;
     nsTArray<WebGLRefPtr<WebGLTexture> > mBoundCubeMapTextures;
 
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -671,17 +671,17 @@ WebGLContext::BindFakeBlackTexturesHelpe
         WebGLTextureFakeBlackStatus s = boundTexturesArray[i]->ResolvedFakeBlackStatus();
         MOZ_ASSERT(s != WebGLTextureFakeBlackStatus::Unknown);
 
         if (MOZ_LIKELY(s == WebGLTextureFakeBlackStatus::NotNeeded)) {
             continue;
         }
 
         bool alpha = s == WebGLTextureFakeBlackStatus::UninitializedImageData &&
-                     FormatHasAlpha(boundTexturesArray[i]->ImageInfoBase().WebGLFormat());
+                     FormatHasAlpha(boundTexturesArray[i]->ImageInfoBase().InternalFormat());
         UniquePtr<FakeBlackTexture>&
             blackTexturePtr = alpha
                               ? transparentTextureScopedPtr
                               : opaqueTextureScopedPtr;
 
         if (!blackTexturePtr) {
             GLenum format = alpha ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
             blackTexturePtr = MakeUnique<FakeBlackTexture>(gl, target, format);
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -370,17 +370,18 @@ WebGLContext::CopyTexSubImage2D_base(Tex
     const char* info = sub ? "copyTexSubImage2D" : "copyTexImage2D";
     WebGLTexImageFunc func = sub ? WebGLTexImageFunc::CopyTexSubImage : WebGLTexImageFunc::CopyTexImage;
 
     // TODO: This changes with color_buffer_float. Reassess when the
     // patch lands.
     if (!ValidateTexImage(2, texImageTarget, level, internalformat,
                           xoffset, yoffset, 0,
                           width, height, 0,
-                          0, internalformat, LOCAL_GL_UNSIGNED_BYTE,
+                          0,
+                          LOCAL_GL_NONE, LOCAL_GL_NONE,
                           func))
     {
         return;
     }
 
     if (!ValidateCopyTexImage(internalformat, func))
         return;
 
@@ -473,65 +474,65 @@ WebGLContext::CopyTexImage2D(GLenum rawT
                              GLsizei height,
                              GLint border)
 {
     if (IsContextLost())
         return;
 
     // copyTexImage2D only generates textures with type = UNSIGNED_BYTE
     const WebGLTexImageFunc func = WebGLTexImageFunc::CopyTexImage;
-    const GLenum format = internalformat; // WebGL/ES Format
-    const GLenum type = LOCAL_GL_UNSIGNED_BYTE; // WebGL/ES Format
 
     if (!ValidateTexImageTarget(2, rawTexImgTarget, WebGLTexImageFunc::CopyTexImage))
         return;
 
-    if (!ValidateTexImage(2, rawTexImgTarget, level, format,
+    if (!ValidateTexImage(2, rawTexImgTarget, level, internalformat,
                           0, 0, 0,
                           width, height, 0,
-                          border, format, type,
+                          border, LOCAL_GL_NONE, LOCAL_GL_NONE,
                           func))
     {
         return;
     }
 
-    if (!ValidateCopyTexImage(format, func))
+    if (!ValidateCopyTexImage(internalformat, func))
         return;
 
     if (!mBoundFramebuffer)
         ClearBackbufferIfNeeded();
 
     const TexImageTarget texImageTarget(rawTexImgTarget);
 
     // check if the memory size of this texture may change with this call
     bool sizeMayChange = true;
     WebGLTexture* tex = activeBoundTextureForTexImageTarget(texImageTarget);
     if (tex->HasImageInfoAt(texImageTarget, level)) {
         const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
 
         sizeMayChange = width != imageInfo.Width() ||
                         height != imageInfo.Height() ||
-                        format != imageInfo.WebGLFormat() ||
-                        type != imageInfo.WebGLType();
+                        internalformat != imageInfo.InternalFormat();
     }
 
     if (sizeMayChange)
         GetAndFlushUnderlyingGLErrors();
 
-    CopyTexSubImage2D_base(texImageTarget, level, format, 0, 0, x, y, width, height, false);
+    CopyTexSubImage2D_base(texImageTarget, level, internalformat, 0, 0, x, y, width, height, false);
 
     if (sizeMayChange) {
         GLenum error = GetAndFlushUnderlyingGLErrors();
         if (error) {
             GenerateWarning("copyTexImage2D generated error %s", ErrorName(error));
             return;
         }
     }
 
-    tex->SetImageInfo(texImageTarget, level, width, height, format, type,
+    tex->SetImageInfo(texImageTarget, level, width, height,
+                      internalformat,
+                      LOCAL_GL_UNSIGNED_BYTE, /* dummy, artifact of us storing
+                                               the wrong data in ImageInfo */
                       WebGLImageDataStatus::InitializedImageData);
 }
 
 void
 WebGLContext::CopyTexSubImage2D(GLenum rawTexImgTarget,
                                 GLint level,
                                 GLint xoffset,
                                 GLint yoffset,
@@ -573,34 +574,34 @@ WebGLContext::CopyTexSubImage2D(GLenum r
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
     if (!tex)
         return ErrorInvalidOperation("copyTexSubImage2D: no texture bound to this target");
 
     if (!tex->HasImageInfoAt(texImageTarget, level))
         return ErrorInvalidOperation("copyTexSubImage2D: no texture image previously defined for this level and face");
 
-    const WebGLTexture::ImageInfo &imageInfo = tex->ImageInfoAt(texImageTarget, level);
+    const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
     GLsizei texWidth = imageInfo.Width();
     GLsizei texHeight = imageInfo.Height();
 
     if (xoffset + width > texWidth || xoffset + width < 0)
       return ErrorInvalidValue("copyTexSubImage2D: xoffset+width is too large");
 
     if (yoffset + height > texHeight || yoffset + height < 0)
       return ErrorInvalidValue("copyTexSubImage2D: yoffset+height is too large");
 
     if (!mBoundFramebuffer)
         ClearBackbufferIfNeeded();
 
     if (imageInfo.HasUninitializedImageData()) {
         tex->DoDeferredImageInitialization(texImageTarget, level);
     }
 
-    return CopyTexSubImage2D_base(texImageTarget, level, imageInfo.WebGLFormat().get(), xoffset, yoffset, x, y, width, height, true);
+    return CopyTexSubImage2D_base(texImageTarget, level, imageInfo.InternalFormat().get(), xoffset, yoffset, x, y, width, height, true);
 }
 
 
 already_AddRefed<WebGLProgram>
 WebGLContext::CreateProgram()
 {
     if (IsContextLost())
         return nullptr;
@@ -910,22 +911,22 @@ WebGLContext::GenerateMipmap(GLenum rawT
     if (!tex->HasImageInfoAt(imageTarget, 0))
     {
         return ErrorInvalidOperation("generateMipmap: Level zero of texture is not defined.");
     }
 
     if (!tex->IsFirstImagePowerOfTwo())
         return ErrorInvalidOperation("generateMipmap: Level zero of texture does not have power-of-two width and height.");
 
-    TexInternalFormat webGLFormat = tex->ImageInfoAt(imageTarget, 0).WebGLFormat();
-    if (IsTextureFormatCompressed(webGLFormat))
+    TexInternalFormat internalformat = tex->ImageInfoAt(imageTarget, 0).InternalFormat();
+    if (IsTextureFormatCompressed(internalformat))
         return ErrorInvalidOperation("generateMipmap: Texture data at level zero is compressed.");
 
     if (IsExtensionEnabled(WebGLExtensionID::WEBGL_depth_texture) &&
-        (IsGLDepthFormat(webGLFormat) || IsGLDepthStencilFormat(webGLFormat)))
+        (IsGLDepthFormat(internalformat) || IsGLDepthStencilFormat(internalformat)))
     {
         return ErrorInvalidOperation("generateMipmap: "
                                      "A texture that has a base internal format of "
                                      "DEPTH_COMPONENT or DEPTH_STENCIL isn't supported");
     }
 
     if (!tex->AreAllLevel0ImageInfosEqual())
         return ErrorInvalidOperation("generateMipmap: The six faces of this cube map have different dimensions, format, or type.");
@@ -1171,20 +1172,20 @@ WebGLContext::GetFramebufferAttachmentPa
         }
 
         ErrorInvalidEnumInfo("getFramebufferAttachmentParameter: pname", pname);
         return JS::NullValue();
     } else if (fba.Texture()) {
         switch (pname) {
              case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING_EXT:
                 if (IsExtensionEnabled(WebGLExtensionID::EXT_sRGB)) {
-                    const TexInternalFormat webGLFormat =
-                        fba.Texture()->ImageInfoBase().WebGLFormat();
-                    return (webGLFormat == LOCAL_GL_SRGB ||
-                            webGLFormat == LOCAL_GL_SRGB_ALPHA) ?
+                    const TexInternalFormat internalformat =
+                        fba.Texture()->ImageInfoBase().InternalFormat();
+                    return (internalformat == LOCAL_GL_SRGB ||
+                            internalformat == LOCAL_GL_SRGB_ALPHA) ?
                         JS::NumberValue(uint32_t(LOCAL_GL_SRGB)) :
                         JS::NumberValue(uint32_t(LOCAL_GL_LINEAR));
                 }
                 break;
 
             case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
                 return JS::NumberValue(uint32_t(LOCAL_GL_TEXTURE));
 
@@ -1214,17 +1215,17 @@ WebGLContext::GetFramebufferAttachmentPa
                     return JS::NullValue();
                 }
 
                 if (!fba.IsComplete())
                     return JS::NumberValue(uint32_t(LOCAL_GL_NONE));
 
                 uint32_t ret = LOCAL_GL_NONE;
                 TexType type = fba.Texture()->ImageInfoAt(fba.ImageTarget(),
-                                                          fba.MipLevel()).WebGLType();
+                                                          fba.MipLevel()).Type();
                 switch (type.get()) {
                 case LOCAL_GL_UNSIGNED_BYTE:
                 case LOCAL_GL_UNSIGNED_SHORT_4_4_4_4:
                 case LOCAL_GL_UNSIGNED_SHORT_5_5_5_1:
                 case LOCAL_GL_UNSIGNED_SHORT_5_6_5:
                     ret = LOCAL_GL_UNSIGNED_NORMALIZED;
                     break;
                 case LOCAL_GL_FLOAT:
@@ -3293,31 +3294,34 @@ WebGLContext::CompileShader(WebGLShader 
 
     gl->fCompileShader(shadername);
     GLint ok;
     gl->fGetShaderiv(shadername, LOCAL_GL_COMPILE_STATUS, &ok);
     shader->SetCompileStatus(ok);
 }
 
 void
-WebGLContext::CompressedTexImage2D(GLenum rawTexImgTarget, GLint level, GLenum internalformat,
+WebGLContext::CompressedTexImage2D(GLenum rawTexImgTarget,
+                                   GLint level,
+                                   GLenum internalformat,
                                    GLsizei width, GLsizei height, GLint border,
                                    const ArrayBufferView& view)
 {
     if (IsContextLost())
         return;
 
     const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexImage;
 
     if (!ValidateTexImageTarget(2, rawTexImgTarget, WebGLTexImageFunc::CompTexImage))
         return;
 
     if (!ValidateTexImage(2, rawTexImgTarget, level, internalformat,
                           0, 0, 0, width, height, 0,
-                          border, internalformat, LOCAL_GL_UNSIGNED_BYTE,
+                          border, LOCAL_GL_NONE,
+                          LOCAL_GL_NONE,
                           func))
     {
         return;
     }
 
     view.ComputeLengthAndData();
 
     uint32_t byteLength = view.Length();
@@ -3338,62 +3342,67 @@ WebGLContext::CompressedTexImage2D(GLenu
     MOZ_ASSERT(tex);
     tex->SetImageInfo(texImageTarget, level, width, height, internalformat, LOCAL_GL_UNSIGNED_BYTE,
                       WebGLImageDataStatus::InitializedImageData);
 }
 
 void
 WebGLContext::CompressedTexSubImage2D(GLenum rawTexImgTarget, GLint level, GLint xoffset,
                                       GLint yoffset, GLsizei width, GLsizei height,
-                                      GLenum format, const ArrayBufferView& view)
+                                      GLenum internalformat,
+                                      const ArrayBufferView& view)
 {
     if (IsContextLost())
         return;
 
     const WebGLTexImageFunc func = WebGLTexImageFunc::CompTexSubImage;
 
     if (!ValidateTexImageTarget(2, rawTexImgTarget, WebGLTexImageFunc::CompTexSubImage))
         return;
 
     if (!ValidateTexImage(2, rawTexImgTarget,
-                          level, format,
+                          level, internalformat,
                           xoffset, yoffset, 0,
                           width, height, 0,
-                          0, format, LOCAL_GL_UNSIGNED_BYTE,
+                          0, LOCAL_GL_NONE, LOCAL_GL_NONE,
                           func))
     {
         return;
     }
 
     const TexImageTarget texImageTarget(rawTexImgTarget);
 
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
     MOZ_ASSERT(tex);
     WebGLTexture::ImageInfo& levelInfo = tex->ImageInfoAt(texImageTarget, level);
 
+    if (internalformat != levelInfo.InternalFormat()) {
+        return ErrorInvalidOperation("compressedTexImage2D: internalformat does not match the existing image");
+    }
+
     view.ComputeLengthAndData();
 
     uint32_t byteLength = view.Length();
-    if (!ValidateCompTexImageDataSize(level, format, width, height, byteLength, func))
+    if (!ValidateCompTexImageDataSize(level, internalformat, width, height, byteLength, func))
         return;
 
-    if (!ValidateCompTexImageSize(level, format,
+    if (!ValidateCompTexImageSize(level, internalformat,
                                   xoffset, yoffset,
                                   width, height,
                                   levelInfo.Width(), levelInfo.Height(),
                                   func))
     {
         return;
     }
 
     if (levelInfo.HasUninitializedImageData())
         tex->DoDeferredImageInitialization(texImageTarget, level);
 
     MakeContextCurrent();
-    gl->fCompressedTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, width, height, format, byteLength, view.Data());
+    gl->fCompressedTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, width, height, internalformat, byteLength, view.Data());
 }
 
 JS::Value
 WebGLContext::GetShaderParameter(WebGLShader *shader, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
@@ -3572,63 +3581,63 @@ WebGLContext::GetShaderTranslatedSource(
     if (!ValidateObject("getShaderTranslatedSource: shader", shader))
         return;
 
     retval.Assign(shader->TranslatedSource());
 }
 
 GLenum WebGLContext::CheckedTexImage2D(TexImageTarget texImageTarget,
                                        GLint level,
-                                       GLenum internalFormat,
+                                       TexInternalFormat internalFormat,
                                        GLsizei width,
                                        GLsizei height,
                                        GLint border,
-                                       GLenum format,
-                                       GLenum type,
+                                       TexFormat format,
+                                       TexType type,
                                        const GLvoid *data)
 {
-    MOZ_ASSERT(internalFormat == format);
     WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
     MOZ_ASSERT(tex != nullptr, "no texture bound");
 
     bool sizeMayChange = true;
 
     if (tex->HasImageInfoAt(texImageTarget, level)) {
         const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
         sizeMayChange = width != imageInfo.Width() ||
                         height != imageInfo.Height() ||
-                        format != imageInfo.WebGLFormat() ||
-                        type != imageInfo.WebGLType();
+                        internalFormat != imageInfo.InternalFormat();
     }
 
     // Convert to format and type required by OpenGL 'driver'.
     GLenum driverType = DriverTypeFromType(gl, type);
     GLenum driverInternalFormat = LOCAL_GL_NONE;
     GLenum driverFormat = LOCAL_GL_NONE;
-    DriverFormatsFromFormatAndType(gl, format, type, &driverInternalFormat, &driverFormat);
+    DriverFormatsFromFormatAndType(gl, internalFormat, type, &driverInternalFormat, &driverFormat);
 
     if (sizeMayChange) {
         GetAndFlushUnderlyingGLErrors();
     }
 
     gl->fTexImage2D(texImageTarget.get(), level, driverInternalFormat, width, height, border, driverFormat, driverType, data);
 
     GLenum error = LOCAL_GL_NO_ERROR;
     if (sizeMayChange) {
         error = GetAndFlushUnderlyingGLErrors();
     }
 
     return error;
 }
 
 void
-WebGLContext::TexImage2D_base(TexImageTarget texImageTarget, GLint level, GLenum internalformat,
+WebGLContext::TexImage2D_base(TexImageTarget texImageTarget, GLint level,
+                              GLenum internalformat,
                               GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
                               GLint border,
-                              GLenum format, GLenum type,
+                              GLenum format,
+                              GLenum type,
                               void* data, uint32_t byteLength,
                               int jsArrayType, // a TypedArray format enum, or -1 if not relevant
                               WebGLTexelFormat srcFormat, bool srcPremultiplied)
 {
     const WebGLTexImageFunc func = WebGLTexImageFunc::TexImage;
 
     if (!ValidateTexImage(2, texImageTarget, level, internalformat,
                           0, 0, 0,
@@ -3647,17 +3656,17 @@ WebGLContext::TexImage2D_base(TexImageTa
                                          "with format of DEPTH_COMPONENT or DEPTH_STENCIL, "
                                          "data must be nullptr, "
                                          "level must be zero");
     }
 
     if (!ValidateTexInputData(type, jsArrayType, func))
         return;
 
-    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(format, type);
+    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(internalformat, type);
     WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;
 
     uint32_t srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(actualSrcFormat);
 
     CheckedUint32 checked_neededByteLength =
         GetImageSize(height, width, srcTexelSize, mPixelStoreUnpackAlignment);
 
     CheckedUint32 checked_plainRowSize = CheckedUint32(width) * srcTexelSize;
@@ -3681,17 +3690,17 @@ WebGLContext::TexImage2D_base(TexImageTa
     MakeContextCurrent();
 
     nsAutoArrayPtr<uint8_t> convertedData;
     void* pixels = nullptr;
     WebGLImageDataStatus imageInfoStatusIfSuccess = WebGLImageDataStatus::UninitializedImageData;
 
     if (byteLength) {
         size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
-        uint32_t dstTexelSize = GetBitsPerTexel(format, type) / 8;
+        uint32_t dstTexelSize = GetBitsPerTexel(internalformat, type) / 8;
         size_t   dstPlainRowSize = dstTexelSize * width;
         size_t   unpackAlignment = mPixelStoreUnpackAlignment;
         size_t   dstStride = ((dstPlainRowSize + unpackAlignment-1) / unpackAlignment) * unpackAlignment;
 
         if (actualSrcFormat == dstFormat &&
             srcPremultiplied == mPixelStorePremultiplyAlpha &&
             srcStride == dstStride &&
             !mPixelStoreFlipY)
@@ -3720,17 +3729,17 @@ WebGLContext::TexImage2D_base(TexImageTa
         return;
     }
 
     // in all of the code paths above, we should have either initialized data,
     // or allocated data and left it uninitialized, but in any case we shouldn't
     // have NoImageData at this point.
     MOZ_ASSERT(imageInfoStatusIfSuccess != WebGLImageDataStatus::NoImageData);
 
-    tex->SetImageInfo(texImageTarget, level, width, height, format, type, imageInfoStatusIfSuccess);
+    tex->SetImageInfo(texImageTarget, level, width, height, internalformat, type, imageInfoStatusIfSuccess);
 }
 
 void
 WebGLContext::TexImage2D(GLenum rawTarget, GLint level,
                          GLenum internalformat, GLsizei width,
                          GLsizei height, GLint border, GLenum format,
                          GLenum type, const Nullable<ArrayBufferView> &pixels, ErrorResult& rv)
 {
@@ -3798,28 +3807,39 @@ WebGLContext::TexSubImage2D_base(TexImag
                                  GLsizei width, GLsizei height, GLsizei srcStrideOrZero,
                                  GLenum format, GLenum type,
                                  void* data, uint32_t byteLength,
                                  int jsArrayType,
                                  WebGLTexelFormat srcFormat, bool srcPremultiplied)
 {
     const WebGLTexImageFunc func = WebGLTexImageFunc::TexSubImage;
 
-    if (!ValidateTexImage(2, texImageTarget, level, format,
+    WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
+    if (!tex) {
+        return ErrorInvalidOperation("texSubImage2D: no texture bound on active texture unit");
+    }
+    const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
+    const TexInternalFormat internalformat =  imageInfo.InternalFormat();
+
+    if (!ValidateTexImage(2, texImageTarget, level, internalformat.get(),
                           xoffset, yoffset, 0,
                           width, height, 0,
                           0, format, type, func))
     {
         return;
     }
 
     if (!ValidateTexInputData(type, jsArrayType, func))
         return;
 
-    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(format, type);
+    if (imageInfo.Type() != type) {
+        return ErrorInvalidOperation("texSubImage2D: type parameter does not match the existing image");
+    }
+
+    WebGLTexelFormat dstFormat = GetWebGLTexelFormat(internalformat, type);
     WebGLTexelFormat actualSrcFormat = srcFormat == WebGLTexelFormat::Auto ? dstFormat : srcFormat;
 
     uint32_t srcTexelSize = WebGLTexelConversions::TexelBytesForFormat(actualSrcFormat);
 
     if (width == 0 || height == 0)
         return; // ES 2.0 says it has no effect, we better return right now
 
     CheckedUint32 checked_neededByteLength =
@@ -3833,26 +3853,23 @@ WebGLContext::TexSubImage2D_base(TexImag
     if (!checked_neededByteLength.isValid())
         return ErrorInvalidOperation("texSubImage2D: integer overflow computing the needed buffer size");
 
     uint32_t bytesNeeded = checked_neededByteLength.value();
 
     if (byteLength < bytesNeeded)
         return ErrorInvalidOperation("texSubImage2D: not enough data for operation (need %d, have %d)", bytesNeeded, byteLength);
 
-    WebGLTexture *tex = activeBoundTextureForTexImageTarget(texImageTarget);
-    const WebGLTexture::ImageInfo &imageInfo = tex->ImageInfoAt(texImageTarget, level);
-
     if (imageInfo.HasUninitializedImageData())
         tex->DoDeferredImageInitialization(texImageTarget, level);
 
     MakeContextCurrent();
 
     size_t   srcStride = srcStrideOrZero ? srcStrideOrZero : checked_alignedRowSize.value();
-    uint32_t dstTexelSize = GetBitsPerTexel(format, type) / 8;
+    uint32_t dstTexelSize = GetBitsPerTexel(internalformat, type) / 8;
     size_t   dstPlainRowSize = dstTexelSize * width;
     // There are checks above to ensure that this won't overflow.
     size_t   dstStride = RoundedToNextMultipleOf(dstPlainRowSize, mPixelStoreUnpackAlignment).value();
 
     void* pixels = data;
     nsAutoArrayPtr<uint8_t> convertedData;
 
     // no conversion, no flipping, so we avoid copying anything and just pass the source pointer
@@ -3869,17 +3886,17 @@ WebGLContext::TexSubImage2D_base(TexImag
                     actualSrcFormat, srcPremultiplied,
                     dstFormat, mPixelStorePremultiplyAlpha, dstTexelSize);
         pixels = reinterpret_cast<void*>(convertedData.get());
     }
 
     GLenum driverType = DriverTypeFromType(gl, type);
     GLenum driverInternalFormat = LOCAL_GL_NONE;
     GLenum driverFormat = LOCAL_GL_NONE;
-    DriverFormatsFromFormatAndType(gl, format, type, &driverInternalFormat, &driverFormat);
+    DriverFormatsFromFormatAndType(gl, internalformat, type, &driverInternalFormat, &driverFormat);
 
     gl->fTexSubImage2D(texImageTarget.get(), level, xoffset, yoffset, width, height, driverFormat, driverType, pixels);
 }
 
 void
 WebGLContext::TexSubImage2D(GLenum rawTarget, GLint level,
                             GLint xoffset, GLint yoffset,
                             GLsizei width, GLsizei height,
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -485,21 +485,20 @@ WebGLContext::EnumName(GLenum glenum)
         XX(UNSIGNED_SHORT_5_5_5_1);
         XX(UNSIGNED_SHORT_5_6_5);
 #undef XX
     }
 
     return "[Unknown enum name]";
 }
 
-
 bool
-WebGLContext::IsTextureFormatCompressed(TexInternalFormat format)
+WebGLContext::IsCompressedTextureFormat(GLenum format)
 {
-    switch (format.get()) {
+    switch (format) {
         case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
         case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
         case LOCAL_GL_ATC_RGB:
         case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
         case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
         case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
@@ -508,16 +507,23 @@ WebGLContext::IsTextureFormatCompressed(
         case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
         case LOCAL_GL_ETC1_RGB8_OES:
             return true;
         default:
             return false;
     }
 }
 
+
+bool
+WebGLContext::IsTextureFormatCompressed(TexInternalFormat format)
+{
+    return IsCompressedTextureFormat(format.get());
+}
+
 GLenum
 WebGLContext::GetAndFlushUnderlyingGLErrors()
 {
     // Get and clear GL error in ALL cases.
     GLenum error = gl->GetAndClearError();
 
     // Only store in mUnderlyingGLError if is hasn't already recorded an
     // error.
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -77,16 +77,23 @@ InfoFrom(WebGLTexImageFunc func)
     case WebGLTexImageFunc::CompTexImage:    return "compressedTexImage2D";
     case WebGLTexImageFunc::CompTexSubImage: return "compressedTexSubImage2D";
     default:
         MOZ_ASSERT(false, "Missing case for WebGLTexImageSource");
         return "(error)";
     }
 }
 
+static bool
+IsCompressedFunc(WebGLTexImageFunc func)
+{
+    return func == WebGLTexImageFunc::CompTexImage ||
+           func == WebGLTexImageFunc::CompTexSubImage;
+}
+
 /**
  * Same as ErrorInvalidEnum but uses WebGLContext::EnumName to print displayable
  * name for \a glenum.
  */
 static void
 ErrorInvalidEnumWithName(WebGLContext* ctx, const char* msg, GLenum glenum, WebGLTexImageFunc func)
 {
     const char* name = WebGLContext::EnumName(glenum);
@@ -243,18 +250,18 @@ WebGLProgram::UpdateInfo()
 }
 
 /**
  * Return the simple base format for a given internal format.
  *
  * \return the corresponding \u base internal format (GL_ALPHA, GL_LUMINANCE,
  * GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA), or GL_NONE if invalid enum.
  */
-GLenum
-WebGLContext::BaseTexFormat(GLenum internalFormat) const
+TexInternalFormat
+WebGLContext::BaseTexFormat(TexInternalFormat internalFormat) const
 {
     if (internalFormat == LOCAL_GL_ALPHA ||
         internalFormat == LOCAL_GL_LUMINANCE ||
         internalFormat == LOCAL_GL_LUMINANCE_ALPHA ||
         internalFormat == LOCAL_GL_RGB ||
         internalFormat == LOCAL_GL_RGBA)
     {
         return internalFormat;
@@ -603,73 +610,28 @@ WebGLContext::ValidateTexImageFormat(GLe
     {
         bool validFormat = IsExtensionEnabled(WebGLExtensionID::EXT_sRGB);
         if (!validFormat)
             ErrorInvalidEnum("%s: invalid format %s: need EXT_sRGB enabled",
                              InfoFrom(func), WebGLContext::EnumName(format));
         return validFormat;
     }
 
-    /* WEBGL_compressed_texture_atc added formats */
-    if (format == LOCAL_GL_ATC_RGB ||
-        format == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA ||
-        format == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA)
-    {
-        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_atc);
-        if (!validFormat)
-            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_atc enabled",
-                             InfoFrom(func), WebGLContext::EnumName(format));
-        return validFormat;
-    }
-
-    // WEBGL_compressed_texture_etc1
-    if (format == LOCAL_GL_ETC1_RGB8_OES) {
-        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_etc1);
-        if (!validFormat)
-            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_etc1 enabled",
-                             InfoFrom(func), WebGLContext::EnumName(format));
-        return validFormat;
-    }
-
-
-    if (format == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 ||
-        format == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1 ||
-        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 ||
-        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1)
-    {
-        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_pvrtc);
-        if (!validFormat)
-            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_pvrtc enabled",
-                             InfoFrom(func), WebGLContext::EnumName(format));
-        return validFormat;
-    }
-
-
-    if (format == LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
-        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
-        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
-        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
-    {
-        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_s3tc);
-        if (!validFormat)
-            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_s3tc enabled",
-                             InfoFrom(func), WebGLContext::EnumName(format));
-        return validFormat;
-    }
-
     ErrorInvalidEnumWithName(this, "invalid format", format, func);
 
     return false;
 }
 
 /**
  * Check if the given texture target is valid for TexImage.
  */
 bool
-WebGLContext::ValidateTexImageTarget(GLuint dims, GLenum target, WebGLTexImageFunc func)
+WebGLContext::ValidateTexImageTarget(GLuint dims,
+                                     GLenum target,
+                                     WebGLTexImageFunc func)
 {
     switch (dims) {
     case 2:
         if (target == LOCAL_GL_TEXTURE_2D ||
             IsTexImageCubemapTarget(target))
         {
             return true;
         }
@@ -684,17 +646,18 @@ WebGLContext::ValidateTexImageTarget(GLu
     return false;
 }
 
 /**
  * Return true if type is a valid texture image type for source,
  * taking into account enabled WebGL extensions.
  */
 bool
-WebGLContext::ValidateTexImageType(GLenum type, WebGLTexImageFunc func)
+WebGLContext::ValidateTexImageType(GLenum type,
+                                   WebGLTexImageFunc func)
 {
     /* Core WebGL texture types */
     if (type == LOCAL_GL_UNSIGNED_BYTE ||
         type == LOCAL_GL_UNSIGNED_SHORT_5_6_5 ||
         type == LOCAL_GL_UNSIGNED_SHORT_4_4_4_4 ||
         type == LOCAL_GL_UNSIGNED_SHORT_5_5_5_1)
     {
         return true;
@@ -735,17 +698,18 @@ WebGLContext::ValidateTexImageType(GLenu
 }
 
 /**
  * Validate texture image sizing extra constraints for
  * CompressedTex(Sub)?Image.
  */
 // TODO: WebGL 2
 bool
-WebGLContext::ValidateCompTexImageSize(GLint level, GLenum format,
+WebGLContext::ValidateCompTexImageSize(GLint level,
+                                       GLenum format,
                                        GLint xoffset, GLint yoffset,
                                        GLsizei width, GLsizei height,
                                        GLsizei levelWidth, GLsizei levelHeight,
                                        WebGLTexImageFunc func)
 {
     // Negative parameters must already have been handled above
     MOZ_ASSERT(xoffset >= 0 && yoffset >= 0 &&
                width >= 0 && height >= 0);
@@ -1150,18 +1114,24 @@ WebGLContext::GetBitsPerTexel(TexInterna
     return 0;
 }
 
 /**
  * Perform validation of format/type combinations for TexImage variants.
  * Returns true if the format/type is a valid combination, false otherwise.
  */
 bool
-WebGLContext::ValidateTexImageFormatAndType(GLenum format, GLenum type, WebGLTexImageFunc func)
+WebGLContext::ValidateTexImageFormatAndType(GLenum format,
+                                            GLenum type, WebGLTexImageFunc func)
 {
+    if (IsCompressedFunc(func) || IsCopyFunc(func))
+    {
+        MOZ_ASSERT(type == LOCAL_GL_NONE && format == LOCAL_GL_NONE);
+        return true;
+    }
     if (!ValidateTexImageFormat(format, func) ||
         !ValidateTexImageType(type, func))
     {
         return false;
     }
 
     bool validCombo = false;
 
@@ -1198,46 +1168,114 @@ WebGLContext::ValidateTexImageFormatAndT
         validCombo = (type == LOCAL_GL_UNSIGNED_SHORT ||
                       type == LOCAL_GL_UNSIGNED_INT);
         break;
 
     case LOCAL_GL_DEPTH_STENCIL:
         validCombo = (type == LOCAL_GL_UNSIGNED_INT_24_8);
         break;
 
-    case LOCAL_GL_ATC_RGB:
-    case LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA:
-    case LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA:
-    case LOCAL_GL_ETC1_RGB8_OES:
-    case LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1:
-    case LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1:
-    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1:
-    case LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1:
-    case LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
-    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
-    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
-    case LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
-        validCombo = (type == LOCAL_GL_UNSIGNED_BYTE);
-        break;
-
     default:
         // Only valid formats should be passed to the switch stmt.
         MOZ_ASSERT(false, "Unexpected format and type combo. How'd this happen?");
         validCombo = false;
         // Fall through to return an InvalidOperations. This will alert us to the
         // unexpected case that needs fixing in builds without asserts.
     }
 
     if (!validCombo)
         ErrorInvalidOperation("%s: invalid combination of format %s and type %s",
                               InfoFrom(func), WebGLContext::EnumName(format), WebGLContext::EnumName(type));
 
     return validCombo;
 }
 
+bool
+WebGLContext::ValidateCompTexImageInternalFormat(GLenum format,
+                                                 WebGLTexImageFunc func)
+{
+    if (!IsCompressedTextureFormat(format)) {
+        ErrorInvalidEnum("%s: invalid compressed texture format: %s",
+                         InfoFrom(func), WebGLContext::EnumName(format));
+        return false;
+    }
+
+    /* WEBGL_compressed_texture_atc added formats */
+    if (format == LOCAL_GL_ATC_RGB ||
+        format == LOCAL_GL_ATC_RGBA_EXPLICIT_ALPHA ||
+        format == LOCAL_GL_ATC_RGBA_INTERPOLATED_ALPHA)
+    {
+        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_atc);
+        if (!validFormat)
+            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_atc enabled",
+                             InfoFrom(func), WebGLContext::EnumName(format));
+        return validFormat;
+    }
+
+    // WEBGL_compressed_texture_etc1
+    if (format == LOCAL_GL_ETC1_RGB8_OES) {
+        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_etc1);
+        if (!validFormat)
+            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_etc1 enabled",
+                             InfoFrom(func), WebGLContext::EnumName(format));
+        return validFormat;
+    }
+
+
+    if (format == LOCAL_GL_COMPRESSED_RGB_PVRTC_2BPPV1 ||
+        format == LOCAL_GL_COMPRESSED_RGB_PVRTC_4BPPV1 ||
+        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_2BPPV1 ||
+        format == LOCAL_GL_COMPRESSED_RGBA_PVRTC_4BPPV1)
+    {
+        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_pvrtc);
+        if (!validFormat)
+            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_pvrtc enabled",
+                             InfoFrom(func), WebGLContext::EnumName(format));
+        return validFormat;
+    }
+
+
+    if (format == LOCAL_GL_COMPRESSED_RGB_S3TC_DXT1_EXT ||
+        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT1_EXT ||
+        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT3_EXT ||
+        format == LOCAL_GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
+    {
+        bool validFormat = IsExtensionEnabled(WebGLExtensionID::WEBGL_compressed_texture_s3tc);
+        if (!validFormat)
+            ErrorInvalidEnum("%s: invalid format %s: need WEBGL_compressed_texture_s3tc enabled",
+                             InfoFrom(func), WebGLContext::EnumName(format));
+        return validFormat;
+    }
+
+    return false;
+}
+
+bool
+WebGLContext::ValidateCopyTexImageInternalFormat(GLenum format,
+                                                 WebGLTexImageFunc func)
+{
+    bool valid = format == LOCAL_GL_RGBA ||
+                 format == LOCAL_GL_RGB ||
+                 format == LOCAL_GL_LUMINANCE_ALPHA ||
+                 format == LOCAL_GL_LUMINANCE ||
+                 format == LOCAL_GL_ALPHA;
+    if (!valid)
+    {
+        // in CopyTexImage, the internalformat is a function parameter,
+        // so a bad value is an INVALID_ENUM error.
+        // in CopyTexSubImage, the internalformat is part of existing state,
+        // so this is an INVALID_OPERATION error.
+        GenerateWarning("%s: invalid texture internal format: %s",
+                        InfoFrom(func), WebGLContext::EnumName(format));
+        SynthesizeGLError(func == WebGLTexImageFunc::CopyTexImage
+                          ? LOCAL_GL_INVALID_ENUM
+                          : LOCAL_GL_INVALID_OPERATION);
+    }
+    return valid;
+}
 /**
  * Return true if format, type and jsArrayType are a valid combination.
  * Also returns the size for texel of format and type (in bytes) via
  * \a texelSize.
  *
  * It is assumed that type has previously been validated.
  */
 bool
@@ -1282,17 +1320,18 @@ WebGLContext::ValidateTexInputData(GLenu
 
 /**
  * Checks specific for the CopyTex[Sub]Image2D functions.
  * Verifies:
  * - Framebuffer is complete and has valid read planes
  * - Copy format is a subset of framebuffer format (i.e. all required components are available)
  */
 bool
-WebGLContext::ValidateCopyTexImage(GLenum format, WebGLTexImageFunc func)
+WebGLContext::ValidateCopyTexImage(GLenum format,
+                                   WebGLTexImageFunc func)
 {
     MOZ_ASSERT(IsCopyFunc(func));
 
     // Default framebuffer format
     GLenum fboFormat = bool(gl->GetPixelFormat().alpha > 0) ? LOCAL_GL_RGBA : LOCAL_GL_RGB;
 
     if (mBoundFramebuffer) {
         if (!mBoundFramebuffer->CheckAndInitializeAttachments()) {
@@ -1327,20 +1366,23 @@ WebGLContext::ValidateCopyTexImage(GLenu
 
 /**
  * Test the gl(Copy|Compressed)?Tex[Sub]?Image[23]() parameters for errors.
  * Verifies each of the parameters against the WebGL standard and enabled extensions.
  */
 // TODO: Texture dims is here for future expansion in WebGL 2.0
 bool
 WebGLContext::ValidateTexImage(GLuint dims, TexImageTarget texImageTarget,
-                               GLint level, GLenum internalFormat,
+                               GLint level,
+                               GLenum internalFormat,
                                GLint xoffset, GLint yoffset, GLint zoffset,
                                GLint width, GLint height, GLint depth,
-                               GLint border, GLenum format, GLenum type,
+                               GLint border,
+                               GLenum format,
+                               GLenum type,
                                WebGLTexImageFunc func)
 {
     const char* info = InfoFrom(func);
 
     /* Check level */
     if (level < 0) {
         ErrorInvalidValue("%s: level must be >= 0", info);
         return false;
@@ -1351,23 +1393,31 @@ WebGLContext::ValidateTexImage(GLuint di
         ErrorInvalidValue("%s: border must be 0", info);
         return false;
     }
 
     /* Check incoming image format and type */
     if (!ValidateTexImageFormatAndType(format, type, func))
         return false;
 
-    /* WebGL and OpenGL ES 2.0 impose additional restrictions on the
-     * combinations of format, internalFormat, and type that can be
-     * used.  Formats and types that require additional extensions
-     * (e.g., GL_FLOAT requires GL_OES_texture_float) are filtered
-     * elsewhere.
-     */
-    if (format != internalFormat) {
+    if (IsCompressedFunc(func)) {
+        if (!ValidateCompTexImageInternalFormat(internalFormat, func)) {
+            return false;
+        }
+    } else if (IsCopyFunc(func)) {
+        if (!ValidateCopyTexImageInternalFormat(internalFormat, func)) {
+            return false;
+        }
+    } else if (format != internalFormat) {
+        /* WebGL and OpenGL ES 2.0 impose additional restrictions on the
+         * combinations of format, internalFormat, and type that can be
+         * used.  Formats and types that require additional extensions
+         * (e.g., GL_FLOAT requires GL_OES_texture_float) are filtered
+         * elsewhere.
+         */
         ErrorInvalidOperation("%s: format does not match internalformat", info);
         return false;
     }
 
     /* check internalFormat */
     // TODO: Not sure if this is a bit of over kill.
     if (BaseTexFormat(internalFormat) == LOCAL_GL_NONE) {
         MOZ_ASSERT(false);
@@ -1401,41 +1451,30 @@ WebGLContext::ValidateTexImage(GLuint di
         const WebGLTexture::ImageInfo& imageInfo = tex->ImageInfoAt(texImageTarget, level);
         if (!ValidateTexSubImageSize(xoffset, yoffset, zoffset,
                                      width, height, depth,
                                      imageInfo.Width(), imageInfo.Height(), 0,
                                      func))
         {
             return false;
         }
-
-        /* Require the format and type to match that of the existing
-         * texture as created
-         */
-        if (imageInfo.WebGLFormat() != format ||
-            imageInfo.WebGLType() != type)
-        {
-            ErrorInvalidOperation("%s: format or type doesn't match the existing texture",
-                                  info);
-            return false;
-        }
     }
 
     /* Additional checks for depth textures */
     if (texImageTarget != LOCAL_GL_TEXTURE_2D &&
         (format == LOCAL_GL_DEPTH_COMPONENT ||
          format == LOCAL_GL_DEPTH_STENCIL))
     {
         ErrorInvalidOperation("%s: with format of %s target must be TEXTURE_2D",
                               info, WebGLContext::EnumName(format));
         return false;
     }
 
     /* Additional checks for compressed textures */
-    if (!IsAllowedFromSource(format, func)) {
+    if (!IsAllowedFromSource(internalFormat, func)) {
         ErrorInvalidOperation("%s: Invalid format %s for this operation",
                               info, WebGLContext::EnumName(format));
         return false;
     }
 
     /* Parameters are OK */
     return true;
 }
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -1,23 +1,26 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+#include "WebGLFramebuffer.h"
+
 #include "WebGLContext.h"
-#include "WebGLFramebuffer.h"
+#include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
 #include "WebGLRenderbuffer.h"
+#include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
+#include "WebGLTexture.h"
+
+#include "GLContext.h"
+
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
-#include "WebGLTexture.h"
-#include "WebGLRenderbuffer.h"
-#include "GLContext.h"
-#include "WebGLContextUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::gl;
 
 JSObject*
 WebGLFramebuffer::WrapObject(JSContext* cx)
 {
     return dom::WebGLFramebufferBinding::Wrap(cx, this);
@@ -72,48 +75,48 @@ WebGLFramebuffer::Attachment::IsDefined(
 }
 
 bool
 WebGLFramebuffer::Attachment::HasAlpha() const
 {
     MOZ_ASSERT(HasImage());
 
     if (Texture() && Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel))
-        return FormatHasAlpha(Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).WebGLFormat());
+        return FormatHasAlpha(Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel).InternalFormat());
     else if (Renderbuffer())
         return FormatHasAlpha(Renderbuffer()->InternalFormat());
     else return false;
 }
 
 GLenum
 WebGLFramebuffer::GetFormatForAttachment(const WebGLFramebuffer::Attachment& attachment) const
 {
     MOZ_ASSERT(attachment.IsDefined());
     MOZ_ASSERT(attachment.Texture() || attachment.Renderbuffer());
 
     if (attachment.Texture()) {
         const WebGLTexture& tex = *attachment.Texture();
         MOZ_ASSERT(tex.HasImageInfoAt(attachment.ImageTarget(), 0));
 
         const WebGLTexture::ImageInfo& imgInfo = tex.ImageInfoAt(attachment.ImageTarget(), 0);
-        return imgInfo.WebGLFormat().get();
+        return imgInfo.InternalFormat().get();
     }
 
     if (attachment.Renderbuffer())
         return attachment.Renderbuffer()->InternalFormat();
 
     return LOCAL_GL_NONE;
 }
 
 bool
 WebGLFramebuffer::Attachment::IsReadableFloat() const
 {
     const WebGLTexture* tex = Texture();
     if (tex && tex->HasImageInfoAt(mTexImageTarget, mTexImageLevel)) {
-        GLenum type = tex->ImageInfoAt(mTexImageTarget, mTexImageLevel).WebGLType().get();
+        GLenum type = tex->ImageInfoAt(mTexImageTarget, mTexImageLevel).Type().get();
         switch (type) {
         case LOCAL_GL_FLOAT:
         case LOCAL_GL_HALF_FLOAT_OES:
             return true;
         }
         return false;
     }
 
@@ -323,33 +326,33 @@ WebGLFramebuffer::Attachment::IsComplete
     {
         return false;
     }
 
     if (Texture()) {
         MOZ_ASSERT(Texture()->HasImageInfoAt(mTexImageTarget, mTexImageLevel));
         const WebGLTexture::ImageInfo& imageInfo =
             Texture()->ImageInfoAt(mTexImageTarget, mTexImageLevel);
-        GLenum webGLFormat = imageInfo.WebGLFormat().get();
+        GLenum internalformat = imageInfo.InternalFormat().get();
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_ATTACHMENT)
-            return IsValidFBOTextureDepthFormat(webGLFormat);
+            return IsValidFBOTextureDepthFormat(internalformat);
 
         if (mAttachmentPoint == LOCAL_GL_STENCIL_ATTACHMENT)
             return false; // Textures can't have the correct format for stencil buffers
 
         if (mAttachmentPoint == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
-            return IsValidFBOTextureDepthStencilFormat(webGLFormat);
+            return IsValidFBOTextureDepthStencilFormat(internalformat);
         }
 
         if (mAttachmentPoint >= LOCAL_GL_COLOR_ATTACHMENT0 &&
             mAttachmentPoint <= FBAttachment(LOCAL_GL_COLOR_ATTACHMENT0 - 1 +
                                              WebGLContext::kMaxColorAttachments))
         {
-            return IsValidFBOTextureColorFormat(webGLFormat);
+            return IsValidFBOTextureColorFormat(internalformat);
         }
         MOZ_ASSERT(false, "Invalid WebGL attachment point?");
         return false;
     }
 
     if (Renderbuffer()) {
         GLenum internalFormat = Renderbuffer()->InternalFormat();
 
--- a/dom/canvas/WebGLStrongTypes.h
+++ b/dom/canvas/WebGLStrongTypes.h
@@ -268,32 +268,32 @@ STRONG_GLENUM_END(TexMagFilter)
 
 STRONG_GLENUM_BEGIN(TexWrap)
     STRONG_GLENUM_VALUE(REPEAT),
     STRONG_GLENUM_VALUE(CLAMP_TO_EDGE),
     STRONG_GLENUM_VALUE(MIRRORED_REPEAT),
 STRONG_GLENUM_END(TexWrap)
 
 STRONG_GLENUM_BEGIN(TexFormat)
-    STRONG_GLENUM_VALUE(NONE),
-    STRONG_GLENUM_VALUE(DEPTH_COMPONENT),
-    STRONG_GLENUM_VALUE(RED),
-    STRONG_GLENUM_VALUE(ALPHA),
-    STRONG_GLENUM_VALUE(RGB),
-    STRONG_GLENUM_VALUE(RGBA),
-    STRONG_GLENUM_VALUE(LUMINANCE),
-    STRONG_GLENUM_VALUE(LUMINANCE_ALPHA),
-    STRONG_GLENUM_VALUE(RG),
-    STRONG_GLENUM_VALUE(SRGB),
-    STRONG_GLENUM_VALUE(SRGB_ALPHA),
-    STRONG_GLENUM_VALUE(RG_INTEGER),
-    STRONG_GLENUM_VALUE(DEPTH_STENCIL),
-    STRONG_GLENUM_VALUE(RED_INTEGER),
-    STRONG_GLENUM_VALUE(RGB_INTEGER),
-    STRONG_GLENUM_VALUE(RGBA_INTEGER),
+    STRONG_GLENUM_VALUE(NONE),            // 0x0000
+    STRONG_GLENUM_VALUE(DEPTH_COMPONENT), // 0x1902
+    STRONG_GLENUM_VALUE(RED),             // 0x1903
+    STRONG_GLENUM_VALUE(ALPHA),           // 0x1906
+    STRONG_GLENUM_VALUE(RGB),             // 0x1907
+    STRONG_GLENUM_VALUE(RGBA),            // 0x1908
+    STRONG_GLENUM_VALUE(LUMINANCE),       // 0x1909
+    STRONG_GLENUM_VALUE(LUMINANCE_ALPHA), // 0x190A
+    STRONG_GLENUM_VALUE(RG),              // 0x8227
+    STRONG_GLENUM_VALUE(RG_INTEGER),      // 0x8228
+    STRONG_GLENUM_VALUE(DEPTH_STENCIL),   // 0x84F9
+    STRONG_GLENUM_VALUE(SRGB),            // 0x8C40
+    STRONG_GLENUM_VALUE(SRGB_ALPHA),      // 0x8C42
+    STRONG_GLENUM_VALUE(RED_INTEGER),     // 0x8D94
+    STRONG_GLENUM_VALUE(RGB_INTEGER),     // 0x8D98
+    STRONG_GLENUM_VALUE(RGBA_INTEGER),    // 0x8D99
 STRONG_GLENUM_END(TexFormat)
 
 STRONG_GLENUM_BEGIN(TexInternalFormat)
     STRONG_GLENUM_VALUE(NONE),
     STRONG_GLENUM_VALUE(DEPTH_COMPONENT),
     STRONG_GLENUM_VALUE(ALPHA),
     STRONG_GLENUM_VALUE(RGB),
     STRONG_GLENUM_VALUE(RGBA),
--- a/dom/canvas/WebGLTexture.cpp
+++ b/dom/canvas/WebGLTexture.cpp
@@ -47,17 +47,17 @@ WebGLTexture::Delete() {
     mContext->gl->fDeleteTextures(1, &mGLName);
     LinkedListElement<WebGLTexture>::removeFrom(mContext->mTextures);
 }
 
 int64_t
 WebGLTexture::ImageInfo::MemoryUsage() const {
     if (mImageDataStatus == WebGLImageDataStatus::NoImageData)
         return 0;
-    int64_t bitsPerTexel = WebGLContext::GetBitsPerTexel(mWebGLFormat, mWebGLType);
+    int64_t bitsPerTexel = WebGLContext::GetBitsPerTexel(mInternalFormat, mType);
     return int64_t(mWidth) * int64_t(mHeight) * bitsPerTexel/8;
 }
 
 int64_t
 WebGLTexture::MemoryUsage() const {
     if (IsDeleted())
         return 0;
     int64_t result = 0;
@@ -132,26 +132,27 @@ WebGLTexture::Bind(TexTarget aTexTarget)
         // we need to set it to GL_CLAMP_TO_EDGE to get the expected GLES behavior.
         if (mTarget == LOCAL_GL_TEXTURE_CUBE_MAP && !mContext->gl->IsGLES())
             mContext->gl->fTexParameteri(aTexTarget.get(), LOCAL_GL_TEXTURE_WRAP_R, LOCAL_GL_CLAMP_TO_EDGE);
     }
 }
 
 void
 WebGLTexture::SetImageInfo(TexImageTarget aTexImageTarget, GLint aLevel,
-                  GLsizei aWidth, GLsizei aHeight,
-                  TexInternalFormat aFormat, TexType aType, WebGLImageDataStatus aStatus)
+                           GLsizei aWidth, GLsizei aHeight,
+                           TexInternalFormat aInternalFormat, TexType aType,
+                           WebGLImageDataStatus aStatus)
 {
     MOZ_ASSERT(TexImageTargetToTexTarget(aTexImageTarget) == mTarget);
     if (TexImageTargetToTexTarget(aTexImageTarget) != mTarget)
         return;
 
     EnsureMaxLevelWithCustomImagesAtLeast(aLevel);
 
-    ImageInfoAt(aTexImageTarget, aLevel) = ImageInfo(aWidth, aHeight, aFormat, aType, aStatus);
+    ImageInfoAt(aTexImageTarget, aLevel) = ImageInfo(aWidth, aHeight, aInternalFormat, aType, aStatus);
 
     if (aLevel > 0)
         SetCustomMipmap();
 
     // Invalidate framebuffer status cache
     NotifyFBsStatusChanged();
 
     SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
@@ -330,17 +331,17 @@ WebGLTexture::ResolvedFakeBlackStatus() 
                 mContext->GenerateWarning("%s is a cube map texture, with a minification filter not requiring a mipmap, "
                             "with some level 0 image having width or height not a power of two, and with a wrap mode "
                             "different from CLAMP_TO_EDGE.", msg_rendering_as_black);
                 mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
             }
         }
     }
 
-    if (ImageInfoBase().mWebGLType == LOCAL_GL_FLOAT &&
+    if (ImageInfoBase().mType == LOCAL_GL_FLOAT &&
         !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_float_linear))
     {
         if (mMinFilter == LOCAL_GL_LINEAR ||
             mMinFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR ||
             mMinFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
             mMinFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR)
         {
             mContext->GenerateWarning("%s is a texture with a linear minification filter, "
@@ -350,17 +351,17 @@ WebGLTexture::ResolvedFakeBlackStatus() 
         }
         else if (mMagFilter == LOCAL_GL_LINEAR)
         {
             mContext->GenerateWarning("%s is a texture with a linear magnification filter, "
                                       "which is not compatible with gl.FLOAT by default. "
                                       "Try enabling the OES_texture_float_linear extension if supported.", msg_rendering_as_black);
             mFakeBlackStatus = WebGLTextureFakeBlackStatus::IncompleteTexture;
         }
-    } else if (ImageInfoBase().mWebGLType == LOCAL_GL_HALF_FLOAT_OES &&
+    } else if (ImageInfoBase().mType == LOCAL_GL_HALF_FLOAT_OES &&
                !Context()->IsExtensionEnabled(WebGLExtensionID::OES_texture_half_float_linear))
     {
         if (mMinFilter == LOCAL_GL_LINEAR ||
             mMinFilter == LOCAL_GL_LINEAR_MIPMAP_LINEAR ||
             mMinFilter == LOCAL_GL_LINEAR_MIPMAP_NEAREST ||
             mMinFilter == LOCAL_GL_NEAREST_MIPMAP_LINEAR)
         {
             mContext->GenerateWarning("%s is a texture with a linear minification filter, "
@@ -537,23 +538,23 @@ void
 WebGLTexture::DoDeferredImageInitialization(TexImageTarget imageTarget, GLint level)
 {
     const ImageInfo& imageInfo = ImageInfoAt(imageTarget, level);
     MOZ_ASSERT(imageInfo.mImageDataStatus == WebGLImageDataStatus::UninitializedImageData);
 
     mContext->MakeContextCurrent();
 
     // Try to clear with glCLear.
-    TexInternalFormat format = imageInfo.mWebGLFormat;
-    TexType type = imageInfo.mWebGLType;
-    WebGLTexelFormat texelformat = GetWebGLTexelFormat(format, type);
+    TexInternalFormat internalformat = imageInfo.mInternalFormat;
+    TexType type = imageInfo.mType;
+    WebGLTexelFormat texelformat = GetWebGLTexelFormat(internalformat, type);
 
     bool cleared = ClearWithTempFB(mContext, GLName(),
                                    imageTarget, level,
-                                   format, imageInfo.mHeight, imageInfo.mWidth);
+                                   internalformat, imageInfo.mHeight, imageInfo.mWidth);
     if (cleared) {
         SetImageDataStatus(imageTarget, level, WebGLImageDataStatus::InitializedImageData);
         return;
     }
 
     // That didn't work. Try uploading zeros then.
     gl::ScopedBindTexture autoBindTex(mContext->gl, GLName(), mTarget.get());
 
@@ -567,17 +568,17 @@ WebGLTexture::DoDeferredImageInitializat
     MOZ_ASSERT(checked_byteLength.isValid()); // should have been checked earlier
     ScopedFreePtr<void> zeros;
     zeros = calloc(1, checked_byteLength.value());
 
     gl::GLContext* gl = mContext->gl;
     GLenum driverType = DriverTypeFromType(gl, type);
     GLenum driverInternalFormat = LOCAL_GL_NONE;
     GLenum driverFormat = LOCAL_GL_NONE;
-    DriverFormatsFromFormatAndType(gl, format, type, &driverInternalFormat, &driverFormat);
+    DriverFormatsFromFormatAndType(gl, internalformat, type, &driverInternalFormat, &driverFormat);
 
     mContext->GetAndFlushUnderlyingGLErrors();
     gl->fTexImage2D(imageTarget.get(), level, driverInternalFormat,
                     imageInfo.mWidth, imageInfo.mHeight,
                     0, driverFormat, driverType,
                     zeros);
     GLenum error = mContext->GetAndFlushUnderlyingGLErrors();
     if (error) {
--- a/dom/canvas/WebGLTexture.h
+++ b/dom/canvas/WebGLTexture.h
@@ -62,41 +62,41 @@ protected:
 
 public:
 
     class ImageInfo
         : public WebGLRectangleObject
     {
     public:
         ImageInfo()
-            : mWebGLFormat(LOCAL_GL_NONE)
-            , mWebGLType(LOCAL_GL_NONE)
+            : mInternalFormat(LOCAL_GL_NONE)
+            , mType(LOCAL_GL_NONE)
             , mImageDataStatus(WebGLImageDataStatus::NoImageData)
         {}
 
         ImageInfo(GLsizei width,
                   GLsizei height,
-                  TexInternalFormat webGLFormat,
-                  TexType webGLType,
+                  TexInternalFormat internalFormat,
+                  TexType type,
                   WebGLImageDataStatus status)
             : WebGLRectangleObject(width, height)
-            , mWebGLFormat(webGLFormat)
-            , mWebGLType(webGLType)
+            , mInternalFormat(internalFormat)
+            , mType(type)
             , mImageDataStatus(status)
         {
             // shouldn't use this constructor to construct a null ImageInfo
             MOZ_ASSERT(status != WebGLImageDataStatus::NoImageData);
         }
 
         bool operator==(const ImageInfo& a) const {
             return mImageDataStatus == a.mImageDataStatus &&
                    mWidth == a.mWidth &&
                    mHeight == a.mHeight &&
-                   mWebGLFormat == a.mWebGLFormat &&
-                   mWebGLType == a.mWebGLType;
+                   mInternalFormat == a.mInternalFormat &&
+                   mType == a.mType;
         }
         bool operator!=(const ImageInfo& a) const {
             return !(*this == a);
         }
         bool IsSquare() const {
             return mWidth == mHeight;
         }
         bool IsPositive() const {
@@ -109,26 +109,27 @@ public:
         bool HasUninitializedImageData() const {
             return mImageDataStatus == WebGLImageDataStatus::UninitializedImageData;
         }
         int64_t MemoryUsage() const;
         /*! This is the format passed from JS to WebGL.
          * It can be converted to a value to be passed to driver with
          * DriverFormatsFromFormatAndType().
          */
-        TexInternalFormat WebGLFormat() const { return mWebGLFormat; }
+        TexInternalFormat InternalFormat() const { return mInternalFormat; }
+
         /*! This is the type passed from JS to WebGL.
          * It can be converted to a value to be passed to driver with
          * DriverTypeFromType().
          */
-        TexType WebGLType() const { return mWebGLType; }
+        TexType Type() const { return mType; }
 
     protected:
-        TexInternalFormat mWebGLFormat; //!< This is the WebGL/GLES format
-        TexType mWebGLType;   //!< This is the WebGL/GLES type
+        TexInternalFormat mInternalFormat; //!< This is the WebGL/GLES internal format.
+        TexType mType;   //!< This is the WebGL/GLES type
         WebGLImageDataStatus mImageDataStatus;
 
         friend class WebGLTexture;
     };
 
 private:
     static size_t FaceForTarget(TexImageTarget texImageTarget) {
         if (texImageTarget == LOCAL_GL_TEXTURE_2D)
@@ -222,17 +223,18 @@ protected:
     bool DoesTexture2DMipmapHaveAllLevelsConsistentlyDefined(TexImageTarget texImageTarget) const;
 
 public:
 
     void Bind(TexTarget aTexTarget);
 
     void SetImageInfo(TexImageTarget aTarget, GLint aLevel,
                       GLsizei aWidth, GLsizei aHeight,
-                      TexInternalFormat aFormat, TexType aType, WebGLImageDataStatus aStatus);
+                      TexInternalFormat aInternalFormat, TexType aType,
+                      WebGLImageDataStatus aStatus);
 
     void SetMinFilter(TexMinFilter aMinFilter) {
         mMinFilter = aMinFilter;
         SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
     }
     void SetMagFilter(TexMagFilter aMagFilter) {
         mMagFilter = aMagFilter;
         SetFakeBlackStatus(WebGLTextureFakeBlackStatus::Unknown);
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -60,16 +60,18 @@ enum TelemetryAlgorithm {
   // digest
   TA_SHA_1           = 14,
   TA_SHA_224         = 15,
   TA_SHA_256         = 16,
   TA_SHA_384         = 17,
   TA_SHA_512         = 18,
   // Later additions
   TA_AES_KW          = 19,
+  TA_ECDH            = 20,
+  TA_PBKDF2          = 21
 };
 
 // Convenience functions for extracting / converting information
 
 // OOM-safe CryptoBuffer initialization, suitable for constructors
 #define ATTEMPT_BUFFER_INIT(dst, src) \
   if (!dst.Assign(src)) { \
     mEarlyRv = NS_ERROR_DOM_UNKNOWN_ERR; \
@@ -2134,16 +2136,17 @@ public:
     if (NS_SUCCEEDED(mEarlyRv)) {
       Init(aCx, aAlgorithm, aKey, length);
     }
   }
 
   void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey,
             uint32_t aLength)
   {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_PBKDF2);
     CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_PBKDF2);
 
     // Check that we got a symmetric key
     if (mSymKey.Length() == 0) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
     }
 
@@ -2292,16 +2295,17 @@ public:
     mEarlyRv = GetKeyLengthForAlgorithm(aCx, aTargetAlgorithm, mLength);
     if (NS_SUCCEEDED(mEarlyRv)) {
       Init(aCx, aAlgorithm, aKey);
     }
   }
 
   void Init(JSContext* aCx, const ObjectOrString& aAlgorithm, CryptoKey& aKey)
   {
+    Telemetry::Accumulate(Telemetry::WEBCRYPTO_ALG, TA_ECDH);
     CHECK_KEY_ALGORITHM(aKey.Algorithm(), WEBCRYPTO_ALG_ECDH);
 
     // Check that we have a private key.
     if (!mPrivKey) {
       mEarlyRv = NS_ERROR_DOM_INVALID_ACCESS_ERR;
       return;
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Request.cpp
@@ -0,0 +1,118 @@
+/* -*- Mode: C++; 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 "Request.h"
+
+#include "nsDOMString.h"
+#include "nsISupportsImpl.h"
+#include "nsIURI.h"
+#include "nsPIDOMWindow.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Request)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Request)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Request, mOwner, mHeaders)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Request)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Request::Request(nsISupports* aOwner)
+  : mOwner(aOwner)
+  , mHeaders(new Headers(aOwner))
+{
+  SetIsDOMBinding();
+}
+
+Request::~Request()
+{
+}
+
+/*static*/ already_AddRefed<Request>
+Request::Constructor(const GlobalObject& global,
+                     const RequestOrScalarValueString& aInput,
+                     const RequestInit& aInit, ErrorResult& rv)
+{
+  nsRefPtr<Request> request = new Request(global.GetAsSupports());
+  return request.forget();
+}
+
+already_AddRefed<Request>
+Request::Clone() const
+{
+  nsRefPtr<Request> request = new Request(mOwner);
+  return request.forget();
+}
+
+already_AddRefed<Promise>
+Request::ArrayBuffer(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Request::Blob(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Request::Json(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Request::Text(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+bool
+Request::BodyUsed()
+{
+  return false;
+}
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Request.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_dom_Request_h
+#define mozilla_dom_Request_h
+
+#include "nsISupportsImpl.h"
+#include "nsWrapperCache.h"
+
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class Headers;
+class Promise;
+
+class Request MOZ_FINAL : public nsISupports
+                        , public nsWrapperCache
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Request)
+
+public:
+  Request(nsISupports* aOwner);
+
+  JSObject*
+  WrapObject(JSContext* aCx)
+  {
+    return RequestBinding::Wrap(aCx, this);
+  }
+
+  void
+  GetUrl(DOMString& aUrl) const
+  {
+    aUrl.AsAString() = EmptyString();
+  }
+
+  void
+  GetMethod(nsCString& aMethod) const
+  {
+    aMethod = EmptyCString();
+  }
+
+  RequestMode
+  Mode() const
+  {
+    return RequestMode::Same_origin;
+  }
+
+  RequestCredentials
+  Credentials() const
+  {
+    return RequestCredentials::Omit;
+  }
+
+  void
+  GetReferrer(DOMString& aReferrer) const
+  {
+    aReferrer.AsAString() = EmptyString();
+  }
+
+  Headers* Headers_() const { return mHeaders; }
+
+  static already_AddRefed<Request>
+  Constructor(const GlobalObject& aGlobal, const RequestOrScalarValueString& aInput,
+              const RequestInit& aInit, ErrorResult& rv);
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  already_AddRefed<Request>
+  Clone() const;
+
+  already_AddRefed<Promise>
+  ArrayBuffer(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Blob(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Json(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Text(ErrorResult& aRv);
+
+  bool
+  BodyUsed();
+private:
+  ~Request();
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsRefPtr<Headers> mHeaders;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Request_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Response.cpp
@@ -0,0 +1,139 @@
+/* -*- Mode: C++; 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 "Response.h"
+#include "nsDOMString.h"
+#include "nsPIDOMWindow.h"
+#include "nsIURI.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Response)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Response)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Response, mOwner)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Response)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Response::Response(nsISupports* aOwner)
+  : mOwner(aOwner)
+  , mHeaders(new Headers(aOwner))
+{
+  SetIsDOMBinding();
+}
+
+Response::~Response()
+{
+}
+
+/* static */ already_AddRefed<Response>
+Response::Error(const GlobalObject& aGlobal)
+{
+  ErrorResult result;
+  ResponseInit init;
+  init.mStatus = 0;
+  Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams> body;
+  nsRefPtr<Response> r = Response::Constructor(aGlobal, body, init, result);
+  return r.forget();
+}
+
+/* static */ already_AddRefed<Response>
+Response::Redirect(const GlobalObject& aGlobal, const nsAString& aUrl,
+                   uint16_t aStatus)
+{
+  ErrorResult result;
+  ResponseInit init;
+  Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams> body;
+  nsRefPtr<Response> r = Response::Constructor(aGlobal, body, init, result);
+  return r.forget();
+}
+
+/*static*/ already_AddRefed<Response>
+Response::Constructor(const GlobalObject& global,
+                      const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
+                      const ResponseInit& aInit, ErrorResult& rv)
+{
+  nsRefPtr<Response> response = new Response(global.GetAsSupports());
+  return response.forget();
+}
+
+already_AddRefed<Response>
+Response::Clone()
+{
+  nsRefPtr<Response> response = new Response(mOwner);
+  return response.forget();
+}
+
+already_AddRefed<Promise>
+Response::ArrayBuffer(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Response::Blob(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Response::Json(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+Response::Text(ErrorResult& aRv)
+{
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+  nsRefPtr<Promise> promise = Promise::Create(global, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+bool
+Response::BodyUsed()
+{
+  return false;
+}
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/Response.h
@@ -0,0 +1,108 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef mozilla_dom_Response_h
+#define mozilla_dom_Response_h
+
+#include "nsWrapperCache.h"
+#include "nsISupportsImpl.h"
+
+#include "mozilla/dom/ResponseBinding.h"
+#include "mozilla/dom/UnionTypes.h"
+
+class nsPIDOMWindow;
+
+namespace mozilla {
+namespace dom {
+
+class Headers;
+class Promise;
+
+class Response MOZ_FINAL : public nsISupports
+                         , public nsWrapperCache
+{
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Response)
+
+public:
+  Response(nsISupports* aOwner);
+
+  JSObject*
+  WrapObject(JSContext* aCx)
+  {
+    return ResponseBinding::Wrap(aCx, this);
+  }
+
+  ResponseType
+  Type() const
+  {
+    return ResponseType::Error;
+  }
+
+  void
+  GetUrl(DOMString& aUrl) const
+  {
+    aUrl.AsAString() = EmptyString();
+  }
+
+  uint16_t
+  Status() const
+  {
+    return 400;
+  }
+
+  void
+  GetStatusText(nsCString& aStatusText) const
+  {
+    aStatusText = EmptyCString();
+  }
+
+  Headers*
+  Headers_() const { return mHeaders; }
+
+  static already_AddRefed<Response>
+  Error(const GlobalObject& aGlobal);
+
+  static already_AddRefed<Response>
+  Redirect(const GlobalObject& aGlobal, const nsAString& aUrl, uint16_t aStatus);
+
+  static already_AddRefed<Response>
+  Constructor(const GlobalObject& aGlobal,
+              const Optional<ArrayBufferOrArrayBufferViewOrScalarValueStringOrURLSearchParams>& aBody,
+              const ResponseInit& aInit, ErrorResult& rv);
+
+  nsISupports* GetParentObject() const
+  {
+    return mOwner;
+  }
+
+  already_AddRefed<Response>
+  Clone();
+
+  already_AddRefed<Promise>
+  ArrayBuffer(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Blob(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Json(ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Text(ErrorResult& aRv);
+
+  bool
+  BodyUsed();
+private:
+  ~Response();
+
+  nsCOMPtr<nsISupports> mOwner;
+  nsRefPtr<Headers> mHeaders;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Response_h
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -1,20 +1,24 @@
 # -*- 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/.
 
 EXPORTS.mozilla.dom += [
     'Headers.h',
+    'Request.h',
+    'Response.h',
 ]
 
 UNIFIED_SOURCES += [
     'Headers.cpp',
+    'Request.cpp',
+    'Response.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '../workers',
 ]
 
 FAIL_ON_WARNINGS = True
 MSVC_ENABLE_PGO = True
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -553,26 +553,31 @@ nsresult nsNPAPIPluginInstance::SetWindo
 
     PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("nsNPAPIPluginInstance::SetWindow (about to call it) this=%p\n",this));
 
     bool oldVal = mInPluginInitCall;
     mInPluginInitCall = true;
 
     NPPAutoPusher nppPusher(&mNPP);
 
-    DebugOnly<NPError> error;
+    NPError error;
     NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->setwindow)(&mNPP, (NPWindow*)window), this,
                             NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+    // 'error' is only used if this is a logging-enabled build.
+    // That is somewhat complex to check, so we just use "unused"
+    // to suppress any compiler warnings in build configurations
+    // where the logging is a no-op.
+    mozilla::unused << error;
 
     mInPluginInitCall = oldVal;
 
     NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
     ("NPP SetWindow called: this=%p, [x=%d,y=%d,w=%d,h=%d], clip[t=%d,b=%d,l=%d,r=%d], return=%d\n",
     this, window->x, window->y, window->width, window->height,
-    window->clipRect.top, window->clipRect.bottom, window->clipRect.left, window->clipRect.right, (NPError)error));
+    window->clipRect.top, window->clipRect.bottom, window->clipRect.left, window->clipRect.right, error));
   }
   return NS_OK;
 }
 
 nsresult
 nsNPAPIPluginInstance::NewStreamFromPlugin(const char* type, const char* target,
                                            nsIOutputStream* *result)
 {
--- a/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest
+++ b/dom/plugins/ipc/hangui/plugin-hang-ui.exe.manifest
@@ -23,15 +23,16 @@
     <ms_asmv3:security>
       <ms_asmv3:requestedPrivileges>
         <ms_asmv3:requestedExecutionLevel level="asInvoker" uiAccess="false" />
       </ms_asmv3:requestedPrivileges>
     </ms_asmv3:security>
   </ms_asmv3:trustInfo>
   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
     <application>
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
       <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
       <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
       <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
       <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
     </application>
   </compatibility>
 </assembly>
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -864,16 +864,20 @@ var interfaceNamesInGlobalScope =
     "RadioNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Range",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RecordErrorEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Rect",
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "Request", pref: "dom.fetch.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "Response", pref: "dom.fetch.enabled"},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "RGBColor",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCDataChannelEvent", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCPeerConnectionIceEvent", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "RTCRtpReceiver", pref: "media.peerconnection.enabled"},
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Fetch.webidl
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * http://fetch.spec.whatwg.org/
+ */
+
+typedef object JSON;
+// FIXME(nsm): Bug 1071290: Blobs can't be passed as unions in workers.
+// FIXME(nsm): Bug 739173: FormData is not available in workers.
+// typedef (ArrayBuffer or ArrayBufferView or Blob or FormData or ScalarValueString or URLSearchParams) BodyInit;
+typedef (ArrayBuffer or ArrayBufferView or ScalarValueString or URLSearchParams) BodyInit;
+
+[NoInterfaceObject, Exposed=(Window,Worker)]
+interface Body {
+  readonly attribute boolean bodyUsed;
+  [Throws]
+  Promise<ArrayBuffer> arrayBuffer();
+  [Throws]
+  Promise<Blob> blob();
+  // FIXME(nsm): Bug 739173 FormData is not supported in workers.
+  // Promise<FormData> formData();
+  [Throws]
+  Promise<JSON> json();
+  [Throws]
+  Promise<ScalarValueString> text();
+};
+
+[NoInterfaceObject, Exposed=(Window,Worker)]
+interface GlobalFetch {
+  [Throws, Func="mozilla::dom::Headers::PrefEnabled"]
+  Promise<Response> fetch(RequestInfo input, optional RequestInit init);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Request.webidl
@@ -0,0 +1,38 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * https://fetch.spec.whatwg.org/#request-class
+ */
+
+typedef (Request or ScalarValueString) RequestInfo;
+
+[Constructor(RequestInfo input, optional RequestInit init),
+ Exposed=(Window,Worker),
+ Func="mozilla::dom::Headers::PrefEnabled"]
+interface Request {
+  readonly attribute ByteString method;
+  readonly attribute ScalarValueString url;
+  readonly attribute Headers headers;
+
+  readonly attribute DOMString referrer;
+  readonly attribute RequestMode mode;
+  readonly attribute RequestCredentials credentials;
+
+  Request clone();
+};
+
+Request implements Body;
+
+dictionary RequestInit {
+  ByteString method;
+  HeadersInit headers;
+  BodyInit body;
+  RequestMode mode;
+  RequestCredentials credentials;
+};
+
+enum RequestMode { "same-origin", "no-cors", "cors" };
+enum RequestCredentials { "omit", "same-origin", "include" };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/Response.webidl
@@ -0,0 +1,36 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * https://fetch.spec.whatwg.org/#response-class
+ */
+
+[Constructor(optional BodyInit body, optional ResponseInit init),
+ Exposed=(Window,Worker),
+ Func="mozilla::dom::Headers::PrefEnabled"]
+interface Response {
+  static Response error();
+  static Response redirect(ScalarValueString url, optional unsigned short status = 302);
+
+  readonly attribute ResponseType type;
+
+  readonly attribute ScalarValueString url;
+  readonly attribute unsigned short status;
+  readonly attribute ByteString statusText;
+  readonly attribute Headers headers;
+
+  Response clone();
+};
+
+Response implements Body;
+
+dictionary ResponseInit {
+  unsigned short status = 200;
+  // WebIDL spec doesn't allow default values for ByteString.
+  ByteString statusText;
+  HeadersInit headers;
+};
+
+enum ResponseType { "basic", "cors", "default", "error", "opaque" };
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -460,8 +460,9 @@ interface ChromeWindow {
    *
    * Throws NS_ERROR_NOT_IMPLEMENTED if the OS doesn't support this.
    */
   [Throws, Func="nsGlobalWindow::IsChromeWindow"]
   void beginWindowMove(Event mouseDownEvent, optional Element? panel = null);
 };
 
 Window implements ChromeWindow;
+Window implements GlobalFetch;
--- a/dom/webidl/WorkerGlobalScope.webidl
+++ b/dom/webidl/WorkerGlobalScope.webidl
@@ -33,16 +33,17 @@ partial interface WorkerGlobalScope {
   [Throws]
   void importScripts(DOMString... urls);
 
   readonly attribute WorkerNavigator navigator;
 };
 
 WorkerGlobalScope implements WindowTimers;
 WorkerGlobalScope implements WindowBase64;
+WorkerGlobalScope implements GlobalFetch;
 
 // Not implemented yet: bug 1072107.
 // WorkerGlobalScope implements FontFaceSource;
 
 // Mozilla extensions
 partial interface WorkerGlobalScope {
   attribute EventHandler onclose;
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -120,16 +120,17 @@ WEBIDL_FILES = [
     'DynamicsCompressorNode.webidl',
     'Element.webidl',
     'EngineeringMode.webidl',
     'Event.webidl',
     'EventHandler.webidl',
     'EventListener.webidl',
     'EventSource.webidl',
     'EventTarget.webidl',
+    'Fetch.webidl',
     'File.webidl',
     'FileList.webidl',
     'FileMode.webidl',
     'FileReader.webidl',
     'FileReaderSync.webidl',
     'FocusEvent.webidl',
     'FontFace.webidl',
     'FontFaceSet.webidl',
@@ -322,18 +323,20 @@ WEBIDL_FILES = [
     'ProcessingInstruction.webidl',
     'ProfileTimelineMarker.webidl',
     'Promise.webidl',
     'PromiseDebugging.webidl',
     'PushManager.webidl',
     'RadioNodeList.webidl',
     'Range.webidl',
     'Rect.webidl',
+    'Request.webidl',
     'ResourceStats.webidl',
     'ResourceStatsManager.webidl',
+    'Response.webidl',
     'RGBColor.webidl',
     'RTCConfiguration.webidl',
     'RTCIceCandidate.webidl',
     'RTCIdentityAssertion.webidl',
     'RTCPeerConnection.webidl',
     'RTCPeerConnectionStatic.webidl',
     'RTCRtpReceiver.webidl',
     'RTCRtpSender.webidl',
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -298,16 +298,24 @@ WorkerGlobalScope::GetPerformance()
 
   if (!mPerformance) {
     mPerformance = new Performance(mWorkerPrivate);
   }
 
   return mPerformance;
 }
 
+already_AddRefed<Promise>
+WorkerGlobalScope::Fetch(const RequestOrScalarValueString& aInput,
+                         const RequestInit& aInit, ErrorResult& aRv)
+{
+  aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  return nullptr;
+}
+
 DedicatedWorkerGlobalScope::DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate)
 : WorkerGlobalScope(aWorkerPrivate)
 {
 }
 
 JSObject*
 DedicatedWorkerGlobalScope::WrapGlobalObject(JSContext* aCx)
 {
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -3,16 +3,19 @@
  * 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_workerscope_h__
 #define mozilla_dom_workerscope_h__
 
 #include "Workers.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/Headers.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/dom/UnionTypes.h"
 
 namespace mozilla {
 namespace dom {
 
 class Console;
 class Function;
 class Promise;
 
@@ -115,16 +118,19 @@ public:
   IMPL_EVENT_HANDLER(online)
   IMPL_EVENT_HANDLER(offline)
   IMPL_EVENT_HANDLER(close)
 
   void
   Dump(const Optional<nsAString>& aString) const;
 
   Performance* GetPerformance();
+
+  already_AddRefed<Promise>
+  Fetch(const RequestOrScalarValueString& aInput, const RequestInit& aInit, ErrorResult& aRv);
 };
 
 class DedicatedWorkerGlobalScope MOZ_FINAL : public WorkerGlobalScope
 {
   ~DedicatedWorkerGlobalScope() { }
 
 public:
   explicit DedicatedWorkerGlobalScope(WorkerPrivate* aWorkerPrivate);
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -81,15 +81,16 @@ include('/ipc/chromium/chromium-config.m
 FINAL_LIBRARY = 'xul'
 
 TEST_DIRS += [
     'test/extensions/bootstrap',
     'test/extensions/traditional',
 ]
 
 MOCHITEST_MANIFESTS += [
+    'test/fetch/mochitest.ini',
     'test/mochitest.ini',
     'test/serviceworkers/mochitest.ini',
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+  worker_interfaces.js
+
+[test_interfaces.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+MOCHITEST_MANIFESTS += ['mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/test_interfaces.html
@@ -0,0 +1,48 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1017613 - Test fetch API interfaces</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  function checkEnabled() {
+    var worker = new Worker("worker_interfaces.js");
+    worker.onmessage = function(event) {
+
+      if (event.data.type == 'finish') {
+        SimpleTest.finish();
+      } else if (event.data.type == 'status') {
+        ok(event.data.status, event.data.msg);
+      }
+    }
+
+    worker.onerror = function(event) {
+      ok(false, "Worker had an error: " + event.data);
+      SimpleTest.finish();
+    };
+
+    worker.postMessage(true);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.fetch.enabled", true]
+  ]}, function() {
+    checkEnabled();
+  });
+</script>
+</pre>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/fetch/worker_interfaces.js
@@ -0,0 +1,12 @@
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + " " + msg + "\n");
+  postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+onmessage = function() {
+  ok(typeof Headers === "function", "Headers should be defined");
+  ok(typeof Request === "function", "Request should be defined");
+  ok(typeof Response === "function", "Response should be defined");
+  ok(typeof fetch === "function", "fetch() should be defined");
+  postMessage({ type: 'finish' });
+}
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -530,16 +530,18 @@ protected:
 class PathBuilder : public PathSink
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(PathBuilder)
   /** Finish writing to the path and return a Path object that can be used for
    * drawing. Future use of the builder results in a crash!
    */
   virtual TemporaryRef<Path> Finish() = 0;
+
+  virtual BackendType GetBackendType() const = 0;
 };
 
 struct Glyph
 {
   uint32_t mIndex;
   Point mPosition;
 };
 
--- a/gfx/2d/PathCG.h
+++ b/gfx/2d/PathCG.h
@@ -42,16 +42,18 @@ public:
                                  const Point &aCP2);
   virtual void Close();
   virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
                    Float aEndAngle, bool aAntiClockwise = false);
   virtual Point CurrentPoint() const;
 
   virtual TemporaryRef<Path> Finish();
 
+  virtual BackendType GetBackendType() const { return BackendType::COREGRAPHICS; }
+
 private:
   friend class PathCG;
   friend class ScaledFontMac;
 
   void EnsureActive(const Point &aPoint);
 
   CGMutablePathRef mCGPath;
   Point mCurrentPoint;
--- a/gfx/2d/PathCairo.h
+++ b/gfx/2d/PathCairo.h
@@ -30,16 +30,18 @@ public:
   virtual void QuadraticBezierTo(const Point &aCP1,
                                  const Point &aCP2);
   virtual void Close();
   virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
                    float aEndAngle, bool aAntiClockwise = false);
   virtual Point CurrentPoint() const;
   virtual TemporaryRef<Path> Finish();
 
+  virtual BackendType GetBackendType() const { return BackendType::CAIRO; }
+
 private: // data
   friend class PathCairo;
 
   FillRule mFillRule;
   std::vector<cairo_path_data_t> mPathData;
   // It's easiest to track this here, parsing the path data to find the current
   // point is a little tricky.
   Point mCurrentPoint;
--- a/gfx/2d/PathD2D.h
+++ b/gfx/2d/PathD2D.h
@@ -37,16 +37,18 @@ public:
                                  const Point &aCP2);
   virtual void Close();
   virtual void Arc(const Point &aOrigin, Float aRadius, Float aStartAngle,
                    Float aEndAngle, bool aAntiClockwise = false);
   virtual Point CurrentPoint() const;
 
   virtual TemporaryRef<Path> Finish();
 
+  virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; }
+
   ID2D1GeometrySink *GetSink() { return mSink; }
 
 private:
   friend class PathD2D;
 
   void EnsureActive(const Point &aPoint);
 
   RefPtr<ID2D1GeometrySink> mSink;
--- a/gfx/2d/PathRecording.h
+++ b/gfx/2d/PathRecording.h
@@ -67,16 +67,18 @@ public:
 
   /* Point the current subpath is at - or where the next subpath will start
    * if there is no active subpath.
    */
   virtual Point CurrentPoint() const;
 
   virtual TemporaryRef<Path> Finish();
 
+  virtual BackendType GetBackendType() const { return BackendType::RECORDING; }
+
 private:
   friend class PathRecording;
 
   RefPtr<PathBuilder> mPathBuilder;
   FillRule mFillRule;
   std::vector<PathOp> mPathOps;
 };
 
--- a/gfx/2d/PathSkia.h
+++ b/gfx/2d/PathSkia.h
@@ -31,16 +31,18 @@ public:
   virtual void Close();
   virtual void Arc(const Point &aOrigin, float aRadius, float aStartAngle,
                    float aEndAngle, bool aAntiClockwise = false);
   virtual Point CurrentPoint() const;
   virtual TemporaryRef<Path> Finish();
 
   void AppendPath(const SkPath &aPath);
 
+  virtual BackendType GetBackendType() const { return BackendType::SKIA; }
+
 private:
 
   void SetFillRule(FillRule aFillRule);
 
   SkPath mPath;
   FillRule mFillRule;
 };
 
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -20,16 +20,17 @@ namespace layers {
 class GeckoContentController
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
 
   /**
    * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
    * Implementations per-platform are responsible for actually handling this.
+   * This method will always be called on the Gecko main thread.
    */
   virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
 
   /**
    * Acknowledges the recipt of a scroll offset update for the scrollable
    * frame with the given scroll id. This is used to maintain consistency
    * between APZ and other sources of scroll changes.
    */
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -2398,17 +2398,22 @@ GetDisplayPortRect(const FrameMetrics& a
 
 void
 AsyncPanZoomController::DispatchRepaintRequest(const FrameMetrics& aFrameMetrics) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
     LogRendertraceRect(GetGuid(), "requested displayport", "yellow", GetDisplayPortRect(aFrameMetrics));
 
-    controller->RequestContentRepaint(aFrameMetrics);
+    if (NS_IsMainThread()) {
+      controller->RequestContentRepaint(aFrameMetrics);
+    } else {
+      NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>(
+        controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics));
+    }
     mLastDispatchedPaintMetrics = aFrameMetrics;
   }
 }
 
 void
 AsyncPanZoomController::FireAsyncScrollOnTimeout()
 {
   if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
--- a/gfx/layers/apz/src/GestureEventListener.cpp
+++ b/gfx/layers/apz/src/GestureEventListener.cpp
@@ -62,17 +62,17 @@ GestureEventListener::GestureEventListen
 }
 
 GestureEventListener::~GestureEventListener()
 {
 }
 
 nsEventStatus GestureEventListener::HandleInputEvent(const MultiTouchInput& aEvent)
 {
-  GEL_LOG("Receiving event type %d with %d touches in state %d\n", aEvent.mType, aEvent.mTouches.Length(), mState);
+  GEL_LOG("Receiving event type %d with %lu touches in state %d\n", aEvent.mType, aEvent.mTouches.Length(), mState);
 
   nsEventStatus rv = nsEventStatus_eIgnore;
 
   // Cache the current event since it may become the single or long tap that we
   // send.
   mLastTouchInput = aEvent;
 
   switch (aEvent.mType) {
@@ -250,16 +250,20 @@ nsEventStatus GestureEventListener::Hand
                                    mLastTouchInput.mTime,
                                    mLastTouchInput.mTimeStamp,
                                    GetCurrentFocus(mLastTouchInput),
                                    currentSpan,
                                    currentSpan,
                                    mLastTouchInput.modifiers);
 
       rv = mAsyncPanZoomController->HandleGestureEvent(pinchEvent);
+    } else {
+      // Prevent APZC::OnTouchMove from processing a move event when two
+      // touches are active
+      rv = nsEventStatus_eConsumeNoDefault;
     }
 
     mPreviousSpan = currentSpan;
     break;
   }
 
   case GESTURE_PINCH: {
     if (mLastTouchInput.mTouches.Length() < 2) {
--- a/gfx/layers/apz/src/InputBlockState.cpp
+++ b/gfx/layers/apz/src/InputBlockState.cpp
@@ -71,17 +71,17 @@ TouchBlockState::TimeoutContentResponse(
 }
 
 bool
 TouchBlockState::SetAllowedTouchBehaviors(const nsTArray<TouchBehaviorFlags>& aBehaviors)
 {
   if (mAllowedTouchBehaviorSet) {
     return false;
   }
-  TBS_LOG("%p got allowed touch behaviours for %d points\n", this, aBehaviors.Length());
+  TBS_LOG("%p got allowed touch behaviours for %lu points\n", this, aBehaviors.Length());
   mAllowedTouchBehaviors.AppendElements(aBehaviors);
   mAllowedTouchBehaviorSet = true;
   return true;
 }
 
 bool
 TouchBlockState::CopyAllowedTouchBehaviorsFrom(const TouchBlockState& aOther)
 {
@@ -145,25 +145,25 @@ TouchBlockState::AddEvent(const MultiTou
 {
   TBS_LOG("%p adding event of type %d\n", this, aEvent.mType);
   mEvents.AppendElement(aEvent);
 }
 
 void
 TouchBlockState::DropEvents()
 {
-  TBS_LOG("%p dropping %d events\n", this, mEvents.Length());
+  TBS_LOG("%p dropping %lu events\n", this, mEvents.Length());
   mEvents.Clear();
 }
 
 MultiTouchInput
 TouchBlockState::RemoveFirstEvent()
 {
   MOZ_ASSERT(!mEvents.IsEmpty());
-  TBS_LOG("%p returning first of %d events\n", this, mEvents.Length());
+  TBS_LOG("%p returning first of %lu events\n", this, mEvents.Length());
   MultiTouchInput event = mEvents[0];
   mEvents.RemoveElementAt(0);
   return event;
 }
 
 bool
 TouchBlockState::TouchActionAllowsPinchZoom() const
 {
--- a/gfx/layers/client/ClientImageLayer.cpp
+++ b/gfx/layers/client/ClientImageLayer.cpp
@@ -100,16 +100,23 @@ protected:
       return mImageClientTypeContainer;
     }
 
     if (mContainer->IsAsync()) {
       mImageClientTypeContainer = CompositableType::BUFFER_BRIDGE;
       return mImageClientTypeContainer;
     }
 
+    // Since D3D11 TextureClient doesn't have an internal buffer, modifying the
+    // front buffer directly may break the transactional property of layer updates.
+    if (ClientManager()->GetCompositorBackendType() == LayersBackend::LAYERS_D3D11) {
+      mImageClientTypeContainer = CompositableType::BUFFER_IMAGE_BUFFERED;
+      return mImageClientTypeContainer;
+    }
+
     AutoLockImage autoLock(mContainer);
 
 #ifdef MOZ_WIDGET_GONK
     // gralloc buffer needs CompositableType::BUFFER_IMAGE_BUFFERED to prevent
     // the buffer's usage conflict.
     if (autoLock.GetImage()->GetFormat() == ImageFormat::OVERLAY_IMAGE) {
       mImageClientTypeContainer = CompositableType::IMAGE_OVERLAY;
       return mImageClientTypeContainer;
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -236,13 +236,13 @@ class OTS_API OTSContext {
     virtual TableAction GetTableAction(uint32_t tag) { return ots::TABLE_ACTION_DEFAULT; }
 };
 
 // Force to disable debug output even when the library is compiled with
 // -DOTS_DEBUG.
 void DisableDebugOutput();
 
 // Enable WOFF2 support(experimental).
-void EnableWOFF2();
+void OTS_API EnableWOFF2();
 
 }  // namespace ots
 
 #endif  // OPENTYPE_SANITISER_H_
--- a/gfx/ots/src/moz.build
+++ b/gfx/ots/src/moz.build
@@ -39,16 +39,17 @@ UNIFIED_SOURCES += [
     'metrics.cc',
     'name.cc',
     'os2.cc',
     'ots.cc',
     'post.cc',
     'prep.cc',
     'vhea.cc',
     'vmtx.cc',
+    'woff2.cc',
 ]
 
 MSVC_ENABLE_PGO = True
 
 if CONFIG['GKMEDIAS_SHARED_LIBRARY']:
     NO_VISIBILITY_FLAGS = True
 
 FINAL_LIBRARY = 'gkmedias'
@@ -56,10 +57,11 @@ FINAL_LIBRARY = 'gkmedias'