Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 08 Jun 2014 22:13:38 -0400
changeset 207851 96e6a5f9fa3f2affa3cf6b3e412ec2d8d140a8ef
parent 207850 a2cbf919caeb896aa306464c23f2aeb170bca072 (current diff)
parent 207823 9305a8ec77fe3efc902b73c26807558151e23cdb (diff)
child 207852 5c5e7c0f31c5718ddfc35bfc95ee71f54649522b
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone32.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 b2g-inbound. a=merge
--- a/browser/components/preferences/in-content/advanced.xul
+++ b/browser/components/preferences/in-content/advanced.xul
@@ -193,22 +193,20 @@
                   label="&checkSpelling.label;"
                   accesskey="&checkSpelling.accesskey;"
                   onsyncfrompreference="return gAdvancedPane.readCheckSpelling();"
                   onsynctopreference="return gAdvancedPane.writeCheckSpelling();"
                   preference="layout.spellcheckDefault"/>
       </groupbox>
 #ifdef HAVE_SHELL_SERVICE
       <!-- System Defaults -->
-      <groupbox id="systemDefaultsGroup" orient="vertical">
+      <groupbox id="systemDefaultsGroup" orient="vertical" align="start">
         <caption><label>&systemDefaults.label;</label></caption>
-
         <checkbox id="alwaysCheckDefault" preference="browser.shell.checkDefaultBrowser"
-                  label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"
-                  flex="1"/>
+                  label="&alwaysCheckDefault.label;" accesskey="&alwaysCheckDefault.accesskey;"/>
         <hbox class="indent">
           <deck id="setDefaultPane">
             <button id="setDefaultButton"
                     label="&setDefault.label;" accesskey="&setDefault.accesskey;"
                     oncommand="gAdvancedPane.setDefaultBrowser();"
                     preference="pref.general.disable_button.default_browser"/>
             <label id="isDefaultLabel">&isDefault.label;</label>
           </deck>
@@ -288,21 +286,23 @@
         <caption><label>&httpCache.label;</label></caption>
 
         <hbox align="center">
           <label id="actualDiskCacheSize" flex="1"/>
           <button id="clearCacheButton" icon="clear"
                   label="&clearCacheNow.label;" accesskey="&clearCacheNow.accesskey;"
                   oncommand="gAdvancedPane.clearCache();"/>
         </hbox>
-        <checkbox preference="browser.cache.disk.smart_size.enabled"
-                  id="allowSmartSize" flex="1"
-                  onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
-                  label="&overrideSmartCacheSize.label;"
-                  accesskey="&overrideSmartCacheSize.accesskey;"/>
+        <hbox>
+          <checkbox preference="browser.cache.disk.smart_size.enabled"
+                    id="allowSmartSize"
+                    onsyncfrompreference="return gAdvancedPane.readSmartSizeEnabled();"
+                    label="&overrideSmartCacheSize.label;"
+                    accesskey="&overrideSmartCacheSize.accesskey;"/>
+        </hbox>
         <hbox align="center" class="indent">
           <label id="useCacheBefore" control="cacheSize"
                  accesskey="&limitCacheSizeBefore.accesskey;">
             &limitCacheSizeBefore.label;
           </label>
           <textbox id="cacheSize" type="number" size="4" max="1024"
                   preference="browser.cache.disk.capacity"
                   onsyncfrompreference="return gAdvancedPane.readCacheSize();"
@@ -318,20 +318,21 @@
 
         <hbox align="center">
           <label id="actualAppCacheSize" flex="1"/>
           <button id="clearOfflineAppCacheButton" icon="clear"
                   label="&clearOfflineAppCacheNow.label;" accesskey="&clearOfflineAppCacheNow.accesskey;"
                   oncommand="gAdvancedPane.clearOfflineAppCache();"/>
         </hbox>
         <hbox align="center">
-          <checkbox id="offlineNotify" flex="1"
+          <checkbox id="offlineNotify"
                     label="&offlineNotify.label;" accesskey="&offlineNotify.accesskey;"
                     preference="browser.offline-apps.notify"
                     onsyncfrompreference="return gAdvancedPane.readOfflineNotify();"/>
+          <spacer flex="1"/>
           <button id="offlineNotifyExceptions"
                   label="&offlineNotifyExceptions.label;"
                   accesskey="&offlineNotifyExceptions.accesskey;"
                   oncommand="gAdvancedPane.showOfflineExceptions();"/>
         </hbox>
         <hbox>
           <vbox flex="1">
             <label id="offlineAppsListLabel">&offlineAppsList2.label;</label>
@@ -351,17 +352,17 @@
           </vbox>
         </hbox>
       </groupbox>
     </tabpanel>
 
     <!-- Update -->
     <tabpanel id="updatePanel" orient="vertical">
 #ifdef MOZ_UPDATER
-      <groupbox id="updateApp">
+      <groupbox id="updateApp" align="start">
         <caption><label>&updateApp.label;</label></caption>
         <radiogroup id="updateRadioGroup"
                     oncommand="gAdvancedPane.updateWritePrefs();">
 #ifdef XP_WIN
 #ifdef MOZ_METRO
               <radio id="autoMetro"
                      value="autoMetro"
                      label="&updateAutoMetro.label;"
@@ -403,17 +404,17 @@
 #ifdef MOZ_MAINTENANCE_SERVICE
         <checkbox id="useService"
                   label="&useService.label;"
                   accesskey="&useService.accesskey;"
                   preference="app.update.service.enabled"/>
 #endif
       </groupbox>
 #endif
-      <groupbox id="updateOthers">
+      <groupbox id="updateOthers" align="start">
         <caption><label>&updateOthers.label;</label></caption>
         <checkbox id="enableSearchUpdate"
                   label="&enableSearchUpdate.label;"
                   accesskey="&enableSearchUpdate.accesskey;"
                   preference="browser.search.update"/>
       </groupbox>
     </tabpanel>
 
--- a/browser/components/preferences/in-content/main.xul
+++ b/browser/components/preferences/in-content/main.xul
@@ -183,26 +183,28 @@
               label="&chooseFolderMac.label;"
 #else
               accesskey="&chooseFolderWin.accesskey;"
               label="&chooseFolderWin.label;"
 #endif
               preference="browser.download.folderList"
               onsynctopreference="return gMainPane.getFolderListPref();"/>
     </hbox>
-    <radio id="alwaysAsk"
-           value="false"
-           label="&alwaysAsk.label;"
-           accesskey="&alwaysAsk.accesskey;"/>
+    <hbox>
+      <radio id="alwaysAsk"
+             value="false"
+             label="&alwaysAsk.label;"
+             accesskey="&alwaysAsk.accesskey;"/>
+    </hbox>
   </radiogroup>
 </groupbox>
 
 <!-- Tab preferences -->
 <groupbox data-category="paneGeneral"
-          hidden="true">
+          hidden="true" align="start">
     <caption><label>&tabsGroup.label;</label></caption>
     <checkbox id="linkTargeting" label="&newWindowsAsTabs.label;"
               accesskey="&newWindowsAsTabs.accesskey;"
               preference="browser.link.open_newwindow"
               onsyncfrompreference="return gMainPane.readLinkTarget();"
               onsynctopreference="return gMainPane.writeLinkTarget();"/>
 
     <checkbox id="warnCloseMultiple" label="&warnCloseMultipleTabs.label;"
--- a/browser/components/preferences/in-content/security.xul
+++ b/browser/components/preferences/in-content/security.xul
@@ -40,58 +40,63 @@
   <label class="header-name">&paneSecurity.title;</label>
 </hbox>
 
 <!-- addons, forgery (phishing) UI -->
 <groupbox id="addonsPhishingGroup" data-category="paneSecurity" hidden="true">
   <caption><label>&general.label;</label></caption>
 
   <hbox id="addonInstallBox">
-    <checkbox id="warnAddonInstall" flex="1"
+    <checkbox id="warnAddonInstall"
               label="&warnAddonInstall.label;"
               accesskey="&warnAddonInstall.accesskey;"
               preference="xpinstall.whitelist.required"
               onsyncfrompreference="return gSecurityPane.readWarnAddonInstall();"/>
+    <spacer flex="1"/>
     <button id="addonExceptions"
             label="&addonExceptions.label;"
             accesskey="&addonExceptions.accesskey;"
             oncommand="gSecurityPane.showAddonExceptions();"/>
   </hbox>
 
   <separator class="thin"/>
-  <checkbox id="blockAttackSites"
-            label="&blockAttackSites.label;"
-            accesskey="&blockAttackSites.accesskey;"
-            preference="browser.safebrowsing.malware.enabled" />
-  <checkbox id="blockWebForgeries"
-            label="&blockWebForgeries.label;"
-            accesskey="&blockWebForgeries.accesskey;"
-            preference="browser.safebrowsing.enabled" />
+  <vbox align="start">
+    <checkbox id="blockAttackSites"
+              label="&blockAttackSites.label;"
+              accesskey="&blockAttackSites.accesskey;"
+              preference="browser.safebrowsing.malware.enabled" />
+    <checkbox id="blockWebForgeries"
+              label="&blockWebForgeries.label;"
+              accesskey="&blockWebForgeries.accesskey;"
+              preference="browser.safebrowsing.enabled" />
+  </vbox>
 </groupbox>
 
 <!-- Passwords -->
 <groupbox id="passwordsGroup" orient="vertical" data-category="paneSecurity" hidden="true">
   <caption><label>&passwords.label;</label></caption>
 
   <hbox id="savePasswordsBox">
-    <checkbox id="savePasswords" flex="1"
+    <checkbox id="savePasswords"
               label="&rememberPasswords.label;" accesskey="&rememberPasswords.accesskey;"
               preference="signon.rememberSignons"
               onsyncfrompreference="return gSecurityPane.readSavePasswords();"/>
+    <spacer flex="1"/>
     <button id="passwordExceptions"
             label="&passwordExceptions.label;"
             accesskey="&passwordExceptions.accesskey;"
             oncommand="gSecurityPane.showPasswordExceptions();"
             preference="pref.privacy.disable_button.view_passwords_exceptions"/>
   </hbox>
   <hbox id="masterPasswordBox">
-    <checkbox id="useMasterPassword" flex="1"
+    <checkbox id="useMasterPassword"
               oncommand="gSecurityPane.updateMasterPasswordButton();"
               label="&useMasterPassword.label;"
               accesskey="&useMasterPassword.accesskey;"/>
+    <spacer flex="1"/>
     <button id="changeMasterPassword"
             label="&changeMasterPassword.label;"
             accesskey="&changeMasterPassword.accesskey;"
             oncommand="gSecurityPane.changeMasterPassword();"/>
   </hbox>
 
   <hbox id="showPasswordsBox">
     <spacer flex="1"/>
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -190,17 +190,17 @@
 
   <!-- These panels are for the Firefox Accounts identity provider -->
   <vbox id="fxaDeterminingStatus" align="center">
     <spacer flex="1"/>
     <label>&determiningAcctStatus.label;</label>
     <spacer flex="1"/>
   </vbox>
 
-  <vbox id="noFxaAccount">
+  <vbox id="noFxaAccount" align="start">
     <label>&welcome.description;</label>
     <label class="text-link"
            onclick="gSyncPane.signUp(); return false;">
       &welcome.createAccount.label;
     </label>
     <label class="text-link"
            onclick="gSyncPane.signIn(); return false;">
       &welcome.signIn.label;
--- a/browser/components/translation/TranslationDocument.jsm
+++ b/browser/components/translation/TranslationDocument.jsm
@@ -132,42 +132,53 @@ this.TranslationDocument.prototype = {
     if (item.isSimpleRoot) {
       let text = item.nodeRef.firstChild.nodeValue.trim();
       item.original = [text];
       return text;
     }
 
     let str = "";
     item.original = [];
+    let wasLastItemPlaceholder = false;
 
     for (let child of item.nodeRef.childNodes) {
       if (child.nodeType == TEXT_NODE) {
         let x = child.nodeValue.trim();
-        str += x;
-        item.original.push(x);
+        if (x != "") {
+          item.original.push(x);
+          str += x;
+          wasLastItemPlaceholder = false;
+        }
         continue;
       }
 
       let objInMap = this.itemsMap.get(child);
       if (objInMap && !objInMap.isRoot) {
         // If this childNode is present in the itemsMap, it means
         // it's a translation node: it has useful content for translation.
         // In this case, we need to stringify this node.
         // However, if this item is a root, we should skip it here in this
         // object's child list (and just add a placeholder for it), because
         // it will be stringfied separately for being a root.
         item.original.push(objInMap);
         str += this.generateTextForItem(objInMap);
+        wasLastItemPlaceholder = false;
       } else {
         // Otherwise, if this node doesn't contain any useful content,
         // or if it is a root itself, we can replace it with a placeholder node.
         // We can't simply eliminate this node from our string representation
         // because that could change the HTML structure (e.g., it would
         // probably merge two separate text nodes).
-        str += '<br/>';
+        // It's not necessary to add more than one placeholder in sequence;
+        // we can optimize them away.
+        if (!wasLastItemPlaceholder) {
+          item.original.push(TranslationItem_NodePlaceholder);
+          str += '<br>';
+          wasLastItemPlaceholder = true;
+        }
       }
     }
 
     return generateTranslationHtmlForItem(item, str);
   },
 
   /**
    * Changes the document to display its translated
@@ -254,18 +265,18 @@ function TranslationItem(node, id, isRoo
   this.children = [];
 }
 
 TranslationItem.prototype = {
   isRoot: false,
   isSimpleRoot: false,
 
   toString: function() {
-    let rootType = this._isRoot
-                   ? (this._isSimpleRoot ? ' (simple root)' : ' (non simple root)')
+    let rootType = this.isRoot
+                   ? (this.isSimpleRoot ? ' (simple root)' : ' (non simple root)')
                    : '';
     return "[object TranslationItem: <" + this.nodeRef.localName + ">"
            + rootType + "]";
   },
 
   /**
    * This function will parse the result of the translation of one translation
    * item. If this item was a simple root, all we sent was a plain-text version
@@ -320,26 +331,39 @@ TranslationItem.prototype = {
    *                 or "original".
    */
   swapText: function(target) {
     swapTextForItem(this, target);
   }
 };
 
 /**
+ * This object represents a placeholder item for translation. It's similar to
+ * the TranslationItem class, but it represents nodes that have no meaningful
+ * content for translation. These nodes will be replaced by "<br>" in a
+ * translation request. It's necessary to keep them to use it as a mark
+ * for correct positioning and spliting of text nodes.
+ */
+const TranslationItem_NodePlaceholder = {
+  toString: function() {
+    return "[object TranslationItem_NodePlaceholder]";
+  }
+};
+
+/**
  * Generate the outer HTML representation for a given item.
  *
  * @param   item       A TranslationItem object.
  * param    content    The inner content for this item.
  * @returns string     The outer HTML needed for translation
  *                     of this item.
  */
 function generateTranslationHtmlForItem(item, content) {
   let localName = item.isRoot ? "div" : "b";
-  return '<' + localName + ' id="n' + item.id + '">' +
+  return '<' + localName + ' id=n' + item.id + '>' +
          content +
          "</" + localName + ">";
 }
 
  /**
  * Regenerate the text string that represents a TranslationItem object,
  * with data from its "original" array. The array must have already
  * been created by TranslationDocument.generateTextForItem().
@@ -349,32 +373,23 @@ function generateTranslationHtmlForItem(
  * @returns        A string representation of the TranslationItem.
  */
 function regenerateTextFromOriginalHelper(item) {
   if (item.isSimpleRoot) {
     return item.original[0];
   }
 
   let str = "";
-
-  let wasLastItemText = false;
   for (let child of item.original) {
     if (child instanceof TranslationItem) {
       str += regenerateTextFromOriginalHelper(child);
-      wasLastItemText = false;
+    } else if (child === TranslationItem_NodePlaceholder) {
+      str += "<br>";
     } else {
-      // The non-significant elements (which were replaced with <br/>
-      // during the first call to generateTextForItem) are not stored
-      // in the original array. If they are not properly re-generated,
-      // two adjacent text nodes would be merged into one.
-      if (wasLastItemText) {
-        str += "<br/>";
-      }
       str += child;
-      wasLastItemText = true;
     }
   }
 
   return generateTranslationHtmlForItem(item, str);
 }
 
 /**
  * Helper function to parse a HTML doc result.
@@ -390,30 +405,89 @@ function regenerateTextFromOriginalHelpe
  *
  * For text nodes we simply add it as a string.
  */
 function parseResultNode(item, node) {
   item.translation = [];
   for (let child of node.childNodes) {
     if (child.nodeType == TEXT_NODE) {
       item.translation.push(child.nodeValue);
+    } else if (child.localName == "br") {
+      item.translation.push(TranslationItem_NodePlaceholder);
     } else {
       let translationItemChild = item.getChildById(child.id);
 
       if (translationItemChild) {
         item.translation.push(translationItemChild);
         parseResultNode(translationItemChild, child);
       }
     }
   }
 }
 
 /**
  * Helper function to swap the text of a TranslationItem
  * between its original and translated states.
+ * How it works:
+ *
+ * The function iterates through the target array (either the `original` or
+ * `translation` array from the TranslationItem), while also keeping a pointer
+ * to a current position in the child nodes from the actual DOM node that we
+ * are modifying. This pointer is moved forward after each item of the array
+ * is translated. If, at any given time, the pointer doesn't match the expected
+ * node that was supposed to be seen, it means that the original and translated
+ * contents have a different ordering, and thus we need to adjust that.
+ *
+ * A full example of the reordering process, swapping from Original to
+ * Translation:
+ *
+ *    Original (en): <div>I <em>miss</em> <b>you</b></div>
+ *
+ * Translation (fr): <div><b>Tu</b> me <em>manques</em></div>
+ *
+ * Step 1:
+ *   pointer points to firstChild of the DOM node, textnode "I "
+ *   first item in item.translation is [object TranslationItem <b>]
+ *
+ *   pointer does not match the expected element, <b>. So let's move <b> to the
+ *   pointer position.
+ *
+ *   Current state of the DOM:
+ *     <div><b>you</b>I <em>miss</em> </div>
+ *
+ * Step 2:
+ *   pointer moves forward to nextSibling, textnode "I " again.
+ *   second item in item.translation is the string " me "
+ *
+ *   pointer points to a text node, and we were expecting a text node. Match!
+ *   just replace the text content.
+ *
+ *   Current state of the DOM:
+ *     <div><b>you</b> me <em>miss</em> </div>
+ *
+ * Step 3:
+ *   pointer moves forward to nextSibling, <em>miss</em>
+ *   third item in item.translation is [object TranslationItem <em>]
+ *
+ *   pointer points to the expected node. Match! Nothing to do.
+ *
+ * Step 4:
+ *   all items in this item.translation were transformed. The remaining
+ *   text nodes are cleared to "", and domNode.normalize() removes them.
+ *
+ *   Current state of the DOM:
+ *     <div><b>you</b> me <em>miss</em></div>
+ *
+ * Further steps:
+ *   After that, the function will visit the child items (from the visitStack),
+ *   and the text inside the <b> and <em> nodes will be swapped as well,
+ *   yielding the final result:
+ *
+ *     <div><b>Tu</b> me <em>manques</em></div>
+ *
  *
  * @param item     A TranslationItem object
  * @param target   A string that is either "translation"
  *                 or "original".
  */
 function swapTextForItem(item, target) {
   // visitStack is the stack of items that we still need to visit.
   // Let's start the process by adding the root item.
@@ -424,76 +498,176 @@ function swapTextForItem(item, target) {
     let curItem = visitStack.shift();
 
     let domNode = curItem.nodeRef;
     if (!domNode) {
       // Skipping this item due to a missing node.
       continue;
     }
 
-    let sourceNodeCount = 0;
-
     if (!curItem[target]) {
       // Translation not found for this item. This could be due to
       // an error in the server response. For example, if a translation
       // was broken in various chunks, and one of the chunks failed,
       // the items from that chunk will be missing its "translation"
       // field.
       continue;
     }
 
+    domNode.normalize();
+
+    // curNode points to the child nodes of the DOM node that we are
+    // modifying. During most of the process, while the target array is
+    // being iterated (in the for loop below), it should walk together with
+    // the array and be pointing to the correct node that needs to modified.
+    // If it's not pointing to it, that means some sort of node reordering
+    // will be necessary to produce the correct translation.
+    // Note that text nodes don't need to be reordered, as we can just replace
+    // the content of one text node with another.
+    //
+    // curNode starts in the firstChild...
+    let curNode = domNode.firstChild;
+
+    // ... actually, let's make curNode start at the first useful node (either
+    // a non-blank text node or something else). This is not strictly necessary,
+    // as the reordering algorithm would correctly handle this case. However,
+    // this better aligns the resulting translation with the DOM content of the
+    // page, avoiding cases that would need to be unecessarily reordered.
+    //
+    // An example of how this helps:
+    //
+    // ---- Original: <div>                <b>Hello </b> world.</div>
+    //                       ^textnode 1      ^item 1      ^textnode 2
+    //
+    // - Translation: <div><b>Hallo </b> Welt.</div>
+    //
+    // Transformation process without this optimization:
+    //   1 - start pointer at textnode 1
+    //   2 - move item 1 to first position inside the <div>
+    //
+    //       Node now looks like: <div><b>Hello </b>[         ][ world.]</div>
+    //                                         textnode 1^       ^textnode 2
+    //
+    //   3 - replace textnode 1 with " Welt."
+    //   4 - clear remaining text nodes (in this case, textnode 2)
+    //
+    // Transformation process with this optimization:
+    //   1 - start pointer at item 1
+    //   2 - item 1 is already in position
+    //   3 - replace textnode 2 with " Welt."
+    //
+    // which completely avoids any node reordering, and requires only one
+    // text change instead of two (while also leaving the page closer to
+    // its original state).
+    while (curNode &&
+           curNode.nodeType == TEXT_NODE &&
+           curNode.nodeValue.trim() == "") {
+      curNode = curNode.nextSibling;
+    }
+
     // Now let's walk through all items in the `target` array of the
     // TranslationItem. This means either the TranslationItem.original or
     // TranslationItem.translation array.
-    for (let child of curItem[target]) {
-      // If the array element is another TranslationItem object, let's
-      // add it to the stack to be visited
-      if (child instanceof TranslationItem) {
-        // Adding this child to the stack.
-        visitStack.push(child);
-        continue;
-      }
+    for (let targetItem of curItem[target]) {
+
+      if (targetItem instanceof TranslationItem) {
+        // If the array element is another TranslationItem object, let's
+        // add it to the stack to be visited.
+        visitStack.push(targetItem);
+
+        let targetNode = targetItem.nodeRef;
+
+            // If the node is not in the expected position, let's reorder
+            // it into position...
+        if (curNode != targetNode &&
+            // ...unless the page has reparented this node under a totally
+            // different node (or removed it). In this case, all bets are off
+            // on being able to do anything correctly, so it's better not to
+            // bring back the node to this parent.
+            targetNode.parentNode == domNode) {
+
+          // We don't need to null-check curNode because insertBefore(..., null)
+          // does what we need in that case: reorder this node to the end
+          // of child nodes.
+          domNode.insertBefore(targetNode, curNode);
+          curNode = targetNode;
+        }
+
+        // Move pointer forward. Since we do not add empty text nodes to the
+        // list of translation items, we must skip them here too while
+        // traversing the DOM in order to get better alignment between the
+        // text nodes and the translation items.
+        if (curNode) {
+          curNode = getNextSiblingSkippingEmptyTextNodes(curNode);
+        }
 
-      // If it's a string, say, the Nth string of the `target` array, let's
-      // replace the Nth child TextNode of this element with this string.
-      // During our translation process we skipped all empty text nodes, so we
-      // must also skip them here. If there are not enough text nodes to be used,
-      // a new text node will be created and appended to the end of the element.
-      let targetTextNode = getNthNonEmptyTextNodeFromElement(sourceNodeCount++, domNode);
+      } else if (targetItem === TranslationItem_NodePlaceholder) {
+        // If the current item is a placeholder node, we need to move
+        // our pointer "past" it, jumping from one side of a block of
+        // elements + empty text nodes to the other side. Even if
+        // non-placeholder elements exists inside the jumped block,
+        // they will be pulled correctly later in the process when the
+        // targetItem for those nodes are handled.
+
+        while (curNode &&
+               (curNode.nodeType != TEXT_NODE ||
+                curNode.nodeValue.trim() == "")) {
+          curNode = curNode.nextSibling;
+        }
 
-      // A trailing and a leading space must be preserved because they are meaningful in HTML.
-      let preSpace = targetTextNode.nodeValue.startsWith(" ") ? " " : "";
-      let endSpace = targetTextNode.nodeValue.endsWith(" ") ? " " : "";
-      targetTextNode.nodeValue = preSpace + child + endSpace;
+      } else {
+        // Finally, if it's a text item, we just need to find the next
+        // text node to use. Text nodes don't need to be reordered, so
+        // the first one found can be used.
+        while (curNode && curNode.nodeType != TEXT_NODE) {
+          curNode = curNode.nextSibling;
+        }
+
+        // If none was found and we reached the end of the child nodes,
+        // let's create a new one.
+        if (!curNode) {
+          // We don't know if the original content had a space or not,
+          // so the best bet is to create the text node with " " which
+          // will add one space at the beginning and one at the end.
+          curNode = domNode.appendChild(domNode.ownerDocument.createTextNode(" "));
+        }
+
+        // A trailing and a leading space must be preserved because
+        // they are meaningful in HTML.
+        let preSpace = /^\s/.test(curNode.nodeValue) ? " " : "";
+        let endSpace = /\s$/.test(curNode.nodeValue) ? " " : "";
+
+        curNode.nodeValue = preSpace + targetItem + endSpace;
+        curNode = getNextSiblingSkippingEmptyTextNodes(curNode);
+      }
     }
 
-    // The translated version of a node might have less text nodes than its original
-    // version. If that's the case, let's clear the remaining nodes.
-    if (sourceNodeCount > 0) {
-      clearRemainingNonEmptyTextNodesFromElement(sourceNodeCount, domNode);
+    // The translated version of a node might have less text nodes than its
+    // original version. If that's the case, let's clear the remaining nodes.
+    if (curNode) {
+      clearRemainingNonEmptyTextNodesFromElement(curNode);
     }
+
+    // And remove any garbage "" nodes left after clearing.
+    domNode.normalize();
   }
 }
 
-function getNthNonEmptyTextNodeFromElement(n, element) {
-  for (let childNode of element.childNodes) {
-    if (childNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
-        childNode.nodeValue.trim() != "") {
-      if (n-- == 0)
-        return childNode;
-    }
+function getNextSiblingSkippingEmptyTextNodes(startSibling) {
+  let item = startSibling.nextSibling;
+  while (item &&
+         item.nodeType == TEXT_NODE &&
+         item.nodeValue.trim() == "") {
+    item = item.nextSibling;
   }
-
-  // If there are not enough DOM nodes, let's create a new one.
-  return element.appendChild(element.ownerDocument.createTextNode(""));
+  return item;
 }
 
-function clearRemainingNonEmptyTextNodesFromElement(start, element) {
-  let count = 0;
-  for (let childNode of element.childNodes) {
-    if (childNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
-        childNode.nodeValue.trim() != "") {
-      if (count++ >= start) {
-        childNode.nodeValue = "";
-      }
+function clearRemainingNonEmptyTextNodesFromElement(startSibling) {
+  let item = startSibling;
+  while (item) {
+    if (item.nodeType == TEXT_NODE &&
+        item.nodeValue != "") {
+      item.nodeValue = "";
     }
+    item = item.nextSibling;
   }
 }
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -22,16 +22,17 @@ support-files =
 [browser_toolbox_options_disable_js.js]
 # [browser_toolbox_raise.js] # Bug 962258
 # skip-if = os == "win"
 [browser_toolbox_ready.js]
 [browser_toolbox_select_event.js]
 [browser_toolbox_sidebar.js]
 [browser_toolbox_tabsswitch_shortcuts.js]
 [browser_toolbox_tool_ready.js]
+[browser_toolbox_window_reload_target.js]
 [browser_toolbox_window_shortcuts.js]
 [browser_toolbox_window_title_changes.js]
 [browser_toolbox_zoom.js]
 [browser_toolbox_custom_host.js]
 
 # We want this test to run for mochitest-dt as well, so we include it here:
 [../../../base/content/test/general/browser_parsable_css.js]
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_window_reload_target.js
@@ -0,0 +1,101 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let Toolbox = devtools.Toolbox;
+
+let target, toolbox, description, reloadsSent, toolIDs;
+
+function test() {
+  waitForExplicitFinish();
+
+  addTab("data:text/html;charset=utf-8,"+
+         "<html><head><title>Test reload</title></head>"+
+         "<body><h1>Testing reload from devtools</h1></body></html>",
+         () => {
+    target = TargetFactory.forTab(gBrowser.selectedTab);
+
+    target.makeRemote().then(() => {
+      toolIDs = gDevTools.getToolDefinitionArray()
+                  .filter(def => def.isTargetSupported(target))
+                  .map(def => def.id);
+      gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.BOTTOM)
+               .then(startReloadTest);
+    });
+  });
+}
+
+function startReloadTest(aToolbox) {
+  toolbox = aToolbox;
+
+  reloadsSent = 0;
+  let reloads = 0;
+  let reloadCounter = (event) => {
+    reloads++;
+    info("Detected reload #"+reloads);
+    is(reloads, reloadsSent, "Reloaded from devtools window once and only for "+description+"");
+  };
+  gBrowser.selectedBrowser.addEventListener('load', reloadCounter, true);
+
+  testAllTheTools("docked", () => {
+    let origHostType = toolbox.hostType;
+    toolbox.switchHost(Toolbox.HostType.WINDOW).then(() => {
+      toolbox.doc.defaultView.focus();
+      testAllTheTools("undocked", () => {
+        toolbox.switchHost(origHostType).then(() => {
+          gBrowser.selectedBrowser.removeEventListener('load', reloadCounter, true);
+          // If we finish too early, the inspector breaks promises:
+          toolbox.getPanel("inspector").once("new-root", finishUp);
+        });
+      });
+    });
+  }, toolIDs.length-1 /* only test 1 tool in docked mode, to cut down test time */);
+}
+
+function testAllTheTools(docked, callback, toolNum=0) {
+  if (toolNum >= toolIDs.length) {
+    return callback();
+  }
+  toolbox.selectTool(toolIDs[toolNum]).then(() => {
+    testReload("toolbox-reload-key", docked, toolIDs[toolNum], () => {
+      testReload("toolbox-reload-key2", docked, toolIDs[toolNum], () => {
+        testReload("toolbox-force-reload-key", docked, toolIDs[toolNum], () => {
+          testReload("toolbox-force-reload-key2", docked, toolIDs[toolNum], () => {
+            testAllTheTools(docked, callback, toolNum+1);
+          });
+        });
+      });
+    });
+  });
+}
+
+function synthesizeKeyForToolbox(keyId) {
+  let el = toolbox.doc.getElementById(keyId);
+  let key = el.getAttribute("key") || el.getAttribute("keycode");
+  let mod = {};
+  el.getAttribute("modifiers").split(" ").forEach((m) => mod[m+"Key"] = true);
+  info("Synthesizing: key="+key+", mod="+JSON.stringify(mod));
+  EventUtils.synthesizeKey(key, mod, toolbox.doc.defaultView);
+}
+
+function testReload(key, docked, toolID, callback) {
+  let complete = () => {
+    gBrowser.selectedBrowser.removeEventListener('load', complete, true);
+    return callback();
+  };
+  gBrowser.selectedBrowser.addEventListener('load', complete, true);
+
+  description = docked+" devtools with tool "+toolID+", key #" + key;
+  info("Testing reload in "+description);
+  synthesizeKeyForToolbox(key);
+  reloadsSent++;
+}
+
+function finishUp() {
+  toolbox.destroy().then(() => {
+    gBrowser.removeCurrentTab();
+
+    target = toolbox = description = reloadsSent = toolIDs = null;
+
+    finish();
+  });
+}
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -242,16 +242,17 @@ Toolbox.prototype = {
         let closeButton = this.doc.getElementById("toolbox-close");
         closeButton.addEventListener("command", this.destroy, true);
 
         this._buildDockButtons();
         this._buildOptions();
         this._buildTabs();
         this._buildButtons();
         this._addKeysToWindow();
+        this._addReloadKeys();
         this._addToolSwitchingKeys();
         this._addZoomKeys();
         this._loadInitialZoom();
 
         this._telemetry.toolOpened("toolbox");
 
         this.selectTool(this._defaultToolId).then(panel => {
           this.emit("ready");
@@ -290,16 +291,29 @@ Toolbox.prototype = {
 
   _splitConsoleOnKeypress: function(e) {
     let responsiveModeActive = this._isResponsiveModeActive();
     if (e.keyCode === e.DOM_VK_ESCAPE && !responsiveModeActive) {
       this.toggleSplitConsole();
     }
   },
 
+  _addReloadKeys: function() {
+    [
+      ["toolbox-reload-key", false],
+      ["toolbox-reload-key2", false],
+      ["toolbox-force-reload-key", true],
+      ["toolbox-force-reload-key2", true]
+    ].forEach(([id, force]) => {
+      this.doc.getElementById(id).addEventListener("command", (event) => {
+        this.reloadTarget(force);
+      }, true);
+    });
+  },
+
   _addToolSwitchingKeys: function() {
     let nextKey = this.doc.getElementById("toolbox-next-tool-key");
     nextKey.addEventListener("command", this.selectNextTool.bind(this), true);
     let prevKey = this.doc.getElementById("toolbox-previous-tool-key");
     prevKey.addEventListener("command", this.selectPreviousTool.bind(this), true);
 
     // Split console uses keypress instead of command so the event can be
     // cancelled with stopPropagation on the keypress, and not preventDefault.
@@ -910,16 +924,23 @@ Toolbox.prototype = {
         this.loadTool("webconsole").then(() => {
           this.focusConsoleInput();
         });
       }
     }
   },
 
   /**
+   * Tells the target tab to reload.
+   */
+  reloadTarget: function(force) {
+    this.target.activeTab.reload({ force: force });
+  },
+
+  /**
    * Loads the tool next to the currently selected tool.
    */
   selectNextTool: function() {
     let tools = this.doc.querySelectorAll(".devtools-tab");
     let selected = this.doc.querySelector(".devtools-tab[selected]");
     let nextIndex = [...tools].indexOf(selected) + 1;
     let next = tools[nextIndex] || tools[0];
     let tool = next.getAttribute("toolid");
--- a/browser/devtools/framework/toolbox.xul
+++ b/browser/devtools/framework/toolbox.xul
@@ -42,16 +42,32 @@
     <key id="toolbox-zoom-out-key"
          key="&toolboxZoomOut.key;"
          oncommand="void(0);"
          modifiers="accel"/>
     <key id="toolbox-zoom-reset-key"
          key="&toolboxZoomReset.key;"
          oncommand="void(0);"
          modifiers="accel"/>
+    <key id="toolbox-reload-key"
+         key="&toolboxReload.key;"
+         oncommand="void(0);"
+         modifiers="accel"/>
+    <key id="toolbox-force-reload-key"
+         key="&toolboxReload.key;"
+         oncommand="void(0);"
+         modifiers="accel shift"/>
+    <key id="toolbox-reload-key2"
+         keycode="VK_F5"
+         oncommand="void(0);"
+         modifiers=""/>
+    <key id="toolbox-force-reload-key2"
+         keycode="VK_F5"
+         oncommand="void(0);"
+         modifiers="accel"/>
   </keyset>
 
   <notificationbox id="toolbox-notificationbox" flex="1">
     <toolbar class="devtools-tabbar">
       <hbox id="toolbox-picker-container" />
       <hbox id="toolbox-tabs" flex="1" />
       <hbox id="toolbox-buttons" pack="end"/>
       <vbox id="toolbox-controls-separator"/>
--- a/browser/devtools/shared/DeveloperToolbar.jsm
+++ b/browser/devtools/shared/DeveloperToolbar.jsm
@@ -260,16 +260,19 @@ const NOTIFICATIONS = {
 };
 
 /**
  * Attach notification constants to the object prototype so tests etc can
  * use them without needing to import anything
  */
 DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
 
+/**
+ * target is dynamic because the selectedTab changes
+ */
 Object.defineProperty(DeveloperToolbar.prototype, "target", {
   get: function() {
     return TargetFactory.forTab(this._chromeWindow.getBrowser().selectedTab);
   },
   enumerable: true
 });
 
 /**
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -485,16 +485,20 @@ StyleEditorUI.prototype = {
           }
 
           editor.onShow();
 
           this.emit("editor-selected", editor);
 
           // Is there any CSS coverage markup to include?
           csscoverage.getUsage(this._target).then(usage => {
+            if (usage == null) {
+              return;
+            }
+
             let href = editor.styleSheet.href || editor.styleSheet.nodeHref;
             usage.createEditorReport(href).then(data => {
               editor.removeAllUnusedRegions();
 
               if (data.reports.length > 0) {
                 // So there is some coverage markup, but can we apply it?
                 let text = editor.sourceEditor.getText() + "\r";
                 // If the CSS text contains a '}' with some non-whitespace
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -221,17 +221,17 @@ StyleSheetEditor.prototype = {
   },
 
   /**
    * Start fetching the full text source for this editor's sheet.
    */
   fetchSource: function(callback) {
     return this.styleSheet.getText().then((longStr) => {
       longStr.string().then((source) => {
-        this._state.text = prettifyCSS(source);
+        this._state.text = CssLogic.prettifyCSS(source);
         this.sourceLoaded = true;
 
         if (callback) {
           callback(source);
         }
         return source;
       });
     }, e => {
@@ -635,83 +635,16 @@ StyleSheetEditor.prototype = {
       this.sourceEditor.destroy();
     }
     this.cssSheet.off("property-change", this._onPropertyChange);
     this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
     this.styleSheet.off("error", this._onError);
   }
 }
 
-
-const TAB_CHARS = "\t";
-
-const CURRENT_OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-const LINE_SEPARATOR = CURRENT_OS === "WINNT" ? "\r\n" : "\n";
-
-/**
- * Prettify minified CSS text.
- * This prettifies CSS code where there is no indentation in usual places while
- * keeping original indentation as-is elsewhere.
- *
- * @param string text
- *        The CSS source to prettify.
- * @return string
- *         Prettified CSS source
- */
-function prettifyCSS(text)
-{
-  // remove initial and terminating HTML comments and surrounding whitespace
-  text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, "");
-
-  let parts = [];    // indented parts
-  let partStart = 0; // start offset of currently parsed part
-  let indent = "";
-  let indentLevel = 0;
-
-  for (let i = 0; i < text.length; i++) {
-    let c = text[i];
-    let shouldIndent = false;
-
-    switch (c) {
-      case "}":
-        if (i - partStart > 1) {
-          // there's more than just } on the line, add line
-          parts.push(indent + text.substring(partStart, i));
-          partStart = i;
-        }
-        indent = TAB_CHARS.repeat(--indentLevel);
-        /* fallthrough */
-      case ";":
-      case "{":
-        shouldIndent = true;
-        break;
-    }
-
-    if (shouldIndent) {
-      let la = text[i+1]; // one-character lookahead
-      if (!/\n/.test(la) || /^\s+$/.test(text.substring(i+1, text.length))) {
-        // following character should be a new line, but isn't,
-        // or it's whitespace at the end of the file
-        parts.push(indent + text.substring(partStart, i + 1));
-        if (c == "}") {
-          parts.push(""); // for extra line separator
-        }
-        partStart = i + 1;
-      } else {
-        return text; // assume it is not minified, early exit
-      }
-    }
-
-    if (c == "{") {
-      indent = TAB_CHARS.repeat(++indentLevel);
-    }
-  }
-  return parts.join(LINE_SEPARATOR);
-}
-
 /**
  * Find a path on disk for a file given it's hosted uri, the uri of the
  * original resource that generated it (e.g. Sass file), and the location of the
  * local file for that source.
  *
  * @param {nsIURI} uri
  *        The uri of the resource
  * @param {nsIURI} origUri
--- a/browser/locales/en-US/chrome/browser/devtools/gcli.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gcli.properties
@@ -75,29 +75,16 @@ fileErrIsNotFile='%1$S' is not a file
 # filename
 fileErrIsNotDirectory='%1$S' is not a directory
 
 # LOCALIZATION NOTE: Error message given when a file argument does not match
 # the specified regular expression %1$S is a filename %2$S is a regular
 # expression
 fileErrDoesntMatch='%1$S' does not match '%2$S'
 
-# LOCALIZATION NOTE: When a command has a parameter that has a number of
-# pre-defined options the user interface presents these in a drop-down menu,
-# where the first 'option' is an indicator that a selection should be made.
-# This string describes that first option.
-fieldSelectionSelect=Select a %S…
-
-# LOCALIZATION NOTE (fieldArrayAdd, fieldArrayDel): When a command has a
-# parameter that can be repeated multiple times (e.g. like the 'cat a.txt
-# b.txt' command) the user interface presents buttons to add and remove
-# arguments. This string is used to add arguments.
-fieldArrayAdd=Add
-fieldArrayDel=Delete
-
 # LOCALIZATION NOTE: When the menu has displayed all the matches that it
 # should (i.e. about 10 items) then we display this to alert the user that
 # more matches are available.
 fieldMenuMore=More matches, keep typing
 
 # LOCALIZATION NOTE: The command line provides completion for JavaScript
 # commands, however there are times when the scope of what we're completing
 # against can't be used. This error message is displayed when this happens.
@@ -176,25 +163,25 @@ helpListNone=No commands starting with '
 # LOCALIZATION NOTE (helpManRequired, helpManOptional, helpManDefault): When
 # the 'help x' command wants to show the manual for the 'x' command, it needs
 # to be able to describe the parameters as either required or optional, or if
 # they have a default value.
 helpManRequired=required
 helpManOptional=optional
 helpManDefault=optional, default=%S
 
+# LOCALIZATION NOTE (helpIntro): This forms part of the output from the 'help'
+# command. 'GCLI' is a project name and should be left untranslated.
+helpIntro=GCLI is an experiment to create a highly usable command line for web developers.
+
 # LOCALIZATION NOTE: Text shown as part of the output of the 'help' command
 # when the command in question has sub-commands, before a list of the matching
 # sub-commands.
 subCommands=Sub-Commands
 
-# LOCALIZATION NOTE: Text shown as part of the output of the 'help' command
-# when the command in question should have sub-commands but in fact has none.
-subCommandsNone=None
-
 # LOCALIZATION NOTE (contextDesc, contextManual, contextPrefixDesc): These
 # strings are used to describe the 'context' command and its 'prefix'
 # parameter. See localization comment for 'connect' for an explanation about
 # 'prefix'.
 contextDesc=Concentrate on a group of commands
 contextManual=Setup a default prefix to future commands. For example 'context git' would allow you to type 'commit' rather than 'git commit'.
 contextPrefixDesc=The command prefix
 
@@ -221,35 +208,29 @@ connectMethodDesc=The method of connecti
 connectUrlDesc=The URL to connect to
 connectDupReply=Connection called %S already exists.
 
 # LOCALIZATION NOTE: The output of the 'connect' command, telling the user
 # what it has done. Parameters: %S is the prefix command. See localization
 # comment for 'connect' for an explanation about 'prefix'.
 connectReply=Added %S commands.
 
-# LOCALIZATION NOTE (disconnectDesc2, disconnectManual2, disconnectPrefixDesc,
-# disconnectForceDesc): These strings describe the 'disconnect' command and
+# LOCALIZATION NOTE (disconnectDesc2, disconnectManual2,
+# disconnectPrefixDesc): These strings describe the 'disconnect' command and
 # all its available parameters. See localization comment for 'connect' for an
 # explanation about 'prefix'.
 disconnectDesc2=Disconnect from server
 disconnectManual2=Disconnect from a server currently connected for remote commands execution
 disconnectPrefixDesc=Parent prefix for imported commands
-disconnectForceDesc=Ignore outstanding requests
 
 # LOCALIZATION NOTE: This is the output of the 'disconnect' command,
 # explaining the user what has been done. Parameters: %S is the number of
 # commands removed.
 disconnectReply=Removed %S commands.
 
-# LOCALIZATION NOTE: This error message is displayed when the user attempts to
-# disconnect before all requests have completed. Parameters: %S is a list of
-# incomplete requests.
-disconnectOutstanding=Outstanding requests (%S)
-
 # LOCALIZATION NOTE (globalDesc, globalWindowDesc, globalOutput): These
 # strings describe the 'global' command and its parameters
 globalDesc=Change the JS global
 globalWindowDesc=The new window/global
 globalOutput=JS global is now %S
 
 # LOCALIZATION NOTE: These strings describe the 'clear' command
 clearDesc=Clear the output area
@@ -284,23 +265,16 @@ prefShowSettingValue=%1$S: %2$S
 # describe the 'pref set' command and all its parameters.
 prefSetDesc=Alter a setting
 prefSetManual=Alter preferences defined by the environment
 prefSetSettingDesc=Setting to alter
 prefSetSettingManual=The name of the setting to alter.
 prefSetValueDesc=New value for setting
 prefSetValueManual=The new value for the specified setting
 
-# LOCALIZATION NOTE (prefSetCheckHeading, prefSetCheckBody, prefSetCheckGo):
-# These strings are displayed to the user the first time they try to alter a
-# setting.
-prefSetCheckHeading=This might void your warranty!
-prefSetCheckBody=Changing these advanced settings can be harmful to the stability, security, and performance of this application. You should only continue if you are sure of what you are doing.
-prefSetCheckGo=I'll be careful, I promise!
-
 # LOCALIZATION NOTE (prefResetDesc, prefResetManual, prefResetSettingDesc,
 # prefResetSettingManual): These strings describe the 'pref reset' command and
 # all its parameters.
 prefResetDesc=Reset a setting
 prefResetManual=Reset the value of a setting to the system defaults
 prefResetSettingDesc=Setting to reset
 prefResetSettingManual=The name of the setting to reset to the system default value
 
@@ -333,13 +307,8 @@ introTextGo=Got it!
 # LOCALIZATION NOTE: This is a short description of the 'hideIntro' setting.
 hideIntroDesc=Show the initial welcome message
 
 # LOCALIZATION NOTE: This is a description of the 'eagerHelper' setting. It's
 # displayed when the user asks for help on the settings. eagerHelper allows
 # users to select between showing no tooltips, permanent tooltips, and only
 # important tooltips.
 eagerHelperDesc=How eager are the tooltips
-
-# LOCALIZATION NOTE: This text is displayed at the top of the output for the
-# help command, just before the list of commands. This text is wrapped inside
-# a link to a localized MDN article.
-introBody=For more information see MDN.
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -574,25 +574,28 @@ resizeModeManual2=Responsive websites re
 # name, which is why it should be as short as possible.
 cmdDesc=Manipulate the commands
 
 # LOCALIZATION NOTE (cmdRefreshDesc) A very short description of the 'cmd refresh'
 # command. This string is designed to be shown in a menu alongside the command
 # name, which is why it should be as short as possible.
 cmdRefreshDesc=Re-read mozcmd directory
 
-# LOCALIZATION NOTE (cmdStatus2) When the we load new commands from mozcmd
+# LOCALIZATION NOTE (cmdStatus3) When the we load new commands from mozcmd
 # directory, we report where we loaded from using %1$S.
-cmdStatus2=Read commands from '%1$S'
+cmdStatus3=Loaded commands from '%1$S'
 
-# LOCALIZATION NOTE (cmdSetdirDesc) 
+# LOCALIZATION NOTE (cmdSetdirDesc)  A very short description of the 'cmd setdir'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
 cmdSetdirDesc=Setup a mozcmd directory
 
-# LOCALIZATION NOTE (cmdSetdirManual) 
-cmdSetdirManual=A 'mozcmd' directory is an easy way to create new custom commands for the Firefox command line. For more information see the <a href="https://developer.mozilla.org/en-US/docs/Tools/GCLI/Customization">MDN documentation</a>.
+# LOCALIZATION NOTE (cmdSetdirManual2) A fuller description of the 'cmd setdir'
+# command, displayed when the user asks for help on what it does.
+cmdSetdirManual2=A 'mozcmd' directory is an easy way to create new custom commands. For more information see the <a href="https://developer.mozilla.org/docs/Tools/GCLI/Customization">MDN documentation</a>.
 
 # LOCALIZATION NOTE (cmdSetdirDirectoryDesc) The description of the directory
 # parameter to the 'cmd setdir' command.
 cmdSetdirDirectoryDesc=Directory containing .mozcmd files
 
 # LOCALIZATION NOTE (addonDesc) A very short description of the 'addon'
 # command. This string is designed to be shown in a menu alongside the command
 # name, which is why it should be as short as possible.
@@ -1383,9 +1386,8 @@ listenNoInitOutput=DebuggerServer not in
 # LOCALIZATION NOTE (mediaDesc, mediaEmulateDesc, mediaEmulateManual,
 # mediaEmulateType, mediaResetDesc, mediaResetManual) These strings describe
 # the 'media' commands and all available parameters.
 mediaDesc=CSS media type emulation
 mediaEmulateDesc=Emulate a specified CSS media type
 mediaEmulateManual=View the document as if rendered on a device supporting the given media type, with the relevant CSS rules applied.
 mediaEmulateType=The media type to emulate
 mediaResetDesc=Stop emulating a CSS media type
-mediaResetManual=Stop emulating a CSS media type
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -12,16 +12,18 @@
 <!ENTITY toolboxNextTool.key           "]">
 <!ENTITY toolboxPreviousTool.key       "[">
 
 <!ENTITY toolboxZoomIn.key             "+">
 <!ENTITY toolboxZoomIn.key2            "="> <!-- + is above this key on many keyboards -->
 <!ENTITY toolboxZoomOut.key            "-">
 <!ENTITY toolboxZoomReset.key          "0">
 
+<!ENTITY toolboxReload.key             "r">
+
 <!-- LOCALIZATION NOTE (options.context.advancedSettings): This is the label for
   -  the heading of the advanced settings group in the options panel. -->
 <!ENTITY options.context.advancedSettings "Advanced settings">
 
 <!-- LOCALIZATION NOTE (options.context.inspector): This is the label for
   -  the heading of the Inspector group in the options panel. -->
 <!ENTITY options.context.inspector "Inspector">
 
--- a/build/autoconf/toolchain.m4
+++ b/build/autoconf/toolchain.m4
@@ -5,25 +5,50 @@ dnl file, You can obtain one at http://m
 AC_DEFUN([MOZ_TOOL_VARIABLES],
 [
 GNU_AS=
 GNU_LD=
 GNU_CC=
 GNU_CXX=
 CC_VERSION='N/A'
 CXX_VERSION='N/A'
-if test "$GCC" = "yes"; then
-    GNU_CC=1
-    CC_VERSION=`$CC -v 2>&1 | grep 'gcc version'`
+cat <<EOF > conftest.c
+#if defined(_MSC_VER)
+#if defined(__clang__)
+COMPILER clang-cl _MSC_VER
+#else
+COMPILER msvc _MSC_VER
+#endif
+#elif defined(__clang__)
+COMPILER clang __clang_major__.__clang_minor__.__clang_patchlevel__
+#elif defined(__GNUC__)
+COMPILER gcc __GNUC__.__GNUC_MINOR__.__GNUC_PATCHLEVEL__
+#elif defined(__INTEL_COMPILER)
+COMPILER icc __INTEL_COMPILER
+#endif
+EOF
+read dummy compiler CC_VERSION <<EOF
+$($CC -E conftest.c 2>/dev/null | grep COMPILER)
+EOF
+read dummy cxxcompiler CXX_VERSION <<EOF
+$($CXX -E conftest.c 2>/dev/null | grep COMPILER)
+EOF
+if test "$compiler" != "$cxxcompiler"; then
+    AC_MSG_ERROR([Your C and C++ compilers are different.  You need to use the same compiler.])
 fi
-if test "$GXX" = "yes"; then
+CC_VERSION=`echo "$CC_VERSION" | sed 's/ //g'`
+CXX_VERSION=`echo "$CXX_VERSION" | sed 's/ //g'`
+echo "$CC_VERSION"
+echo "$CXX_VERSION"
+ecit 1
+if test "$compiler" = "gcc"; then
+    GNU_CC=1
     GNU_CXX=1
-    CXX_VERSION=`$CXX -v 2>&1 | grep 'gcc version'`
     changequote(<<,>>)
-    GCC_VERSION_FULL=`echo "$CXX_VERSION" | $PERL -pe 's/^.*gcc version ([^ ]*).*/<<$>>1/'`
+    GCC_VERSION_FULL="$CXX_VERSION"
     GCC_VERSION=`echo "$GCC_VERSION_FULL" | $PERL -pe '(split(/\./))[0]>=4&&s/(^\d*\.\d*).*/<<$>>1/;'`
 
     GCC_MAJOR_VERSION=`echo ${GCC_VERSION} | $AWK -F\. '{ print <<$>>1 }'`
     GCC_MINOR_VERSION=`echo ${GCC_VERSION} | $AWK -F\. '{ print <<$>>2 }'`
     changequote([,])
 fi
 
 if test "`echo | $AS -o conftest.out -v 2>&1 | grep -c GNU`" != "0"; then
@@ -36,38 +61,36 @@ fi
 if test "$GNU_CC"; then
     if `$CC -print-prog-name=ld` -v 2>&1 | grep -c GNU >/dev/null; then
         GCC_USE_GNU_LD=1
     fi
 fi
 
 INTEL_CC=
 INTEL_CXX=
-if test "$GCC" = yes; then
-   if test "`$CC -help 2>&1 | grep -c 'Intel(R) C++ Compiler'`" != "0"; then
-     INTEL_CC=1
-   fi
-fi
-
-if test "$GXX" = yes; then
-   if test "`$CXX -help 2>&1 | grep -c 'Intel(R) C++ Compiler'`" != "0"; then
-     INTEL_CXX=1
-   fi
+if test "$compiler" = "icc"; then
+   INTEL_CC=1
+   INTEL_CXX=1
 fi
 
 CLANG_CC=
 CLANG_CXX=
-if test "`$CC -v 2>&1 | egrep -c '(clang version|Apple.*clang)'`" != "0"; then
-   CLANG_CC=1
+CLANG_CL=
+if test "$compiler" = "clang"; then
+    GNU_CC=1
+    GNU_CXX=1
+    CLANG_CC=1
+    CLANG_CXX=1
+fi
+if test "$compiler" = "clang-cl"; then
+    CLANG_CL=1
 fi
 
-if test "`$CXX -v 2>&1 | egrep -c '(clang version|Apple.*clang)'`" != "0"; then
-   CLANG_CXX=1
-fi
 AC_SUBST(CLANG_CXX)
+AC_SUBST(CLANG_CL)
 ])
 
 AC_DEFUN([MOZ_CROSS_COMPILER],
 [
 echo "cross compiling from $host to $target"
 
 _SAVE_CC="$CC"
 _SAVE_CFLAGS="$CFLAGS"
--- a/configure.in
+++ b/configure.in
@@ -460,25 +460,21 @@ case "$target" in
             AC_MSG_ERROR([\$(CXX) test failed.  You must have MS VC++ in your path to build.]) )
         AC_LANG_RESTORE
 
         changequote(,)
         _MSVC_VER_FILTER='s|.*[^!-~]([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*|\1|p'
         changequote([,])
 
         # Determine compiler version
-        CC_VERSION=`${CC} -v 2>&1 | sed -nre "$_MSVC_VER_FILTER"`
-        _CC_MAJOR_VERSION=`echo ${CC_VERSION} | $AWK -F\. '{ print $1 }'`
-        _CC_MINOR_VERSION=`echo ${CC_VERSION} | $AWK -F\. '{ print $2 }'`
-        _CC_RELEASE=`echo ${CC_VERSION} | $AWK -F\. '{ print $3 }'`
-        _CC_BUILD=`echo ${CC_VERSION} | $AWK -F\. '{ print $4 }'`
-        _MSC_VER=${_CC_MAJOR_VERSION}${_CC_MINOR_VERSION}
-
-        CXX_VERSION=`${CXX} -v 2>&1 | sed -nre "$_MSVC_VER_FILTER"`
-        _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | $AWK -F\. '{ print $1 }'`
+        _CC_MAJOR_VERSION=`echo ${CC_VERSION} | cut -c 1-2`
+        _CC_MINOR_VERSION=`echo ${CC_VERSION} | cut -c 3-4`
+        _MSC_VER=${CC_VERSION}
+
+        _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | cut -c 1-2`
 
         if test "$_CC_MAJOR_VERSION" != "$_CXX_MAJOR_VERSION"; then
             AC_MSG_ERROR([The major versions of \$CC and \$CXX do not match.])
         fi
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
         AC_DEFINE(_CRT_NONSTDC_NO_WARNINGS)
 
@@ -491,17 +487,20 @@ case "$target" in
         elif test "$_CC_MAJOR_VERSION" = "18"; then
             _CC_SUITE=12
             MSVS_VERSION=2013
         else
             AC_MSG_ERROR([This version ($CC_VERSION) of the MSVC compiler is unsupported. See https://developer.mozilla.org/en/Windows_Build_Prerequisites.])
         fi
         AC_SUBST(MSVS_VERSION)
 
-        AC_DEFINE(HAVE_SEH_EXCEPTIONS)
+        # Disable SEH on clang-cl because it doesn't implement them yet.
+        if test -z "$CLANG_CL"; then
+            AC_DEFINE(HAVE_SEH_EXCEPTIONS)
+        fi
 
         if test -n "$WIN32_REDIST_DIR"; then
           if test ! -d "$WIN32_REDIST_DIR"; then
             AC_MSG_ERROR([Invalid Win32 Redist directory: ${WIN32_REDIST_DIR}])
           fi
           WIN32_REDIST_DIR=`cd "$WIN32_REDIST_DIR" && pwd`
         fi
 
@@ -612,18 +611,20 @@ case "$target" in
                 MOZ_MSVC_STL_WRAP__RAISE=1
                 AC_DEFINE(MOZ_MSVC_STL_WRAP__RAISE)
             else
                 AC_MSG_ERROR([Gecko exception wrapping doesn't understand your your MSVC/SDK.  Please file a bug describing this error and your build configuration.])
             fi
         fi
 
         if test "$WRAP_STL_INCLUDES" = "1"; then
-            STL_FLAGS='-D_HAS_EXCEPTIONS=0 -I$(DIST)/stl_wrappers'
+            STL_FLAGS='-I$(DIST)/stl_wrappers'
         fi
+        CFLAGS="$CFLAGS -D_HAS_EXCEPTIONS=0"
+        CXXFLAGS="$CXXFLAGS -D_HAS_EXCEPTIONS=0"
     elif test -z "$CLANG_CC"; then
         # Check w32api version
         _W32API_MAJOR_VERSION=`echo $W32API_VERSION | $AWK -F\. '{ print $1 }'`
         _W32API_MINOR_VERSION=`echo $W32API_VERSION | $AWK -F\. '{ print $2 }'`
         AC_MSG_CHECKING([for w32api version >= $W32API_VERSION])
         AC_TRY_COMPILE([#include <w32api.h>],
             #if (__W32API_MAJOR_VERSION < $_W32API_MAJOR_VERSION) || \
                 (__W32API_MAJOR_VERSION == $_W32API_MAJOR_VERSION && \
@@ -894,21 +895,16 @@ tools are selected during the Xcode/Deve
 
   if test "$result" = "no" ; then
     AC_MSG_ERROR([The selected compiler and Mac OS X SDK are incompatible.])
   fi
 fi
 
 fi # COMPILE_ENVIRONMENT
 
-AC_MSG_CHECKING([compiler version])
-# Just print it so it shows up in the logs.
-cc_version=$($CC --version)
-AC_MSG_RESULT([$cc_version])
-
 if test -n "$MAKE"; then
   if test `echo $MAKE | grep -c make.py` != 1; then
      NOT_PYMAKE=$MAKE
   fi
 fi
 
 case "$host_os" in
 mingw*)
@@ -2161,17 +2157,21 @@ ia64*-hpux*)
         CFLAGS="$CFLAGS -we4553"
         CXXFLAGS="$CXXFLAGS -we4553"
         LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib secur32.lib netapi32.lib"
         MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
         WARNINGS_AS_ERRORS='-WX'
         MOZ_OPTIMIZE_FLAGS='-O1 -Oi'
         MOZ_FIX_LINK_PATHS=
         MOZ_COMPONENT_NSPR_LIBS='$(NSPR_LIBS)'
-        LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
+        # Disable these flags on clang-cl since it doesn't ignore unknown arguments by default, and
+        # autoconf insists on passing $LDFLAGS to the compiler.
+        if test -z "$CLANG_CL"; then
+            LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
+        fi
         if test -z "$DEVELOPER_OPTIONS"; then
             LDFLAGS="$LDFLAGS -RELEASE"
         fi
         dnl For profile-guided optimization
         PROFILE_GEN_CFLAGS="-GL"
         PROFILE_GEN_LDFLAGS="-LTCG:PGINSTRUMENT"
         dnl XXX: PGO builds can fail with warnings treated as errors,
         dnl specifically "no profile data available" appears to be
@@ -2249,17 +2249,21 @@ ia64*-hpux*)
         fi
 
         if test -n "$GNU_CC"; then
             CFLAGS="$CFLAGS -mstackrealign -fno-keep-inline-dllexport"
             CXXFLAGS="$CXXFLAGS -mstackrealign -fno-keep-inline-dllexport"
             LDFLAGS="$LDFLAGS -Wl,--enable-stdcall-fixup -Wl,--large-address-aware"
         else
             DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X86"
-            LDFLAGS="$LDFLAGS -SAFESEH"
+            # Disable this flag on clang-cl since it doesn't ignore unknown arguments by default, and
+            # autoconf insists on passing $LDFLAGS to the compiler.
+            if test -z "$CLANG_CL"; then
+                LDFLAGS="$LDFLAGS -SAFESEH"
+            fi
         fi
 
         AC_DEFINE(_X86_)
         ;;
     x86_64-*)
         if test -n "$_WIN32_MSVC"; then
             DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X64"
         fi
--- a/content/media/gmp/gmp-api/gmp-video-codec.h
+++ b/content/media/gmp/gmp-api/gmp-video-codec.h
@@ -30,16 +30,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef GMP_VIDEO_CODEC_h_
 #define GMP_VIDEO_CODEC_h_
 
 #include <stdint.h>
+#include <stddef.h>
 
 enum { kGMPPayloadNameSize = 32};
 enum { kGMPMaxSimulcastStreams = 4};
 
 enum GMPVideoCodecComplexity
 {
   kGMPComplexityNormal = 0,
   kGMPComplexityHigh = 1,
@@ -70,25 +71,43 @@ struct GMPVideoCodecVP8
   uint32_t mNumberOfTemporalLayers;
   bool mDenoisingOn;
   bool mErrorConcealmentOn;
   bool mAutomaticResizeOn;
   bool mFrameDroppingOn;
   int32_t mKeyFrameInterval;
 };
 
+// H264 specific
+struct GMPVideoCodecH264
+{
+  uint8_t        mProfile;
+  uint8_t        mConstraints;
+  uint8_t        mLevel;
+  uint8_t        mPacketizationMode; // 0 or 1
+  bool           mFrameDroppingOn;
+  int32_t        mKeyFrameInterval;
+  // These are null/0 if not externally negotiated
+  const uint8_t* mSPSData;
+  size_t         mSPSLen;
+  const uint8_t* mPPSData;
+  size_t         mPPSLen;
+};
+
 enum GMPVideoCodecType
 {
   kGMPVideoCodecVP8,
+  kGMPVideoCodecH264,
   kGMPVideoCodecInvalid // Should always be last.
 };
 
 union GMPVideoCodecUnion
 {
   GMPVideoCodecVP8 mVP8;
+  GMPVideoCodecH264 mH264;
 };
 
 // Simulcast is when the same stream is encoded multiple times with different
 // settings such as resolution.
 struct GMPSimulcastStream
 {
   uint32_t mWidth;
   uint32_t mHeight;
@@ -123,20 +142,36 @@ struct GMPVideoCodec
 
   uint32_t mQPMax;
   uint32_t mNumberOfSimulcastStreams;
   GMPSimulcastStream mSimulcastStream[kGMPMaxSimulcastStreams];
 
   GMPVideoCodecMode mMode;
 };
 
+// Either single encoded unit, or multiple units separated by 8/16/24/32
+// bit lengths, all with the same timestamp.  Note there is no final 0-length
+// entry; one should check the overall end-of-buffer against where the next
+// length would be.
+enum GMPBufferType {
+  GMP_BufferSingle = 0,
+  GMP_BufferLength8,
+  GMP_BufferLength16,
+  GMP_BufferLength24,
+  GMP_BufferLength32,
+};
+
 struct GMPCodecSpecificInfoGeneric {
   uint8_t mSimulcastIdx;
 };
 
+struct GMPCodecSpecificInfoH264 {
+  uint8_t mSimulcastIdx;
+};
+
 // Note: if any pointers are added to this struct, it must be fitted
 // with a copy-constructor. See below.
 struct GMPCodecSpecificInfoVP8
 {
   bool mHasReceivedSLI;
   uint8_t mPictureIdSLI;
   bool mHasReceivedRPSI;
   uint64_t mPictureIdRPSI;
@@ -156,12 +191,13 @@ union GMPCodecSpecificInfoUnion
 };
 
 // Note: if any pointers are added to this struct or its sub-structs, it
 // must be fitted with a copy-constructor. This is because it is copied
 // in the copy-constructor of VCMEncodedFrame.
 struct GMPCodecSpecificInfo
 {
   GMPVideoCodecType mCodecType;
+  GMPBufferType mBufferType;
   GMPCodecSpecificInfoUnion mCodecSpecific;
 };
 
 #endif // GMP_VIDEO_CODEC_h_
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -373,17 +373,17 @@ skip-if = buildapp == 'b2g' || toolkit =
 [test_mediarecorder_record_session.html]
 [test_mediarecorder_record_startstopstart.html]
 [test_mediarecorder_record_stopms.html]
 [test_mediarecorder_record_timeslice.html]
 [test_mediarecorder_reload_crash.html]
 [test_mediarecorder_unsupported_src.html]
 [test_metadata.html]
 [test_mixed_principals.html]
-skip-if = (os == 'linux'  && debug) || (toolkit == 'android' && debug) || toolkit == 'gonk' # bug 567954 and intermittent leaks
+skip-if = true # bug 567954 and intermittent leaks
 [test_mozHasAudio.html]
 [test_networkState.html]
 [test_new_audio.html]
 [test_no_load_event.html]
 [test_paused.html]
 [test_paused_after_ended.html]
 [test_play_events.html]
 [test_play_events_2.html]
--- a/content/media/test/test_playback_rate_playpause.html
+++ b/content/media/test/test_playback_rate_playpause.html
@@ -14,17 +14,17 @@
 // ASSERTION: Clock should go forwards if the playback rate is > 0.:
 //            'mCurrentFrameTime <= clock_time || mPlaybackRate <= 0',
 //            file MediaDecoderStateMachine.cpp, line 2379
 if (navigator.platform.startsWith("Win")) {
   SimpleTest.expectAssertions(1, 6);
 } else if (navigator.platform.startsWith("Mac")) {
   SimpleTest.expectAssertions(3, 7);
 } else if (navigator.platform.startsWith("Linux arm")) {
-  SimpleTest.expectAssertions(4, 11);
+  SimpleTest.expectAssertions(3, 11);
 }
 
 let manager = new MediaTestManager;
 
 function ontimeupdate(e) {
   var t = e.target;
   if (t.currentTime != 0.0) {
     dump(t.token + " t.currentTime != 0.0.\n");
--- a/content/media/webrtc/LoadManager.cpp
+++ b/content/media/webrtc/LoadManager.cpp
@@ -53,16 +53,17 @@ LoadManager::LoadManager(int aLoadMeasur
   MOZ_ASSERT(mHighLoadThreshold > mLowLoadThreshold);
   mLoadMonitor = new LoadMonitor(mLoadMeasurementInterval);
   mLoadMonitor->Init(mLoadMonitor);
   mLoadMonitor->SetLoadChangeCallback(this);
 }
 
 LoadManager::~LoadManager()
 {
+  LOG(("LoadManager: shutting down LoadMonitor"));
   mLoadMonitor->Shutdown();
 }
 
 void
 LoadManager::LoadChanged(float aSystemLoad, float aProcesLoad)
 {
   // Update total load, and total amount of measured seconds.
   mLoadSum += aSystemLoad;
--- a/content/media/webrtc/LoadMonitor.cpp
+++ b/content/media/webrtc/LoadMonitor.cpp
@@ -144,21 +144,27 @@ public:
   }
 
 private:
   nsRefPtr<LoadMonitor> mLoadMonitor;
 };
 
 void LoadMonitor::Shutdown()
 {
-  MutexAutoLock lock(mLock);
   if (mLoadInfoThread) {
-    mShutdownPending = true;
-    mCondVar.Notify();
+    {
+      MutexAutoLock lock(mLock);
+      LOG(("LoadMonitor: shutting down"));
+      mShutdownPending = true;
+      mCondVar.Notify();
+    }
 
+    // Note: can't just call ->Shutdown() from here; that spins the event
+    // loop here, causing re-entrancy issues if we're invoked from cycle
+    // collection.  Argh.
     mLoadInfoThread = nullptr;
 
     nsRefPtr<LoadMonitorRemoveObserver> remObsRunner = new LoadMonitorRemoveObserver(this);
     if (!NS_IsMainThread()) {
       NS_DispatchToMainThread(remObsRunner);
     } else {
       remObsRunner->Run();
     }
@@ -511,26 +517,41 @@ nsresult LoadInfo::UpdateProcessLoad() {
 #endif
   return NS_OK;
 }
 
 class LoadInfoCollectRunner : public nsRunnable
 {
 public:
   LoadInfoCollectRunner(nsRefPtr<LoadMonitor> loadMonitor,
-                        RefPtr<LoadInfo> loadInfo)
-    : mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval),
+                        RefPtr<LoadInfo> loadInfo,
+                        nsIThread *loadInfoThread)
+    : mThread(loadInfoThread),
+      mLoadUpdateInterval(loadMonitor->mLoadUpdateInterval),
       mLoadNoiseCounter(0)
   {
     mLoadMonitor = loadMonitor;
     mLoadInfo = loadInfo;
   }
 
   NS_IMETHOD Run()
   {
+    if (NS_IsMainThread()) {
+      if (mThread) {
+        // Don't leak threads!
+        mThread->Shutdown(); // can't Shutdown from the thread itself, darn
+        // Don't null out mThread!
+        // See bug 999104.  We must hold a ref to the thread across Dispatch()
+        // since the internal mThread ref could be released while processing
+        // the Dispatch(), and Dispatch/PutEvent itself doesn't hold a ref; it
+        // assumes the caller does.
+      }
+      return NS_OK;
+    }
+
     MutexAutoLock lock(mLoadMonitor->mLock);
     while (!mLoadMonitor->mShutdownPending) {
       mLoadInfo->UpdateSystemLoad();
       mLoadInfo->UpdateProcessLoad();
       float sysLoad = mLoadInfo->GetSystemLoad();
       float procLoad = mLoadInfo->GetProcessLoad();
 
       if ((++mLoadNoiseCounter % (LOG_MANY_ENABLED() ? 1 : 10)) == 0) {
@@ -538,20 +559,23 @@ public:
         mLoadNoiseCounter = 0;
       }
       mLoadMonitor->SetSystemLoad(sysLoad);
       mLoadMonitor->SetProcessLoad(procLoad);
       mLoadMonitor->FireCallbacks();
 
       mLoadMonitor->mCondVar.Wait(PR_MillisecondsToInterval(mLoadUpdateInterval));
     }
+    // ok, we need to exit safely and can't shut ourselves down (DARN)
+    NS_DispatchToMainThread(this);
     return NS_OK;
   }
 
 private:
+  nsCOMPtr<nsIThread> mThread;
   RefPtr<LoadInfo> mLoadInfo;
   nsRefPtr<LoadMonitor> mLoadMonitor;
   int mLoadUpdateInterval;
   int mLoadNoiseCounter;
 };
 
 void
 LoadMonitor::SetProcessLoad(float load) {
@@ -600,17 +624,17 @@ LoadMonitor::Init(nsRefPtr<LoadMonitor> 
   }
 
   nsRefPtr<LoadMonitorAddObserver> addObsRunner = new LoadMonitorAddObserver(self);
   NS_DispatchToMainThread(addObsRunner);
 
   NS_NewNamedThread("Sys Load Info", getter_AddRefs(mLoadInfoThread));
 
   nsRefPtr<LoadInfoCollectRunner> runner =
-    new LoadInfoCollectRunner(self, load_info);
+    new LoadInfoCollectRunner(self, load_info, mLoadInfoThread);
   mLoadInfoThread->Dispatch(runner, NS_DISPATCH_NORMAL);
 
   return NS_OK;
 }
 
 void
 LoadMonitor::SetLoadChangeCallback(LoadNotificationCallback* aCallback)
 {
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -8185,19 +8185,26 @@ class CGUnionStruct(CGThing):
                                        const=True,
                                        bodyInHeader=True,
                                        body=body))
 
             unionValues.append(
                 fill("UnionMember<${structType} > m${name}", **vars))
             enumValues.append("e" + vars["name"])
 
-            toJSValCases.append(
-                CGCase("e" + vars["name"],
-                       self.getConversionToJS(vars, t)))
+            skipToJSVal = False
+            try:
+                toJSValCases.append(
+                    CGCase("e" + vars["name"],
+                           self.getConversionToJS(vars, t)))
+            except MethodNotNewObjectError:
+                # If we can't have a ToJSVal() because one of our members can
+                # only be returned from [NewObject] methods, then just skip
+                # generating ToJSVal.
+                skipToJSVal = True
             destructorCases.append(
                 CGCase("e" + vars["name"],
                        CGGeneric("Destroy%s();\n" % vars["name"])))
             assignmentCases.append(
                 CGCase("e" + vars["name"],
                        CGGeneric("SetAs%s() = aOther.GetAs%s();\n" %
                                  (vars["name"], vars["name"]))))
             if self.ownsMembers and typeNeedsRooting(t):
@@ -8221,28 +8228,29 @@ class CGUnionStruct(CGThing):
 
         dtor = CGSwitch("mType", destructorCases).define()
 
         methods.append(ClassMethod("Uninit", "void", [],
                                    visibility="private", body=dtor,
                                    bodyInHeader=not self.ownsMembers,
                                    inline=not self.ownsMembers))
 
-        methods.append(
-            ClassMethod(
-                "ToJSVal",
-                "bool",
-                [
-                    Argument("JSContext*", "cx"),
-                    Argument("JS::Handle<JSObject*>", "scopeObj"),
-                    Argument("JS::MutableHandle<JS::Value>", "rval")
-                ],
-                body=CGSwitch("mType", toJSValCases,
-                              default=CGGeneric("return false;\n")).define(),
-                const=True))
+        if not skipToJSVal:
+            methods.append(
+                ClassMethod(
+                    "ToJSVal",
+                    "bool",
+                    [
+                        Argument("JSContext*", "cx"),
+                        Argument("JS::Handle<JSObject*>", "scopeObj"),
+                        Argument("JS::MutableHandle<JS::Value>", "rval")
+                    ],
+                    body=CGSwitch("mType", toJSValCases,
+                                  default=CGGeneric("return false;\n")).define(),
+                    const=True))
 
         constructors = [ctor]
         selfName = CGUnionStruct.unionTypeName(self.type, self.ownsMembers)
         if self.ownsMembers:
             if traceCases:
                 traceBody = CGSwitch("mType", traceCases,
                                      default=CGGeneric("")).define()
             else:
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3195,22 +3195,21 @@ PluginInstanceChild::PaintRectToSurface(
     if (mHelperSurface) {
         // On X11 we can paint to non Xlib surface only with HelperSurface
         renderSurface = mHelperSurface;
     }
 #endif
 
     if (mIsTransparent && !CanPaintOnBackground()) {
         // Clear surface content for transparent rendering
-        ColorPattern color(ToColor(aColor));
-        RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(renderSurface);
-        dt->FillRect(gfx::Rect(plPaintRect.x, plPaintRect.y,
-                               plPaintRect.width, plPaintRect.height),
-                     color,
-                     DrawOptions(1.f, CompositionOp::OP_SOURCE));
+        nsRefPtr<gfxContext> ctx = new gfxContext(renderSurface);
+        ctx->SetDeviceColor(aColor);
+        ctx->SetOperator(gfxContext::OPERATOR_SOURCE);
+        ctx->Rectangle(GfxFromNsRect(plPaintRect));
+        ctx->Fill();
     }
 
     PaintRectToPlatformSurface(plPaintRect, renderSurface);
 
     if (renderSurface != aSurface) {
         // Copy helper surface content to target
         RefPtr<DrawTarget> dt = CreateDrawTargetForSurface(aSurface);
         RefPtr<SourceSurface> surface =
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -1,15 +1,16 @@
 /* -*- 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://dev.w3.org/2011/webrtc/editor/webrtc.html#rtcstatsreport-object
+ * http://www.w3.org/2011/04/webrtc/wiki/Stats
  */
 
 enum RTCStatsType {
   "inboundrtp",
   "outboundrtp",
   "session",
   "track",
   "transport",
@@ -26,44 +27,64 @@ dictionary RTCStats {
 
 dictionary RTCRTPStreamStats : RTCStats {
   DOMString ssrc;
   DOMString remoteId;
   boolean isRemote = false;
   DOMString mediaTrackId;
   DOMString transportId;
   DOMString codecId;
+
+  // Video encoder/decoder measurements (absent for rtcp)
+  double bitrateMean;
+  double bitrateStdDev;
+  double framerateMean;
+  double framerateStdDev;
 };
 
 dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats {
   unsigned long packetsReceived;
   unsigned long long bytesReceived;
   double jitter;
   unsigned long packetsLost;
   long mozAvSyncDelay;
   long mozJitterBufferDelay;
   long mozRtt;
+
+  // Video decoder measurement (absent in rtcp case)
+  unsigned long discardedPackets;
 };
 
 dictionary RTCOutboundRTPStreamStats : RTCRTPStreamStats {
   unsigned long packetsSent;
   unsigned long long bytesSent;
+  double targetBitrate;  // config encoder bitrate target of this SSRC in bits/s
+
+  // Video encoder measurement (absent in rtcp case)
+  unsigned long droppedFrames;
 };
 
 dictionary RTCMediaStreamTrackStats : RTCStats {
   DOMString trackIdentifier;      // track.id property
   boolean remoteSource;
   sequence<DOMString> ssrcIds;
-  unsigned long audioLevel;       // Only for audio, the rest are only for video
+  // Stuff that makes sense for video
   unsigned long frameWidth;
   unsigned long frameHeight;
-  double framesPerSecond;         // The nominal FPS value
+  double framesPerSecond;        // The nominal FPS value
   unsigned long framesSent;
-  unsigned long framesReceived;   // Only for remoteSource=true
+  unsigned long framesReceived;  // Only for remoteSource=true
   unsigned long framesDecoded;
+  unsigned long framesDropped;   // See VideoPlaybackQuality.droppedVideoFrames
+  unsigned long framesCorrupted; // as above.
+  // Stuff that makes sense for audio
+  double audioLevel;               // linear, 1.0 = 0 dBov (from RFC 6464).
+  // AEC stuff on audio tracks sourced from a microphone where AEC is applied
+  double echoReturnLoss;           // in decibels from G.168 (2012) section 3.14
+  double echoReturnLossEnhancement; // as above, section 3.15
 };
 
 dictionary RTCMediaStreamStats : RTCStats {
   DOMString streamIdentifier;     // stream.id property
   sequence<DOMString> trackIds;   // Note: stats object ids, not track.id
 };
 
 dictionary RTCTransportStats: RTCStats {
--- a/dom/xbl/nsXBLMaybeCompiled.h
+++ b/dom/xbl/nsXBLMaybeCompiled.h
@@ -11,16 +11,19 @@
 /*
  * A union containing either a pointer representing uncompiled source or a
  * JSObject* representing the compiled result.  The class is templated on the
  * source object type.
  *
  * The purpose of abstracting this as a separate class is to allow it to be
  * wrapped in a JS::Heap<T> to correctly handle post-barriering of the JSObject
  * pointer, when present.
+ *
+ * No implementation of rootKind() is provided, which prevents
+ * Root<nsXBLMaybeCompiled<UncompiledT>> from being used.
  */
 template <class UncompiledT>
 class nsXBLMaybeCompiled
 {
 public:
   nsXBLMaybeCompiled() : mUncompiled(BIT_UNCOMPILED) {}
 
   nsXBLMaybeCompiled(UncompiledT* uncompiled)
@@ -83,21 +86,16 @@ namespace js {
 
 template <class UncompiledT>
 struct GCMethods<nsXBLMaybeCompiled<UncompiledT> >
 {
   typedef struct GCMethods<JSObject *> Base;
 
   static nsXBLMaybeCompiled<UncompiledT> initial() { return nsXBLMaybeCompiled<UncompiledT>(); }
 
-  /*
-   * No implementation of kind() is provided to prevent
-   * Root<nsXBLMaybeCompiled<UncompiledT>> from being used.
-   */
-
   static bool poisoned(nsXBLMaybeCompiled<UncompiledT> function)
   {
     return function.IsCompiled() && Base::poisoned(function.GetJSFunction());
   }
 
   static bool needsPostBarrier(nsXBLMaybeCompiled<UncompiledT> function)
   {
     return function.IsCompiled() && Base::needsPostBarrier(function.GetJSFunction());
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -328,16 +328,34 @@ GLLibraryEGL::EnsureInitialized()
 
             mSymbols.fCreateImage = nullptr;
             mSymbols.fDestroyImage = nullptr;
         }
     } else {
         MarkExtensionUnsupported(KHR_image_pixmap);
     }
 
+    if (IsExtensionSupported(ANDROID_native_fence_sync)) {
+        GLLibraryLoader::SymLoadStruct nativeFenceSymbols[] = {
+            { (PRFuncPtr*) &mSymbols.fDupNativeFenceFDANDROID, { "eglDupNativeFenceFDANDROID", nullptr } },
+            { nullptr, { nullptr } }
+        };
+
+        bool success = GLLibraryLoader::LoadSymbols(mEGLLibrary,
+                                                    &nativeFenceSymbols[0],
+                                                    lookupFunction);
+        if (!success) {
+            NS_ERROR("EGL supports ANDROID_native_fence_sync without exposing its functions!");
+
+            MarkExtensionUnsupported(ANDROID_native_fence_sync);
+
+            mSymbols.fDupNativeFenceFDANDROID = nullptr;
+        }
+    }
+
     mInitialized = true;
     reporter.SetSuccessful();
     return true;
 }
 
 void
 GLLibraryEGL::InitExtensions()
 {
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -407,16 +407,24 @@ public:
     EGLBoolean fGetSyncAttrib(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLint *value)
     {
         BEFORE_GL_CALL;
         EGLBoolean b = mSymbols.fGetSyncAttrib(dpy, sync, attribute, value);
         AFTER_GL_CALL;
         return b;
     }
 
+    EGLint fDupNativeFenceFDANDROID(EGLDisplay dpy, EGLSync sync)
+    {
+        MOZ_ASSERT(mSymbols.fDupNativeFenceFDANDROID);
+        BEFORE_GL_CALL;
+        EGLint ret = mSymbols.fDupNativeFenceFDANDROID(dpy, sync);
+        AFTER_GL_CALL;
+        return ret;
+    }
 
     EGLDisplay Display() {
         return mEGLDisplay;
     }
 
     bool IsANGLE() const {
         return mIsANGLE;
     }
@@ -517,16 +525,18 @@ public:
         typedef EGLSync (GLAPIENTRY * pfnCreateSync)(EGLDisplay dpy, EGLenum type, const EGLint *attrib_list);
         pfnCreateSync fCreateSync;
         typedef EGLBoolean (GLAPIENTRY * pfnDestroySync)(EGLDisplay dpy, EGLSync sync);
         pfnDestroySync fDestroySync;
         typedef EGLint (GLAPIENTRY * pfnClientWaitSync)(EGLDisplay dpy, EGLSync sync, EGLint flags, EGLTime timeout);
         pfnClientWaitSync fClientWaitSync;
         typedef EGLBoolean (GLAPIENTRY * pfnGetSyncAttrib)(EGLDisplay dpy, EGLSync sync, EGLint attribute, EGLint *value);
         pfnGetSyncAttrib fGetSyncAttrib;
+        typedef EGLint (GLAPIENTRY * pfnDupNativeFenceFDANDROID)(EGLDisplay dpy, EGLSync sync);
+        pfnDupNativeFenceFDANDROID fDupNativeFenceFDANDROID;
     } mSymbols;
 
 #ifdef DEBUG
     static void BeforeGLCall(const char* glFunction);
     static void AfterGLCall(const char* glFunction);
 #endif
 
 #ifdef MOZ_B2G
--- a/gfx/gl/SharedSurfaceGralloc.cpp
+++ b/gfx/gl/SharedSurfaceGralloc.cpp
@@ -148,21 +148,34 @@ SharedSurface_Gralloc::Fence()
         mSync = 0;
     }
 
     // When Android native fences are available, try
     // them first since they're more likely to work.
     // Android native fences are also likely to perform better.
     if (mEGL->IsExtensionSupported(GLLibraryEGL::ANDROID_native_fence_sync)) {
         mGL->MakeCurrent();
-        mSync = mEGL->fCreateSync(mEGL->Display(),
-                                  LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID,
-                                  nullptr);
-        if (mSync) {
+        EGLSync sync = mEGL->fCreateSync(mEGL->Display(),
+                                         LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID,
+                                         nullptr);
+        if (sync) {
             mGL->fFlush();
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+            int fenceFd = mEGL->fDupNativeFenceFDANDROID(mEGL->Display(), sync);
+            if (fenceFd != -1) {
+                mEGL->fDestroySync(mEGL->Display(), sync);
+                android::sp<android::Fence> fence(new android::Fence(fenceFd));
+                FenceHandle handle = FenceHandle(fence);
+                mTextureClient->SetAcquireFenceHandle(handle);
+            } else {
+                mSync = sync;
+            }
+#else
+            mSync = sync;
+#endif
             return;
         }
     }
 
     if (mEGL->IsExtensionSupported(GLLibraryEGL::KHR_fence_sync)) {
         mGL->MakeCurrent();
         mSync = mEGL->fCreateSync(mEGL->Display(),
                                   LOCAL_EGL_SYNC_FENCE,
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -733,16 +733,17 @@ ContainerLayer::ContainerLayer(LayerMana
   : Layer(aManager, aImplData),
     mFirstChild(nullptr),
     mLastChild(nullptr),
     mScrollHandoffParentId(FrameMetrics::NULL_SCROLL_ID),
     mPreXScale(1.0f),
     mPreYScale(1.0f),
     mInheritedXScale(1.0f),
     mInheritedYScale(1.0f),
+    mBackgroundColor(0, 0, 0, 0),
     mUseIntermediateSurface(false),
     mSupportsComponentAlphaChildren(false),
     mMayHaveReadbackChild(false)
 {
   mContentFlags = 0; // Clear NO_TEXT, NO_TEXT_OVER_TRANSPARENT
 }
 
 ContainerLayer::~ContainerLayer() {}
@@ -891,17 +892,18 @@ ContainerLayer::RepositionChild(Layer* a
   return true;
 }
 
 void
 ContainerLayer::FillSpecificAttributes(SpecificLayerAttributes& aAttrs)
 {
   aAttrs = ContainerLayerAttributes(GetFrameMetrics(), mScrollHandoffParentId,
                                     mPreXScale, mPreYScale,
-                                    mInheritedXScale, mInheritedYScale);
+                                    mInheritedXScale, mInheritedYScale,
+                                    mBackgroundColor);
 }
 
 bool
 ContainerLayer::HasMultipleChildren()
 {
   uint32_t count = 0;
   for (Layer* child = GetFirstChild(); child; child = child->GetNextSibling()) {
     const nsIntRect *clipRect = child->GetEffectiveClipRect();
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1678,16 +1678,27 @@ public:
     }
 
     MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) InheritedScale", this));
     mInheritedXScale = aXScale;
     mInheritedYScale = aYScale;
     Mutated();
   }
 
+  void SetBackgroundColor(const gfxRGBA& aColor)
+  {
+    if (mBackgroundColor == aColor) {
+      return;
+    }
+
+    MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) BackgroundColor", this));
+    mBackgroundColor = aColor;
+    Mutated();
+  }
+
   virtual void FillSpecificAttributes(SpecificLayerAttributes& aAttrs);
 
   void SortChildrenBy3DZOrder(nsTArray<Layer*>& aArray);
 
   // These getters can be used anytime.
 
   virtual ContainerLayer* AsContainerLayer() { return this; }
   virtual const ContainerLayer* AsContainerLayer() const { return this; }
@@ -1695,16 +1706,18 @@ public:
   virtual Layer* GetFirstChild() const { return mFirstChild; }
   virtual Layer* GetLastChild() const { return mLastChild; }
   const FrameMetrics& GetFrameMetrics() const { return mFrameMetrics; }
   FrameMetrics::ViewID GetScrollHandoffParentId() const { return mScrollHandoffParentId; }
   float GetPreXScale() const { return mPreXScale; }
   float GetPreYScale() const { return mPreYScale; }
   float GetInheritedXScale() const { return mInheritedXScale; }
   float GetInheritedYScale() const { return mInheritedYScale; }
+  
+  gfxRGBA GetBackgroundColor() const { return mBackgroundColor; }
 
   MOZ_LAYER_DECL_NAME("ContainerLayer", TYPE_CONTAINER)
 
   /**
    * ContainerLayer backends need to override ComputeEffectiveTransforms
    * since the decision about whether to use a temporary surface for the
    * container is backend-specific. ComputeEffectiveTransforms must also set
    * mUseIntermediateSurface.
@@ -1781,16 +1794,20 @@ protected:
   nsRefPtr<AsyncPanZoomController> mAPZC;
   FrameMetrics::ViewID mScrollHandoffParentId;
   float mPreXScale;
   float mPreYScale;
   // The resolution scale inherited from the parent layer. This will already
   // be part of mTransform.
   float mInheritedXScale;
   float mInheritedYScale;
+  // This is currently set and used only for scrollable container layers.
+  // When multi-layer-apz (bug 967844) is implemented, this is likely to move
+  // elsewhere (e.g. to Layer).
+  gfxRGBA mBackgroundColor;
   bool mUseIntermediateSurface;
   bool mSupportsComponentAlphaChildren;
   bool mMayHaveReadbackChild;
 };
 
 /**
  * A Layer which just renders a solid color in its visible region. It actually
  * can fill any area that contains the visible region, so if you need to
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -500,16 +500,26 @@ public:
                       const TimeDuration& aDelta) MOZ_OVERRIDE
   {
     // Can't inline these variables due to short-circuit evaluation.
     bool continueX = mApzc.mX.SampleSnapBack(aDelta);
     bool continueY = mApzc.mY.SampleSnapBack(aDelta);
     return continueX || continueY;
   }
 
+  virtual void Cancel() MOZ_OVERRIDE
+  {
+    // If the snap-back animation is cancelled for some reason, we need to
+    // clear the overscroll, otherwise the user would be stuck in the
+    // overscrolled state (since touch blocks beginning in an overscrolled
+    // state are ignored).
+    mApzc.mX.ClearOverscroll();
+    mApzc.mY.ClearOverscroll();
+  }
+
 private:
   AsyncPanZoomController& mApzc;
 };
 
 void
 AsyncPanZoomController::SetFrameTime(const TimeStamp& aTime) {
   sFrameTime = aTime;
 }
@@ -607,16 +617,18 @@ AsyncPanZoomController::GetGestureEventL
   MonitorAutoLock lock(mRefPtrMonitor);
   nsRefPtr<GestureEventListener> listener = mGestureEventListener;
   return listener.forget();
 }
 
 void
 AsyncPanZoomController::Destroy()
 {
+  CancelAnimation();
+
   { // scope the lock
     MonitorAutoLock lock(mRefPtrMonitor);
     mGeckoContentController = nullptr;
     mGestureEventListener = nullptr;
   }
   mPrevSibling = nullptr;
   mLastChild = nullptr;
   mParent = nullptr;
@@ -1679,17 +1691,22 @@ void AsyncPanZoomController::StartAnimat
   mAnimation = aAnimation;
   mLastSampleTime = GetFrameTime();
   ScheduleComposite();
 }
 
 void AsyncPanZoomController::CancelAnimation() {
   ReentrantMonitorAutoEnter lock(mMonitor);
   SetState(NOTHING);
-  mAnimation = nullptr;
+  if (mAnimation) {
+    mAnimation->Cancel();
+    mAnimation = nullptr;
+    // mAnimation->Cancel() may have done something that requires a repaint.
+    RequestContentRepaint();
+  }
 }
 
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ShareFrameMetricsAcrossProcesses() {
   mSharingFrameMetricsAcrossProcesses = true;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -1027,16 +1027,19 @@ public:
   AsyncPanZoomAnimation(const TimeDuration& aRepaintInterval =
                         TimeDuration::Forever())
     : mRepaintInterval(aRepaintInterval)
   { }
 
   virtual bool Sample(FrameMetrics& aFrameMetrics,
                       const TimeDuration& aDelta) = 0;
 
+  // Called if the animation is cancelled before it ends.
+  virtual void Cancel() {}
+
   /**
    * Get the deferred tasks in |mDeferredTasks|. See |mDeferredTasks|
    * for more information.
    * Clears |mDeferredTasks|.
    */
   Vector<Task*> TakeDeferredTasks() {
     Vector<Task*> result;
     mDeferredTasks.swap(result);
--- a/gfx/layers/apz/src/Axis.cpp
+++ b/gfx/layers/apz/src/Axis.cpp
@@ -172,16 +172,20 @@ bool Axis::SampleSnapBack(const TimeDura
   // No overscroll on this axis, do not continue animation.
   return false;
 }
 
 bool Axis::IsOverscrolled() const {
   return mOverscroll != 0;
 }
 
+void Axis::ClearOverscroll() {
+  mOverscroll = 0;
+}
+
 float Axis::PanDistance() {
   return fabsf(mPos - mStartPos);
 }
 
 float Axis::PanDistance(float aPos) {
   return fabsf(aPos - mStartPos);
 }
 
--- a/gfx/layers/apz/src/Axis.h
+++ b/gfx/layers/apz/src/Axis.h
@@ -105,16 +105,21 @@ public:
   bool SampleSnapBack(const TimeDuration& aDelta);
 
   /**
    * Return whether this axis is overscrolled in either direction.
    */
   bool IsOverscrolled() const;
 
   /**
+   * Clear any overscroll amount on this axis.
+   */
+  void ClearOverscroll();
+
+  /**
    * Gets the distance between the starting position of the touch supplied in
    * startTouch() and the current touch from the last
    * updateWithTouchAtDevicePoint().
    */
   float PanDistance();
 
   /**
    * Gets the distance between the starting position of the touch supplied in
--- a/gfx/layers/client/CanvasClient.cpp
+++ b/gfx/layers/client/CanvasClient.cpp
@@ -166,17 +166,17 @@ CanvasClientSurfaceStream::Update(gfx::I
 
     // If IPDLActor is null means this TextureClient didn't AddTextureClient yet
     if (!grallocTextureClient->GetIPDLActor()) {
       grallocTextureClient->SetTextureFlags(mTextureInfo.mTextureFlags);
       AddTextureClient(grallocTextureClient);
     }
 
     if (grallocTextureClient->GetIPDLActor()) {
-      GetForwarder()->UseTexture(this, grallocTextureClient);
+      UseTexture(grallocTextureClient);
     }
 
     if (mBuffer && CompositorChild::ChildProcessHasCompositor()) {
       // remove old buffer from CompositableHost
       RefPtr<AsyncTransactionTracker> tracker = new RemoveTextureFromCompositableTracker();
       // Hold TextureClient until transaction complete.
       tracker->SetTextureClient(mBuffer);
       mBuffer->SetRemoveFromCompositableTracker(tracker);
--- a/gfx/layers/client/CompositableClient.cpp
+++ b/gfx/layers/client/CompositableClient.cpp
@@ -198,15 +198,33 @@ CompositableClient::AddTextureClient(Tex
 }
 
 void
 CompositableClient::OnTransaction()
 {
 }
 
 void
+CompositableClient::UseTexture(TextureClient* aTexture)
+{
+  MOZ_ASSERT(aTexture);
+  if (!aTexture) {
+    return;
+  }
+
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+  FenceHandle handle = aTexture->GetAcquireFenceHandle();
+  if (handle.IsValid()) {
+    RefPtr<FenceDeliveryTracker> tracker = new FenceDeliveryTracker(handle);
+    mForwarder->SendFenceHandle(tracker, aTexture->GetIPDLActor(), handle);
+  }
+#endif
+  mForwarder->UseTexture(this, aTexture);
+}
+
+void
 CompositableClient::RemoveTexture(TextureClient* aTexture)
 {
   mForwarder->RemoveTextureFromCompositable(this, aTexture);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/CompositableClient.h
+++ b/gfx/layers/client/CompositableClient.h
@@ -184,16 +184,18 @@ public:
   virtual void OnDetach() {}
 
   /**
    * Clear any resources that are not immediately necessary. This may be called
    * in low-memory conditions.
    */
   virtual void ClearCachedResources() {}
 
+  virtual void UseTexture(TextureClient* aTexture);
+
   /**
    * Should be called when deataching a TextureClient from a Compositable, because
    * some platforms need to do some extra book keeping when this happens (for
    * example to properly keep track of fences on Gonk).
    *
    * See AutoRemoveTexture to automatically invoke this at the end of a scope.
    */
   virtual void RemoveTexture(TextureClient* aTexture);
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -286,23 +286,36 @@ public:
    * Triggers the destruction of the shared data and the corresponding TextureHost.
    *
    * If the texture flags contain TextureFlags::DEALLOCATE_CLIENT, the destruction
    * will be synchronously coordinated with the compositor side, otherwise it
    * will be done asynchronously.
    */
   void ForceRemove();
 
-  virtual void SetReleaseFenceHandle(FenceHandle aReleaseFenceHandle) {}
+  virtual void SetReleaseFenceHandle(FenceHandle aReleaseFenceHandle)
+  {
+    mReleaseFenceHandle = aReleaseFenceHandle;
+  }
 
   const FenceHandle& GetReleaseFenceHandle() const
   {
     return mReleaseFenceHandle;
   }
 
+  virtual void SetAcquireFenceHandle(FenceHandle aAcquireFenceHandle)
+  {
+    mAcquireFenceHandle = aAcquireFenceHandle;
+  }
+
+  const FenceHandle& GetAcquireFenceHandle() const
+  {
+    return mAcquireFenceHandle;
+  }
+
   /**
    * Set AsyncTransactionTracker of RemoveTextureFromCompositableAsync() transaction.
    */
   virtual void SetRemoveFromCompositableTracker(AsyncTransactionTracker* aTracker) {}
 
   /**
    * This function waits until the buffer is no longer being used.
    */
@@ -342,16 +355,17 @@ protected:
     mFlags |= aFlags;
   }
 
   RefPtr<TextureChild> mActor;
   TextureFlags mFlags;
   bool mShared;
   bool mValid;
   FenceHandle mReleaseFenceHandle;
+  FenceHandle mAcquireFenceHandle;
 
   friend class TextureChild;
   friend void TestTextureClientSurface(TextureClient*, gfxImageSurface*);
   friend void TestTextureClientYCbCr(TextureClient*, PlanarYCbCrData&);
 };
 
 /**
  * TextureClient that wraps a random access buffer such as a Shmem or raw memory.
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/gfx/BaseRect.h"       // for BaseRect
 #include "mozilla/gfx/Matrix.h"         // for Matrix4x4
 #include "mozilla/gfx/Point.h"          // for Point, IntPoint
 #include "mozilla/gfx/Rect.h"           // for IntRect, Rect
 #include "mozilla/layers/Compositor.h"  // for Compositor, etc
 #include "mozilla/layers/CompositorTypes.h"  // for DiagnosticFlags::CONTAINER
 #include "mozilla/layers/Effects.h"     // for Effect, EffectChain, etc
 #include "mozilla/layers/TextureHost.h"  // for CompositingRenderTarget
+#include "mozilla/layers/AsyncPanZoomController.h"  // for AsyncPanZoomController
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_ASSERTION
 #include "nsISupportsImpl.h"            // for MOZ_COUNT_CTOR, etc
 #include "nsISupportsUtils.h"           // for NS_ADDREF, NS_RELEASE
 #include "nsPoint.h"                    // for nsIntPoint
 #include "nsRect.h"                     // for nsIntRect
 #include "nsRegion.h"                   // for nsIntRegion
@@ -311,16 +312,38 @@ ContainerRender(ContainerT* aContainer,
     compositor->SetRenderTarget(surface);
   } else {
     surface = previousTarget;
   }
 
   nsAutoTArray<Layer*, 12> children;
   aContainer->SortChildrenBy3DZOrder(children);
 
+  // If this is a scrollable container layer, and it's overscrolled, the layer's
+  // contents are transformed in a way that would leave blank regions in the
+  // composited area. If the layer has a background color, fill these areas
+  // with the background color by drawing a rectangle of the background color
+  // over the entire composited area before drawing the container contents.
+  if (AsyncPanZoomController* apzc = aContainer->GetAsyncPanZoomController()) {
+    if (apzc->IsOverscrolled()) {
+      gfxRGBA color = aContainer->GetBackgroundColor();
+      // If the background is completely transparent, there's no point in
+      // drawing anything for it. Hopefully the layers behind, if any, will
+      // provide suitable content for the overscroll effect.
+      if (color.a != 0.0) {
+        EffectChain effectChain(aContainer);
+        effectChain.mPrimaryEffect = new EffectSolidColor(ToColor(color));
+        gfx::Rect clipRect(aClipRect.x, aClipRect.y, aClipRect.width, aClipRect.height);
+        Compositor* compositor = aManager->GetCompositor();
+        compositor->DrawQuad(compositor->ClipRectInLayersCoordinates(clipRect),
+            clipRect, effectChain, opacity, Matrix4x4());
+      }
+    }
+  }
+
   /**
    * Render this container's contents.
    */
   for (uint32_t i = 0; i < children.Length(); i++) {
     LayerComposite* layerToRender = static_cast<LayerComposite*>(children.ElementAt(i)->ImplData());
 
     if (layerToRender->GetLayer()->GetEffectiveVisibleRegion().IsEmpty() &&
         !layerToRender->GetLayer()->AsContainerLayer()) {
old mode 100644
new mode 100755
--- a/gfx/layers/ipc/AsyncTransactionTracker.cpp
+++ b/gfx/layers/ipc/AsyncTransactionTracker.cpp
@@ -84,19 +84,23 @@ AsyncTransactionTrackersHolder::AsyncTra
 
 AsyncTransactionTrackersHolder::~AsyncTransactionTrackersHolder()
 {
   if (!mIsTrackersHolderDestroyed) {
     DestroyAsyncTransactionTrackersHolder();
   }
 
   {
-    MOZ_ASSERT(sHolderLock);
-    MutexAutoLock lock(*sHolderLock);
+    if (sHolderLock) {
+      sHolderLock->Lock();
+    }
     sTrackersHolders.erase(mSerial);
+    if (sHolderLock) {
+      sHolderLock->Unlock();
+    }
   }
   MOZ_COUNT_DTOR(AsyncTransactionTrackersHolder);
 }
 
 void
 AsyncTransactionTrackersHolder::HoldUntilComplete(AsyncTransactionTracker* aTransactionTracker)
 {
   if (!aTransactionTracker) {
@@ -165,23 +169,28 @@ AsyncTransactionTrackersHolder::SetRelea
     return;
   }
   holder->SetReleaseFenceHandle(aReleaseFenceHandle, aTransactionId);
 }
 
 void
 AsyncTransactionTrackersHolder::ClearAllAsyncTransactionTrackers()
 {
-  MutexAutoLock lock(*sHolderLock);
+  if (sHolderLock) {
+    sHolderLock->Lock();
+  }
   std::map<uint64_t, RefPtr<AsyncTransactionTracker> >::iterator it;
   for (it = mAsyncTransactionTrackeres.begin();
        it != mAsyncTransactionTrackeres.end(); it++) {
     it->second->NotifyCancel();
   }
   mAsyncTransactionTrackeres.clear();
+  if (sHolderLock) {
+    sHolderLock->Unlock();
+  }
 }
 
 void
 AsyncTransactionTrackersHolder::DestroyAsyncTransactionTrackersHolder() {
   mIsTrackersHolderDestroyed = true;
   ClearAllAsyncTransactionTrackers();
 }
 
--- a/gfx/layers/ipc/CompositableForwarder.h
+++ b/gfx/layers/ipc/CompositableForwarder.h
@@ -179,16 +179,21 @@ public:
   /**
    * Tell the compositor side that the shared data has been modified so that
    * it can react accordingly (upload textures, etc.).
    */
   virtual void UpdatedTexture(CompositableClient* aCompositable,
                               TextureClient* aTexture,
                               nsIntRegion* aRegion) = 0;
 
+
+  virtual void SendFenceHandle(AsyncTransactionTracker* aTracker,
+                               PTextureChild* aTexture,
+                               const FenceHandle& aFence) = 0;
+
   void IdentifyTextureHost(const TextureFactoryIdentifier& aIdentifier);
 
   virtual int32_t GetMaxTextureSize() const MOZ_OVERRIDE
   {
     return mTextureFactoryIdentifier.mMaxTextureSize;
   }
 
   bool IsOnCompositorSide() const MOZ_OVERRIDE { return false; }
--- a/gfx/layers/ipc/FenceUtils.h
+++ b/gfx/layers/ipc/FenceUtils.h
@@ -13,31 +13,52 @@
 /**
  * FenceHandle is used for delivering Fence object via ipc.
  */
 #if MOZ_WIDGET_GONK && ANDROID_VERSION >= 17
 # include "mozilla/layers/FenceUtilsGonk.h"
 #else
 namespace mozilla {
 namespace layers {
+
+struct FenceHandleFromChild;
+
 struct FenceHandle {
+  FenceHandle() {}
+  FenceHandle(const FenceHandleFromChild& aFenceHandle) {}
   bool operator==(const FenceHandle&) const { return false; }
   bool IsValid() const { return false; }
 };
+
+struct FenceHandleFromChild {
+  FenceHandleFromChild() {}
+  FenceHandleFromChild(const FenceHandle& aFence) {}
+  bool operator==(const FenceHandle&) const { return false; }
+  bool operator==(const FenceHandleFromChild&) const { return false; }
+  bool IsValid() const { return false; }
+};
+
 } // namespace layers
 } // namespace mozilla
 #endif // MOZ_WIDGET_GONK && ANDROID_VERSION >= 17
 
 namespace IPC {
 
 #if MOZ_WIDGET_GONK && ANDROID_VERSION >= 17
 #else
 template <>
 struct ParamTraits<mozilla::layers::FenceHandle> {
   typedef mozilla::layers::FenceHandle paramType;
   static void Write(Message*, const paramType&) {}
   static bool Read(const Message*, void**, paramType*) { return false; }
 };
+
+template <>
+struct ParamTraits<mozilla::layers::FenceHandleFromChild> {
+  typedef mozilla::layers::FenceHandleFromChild paramType;
+  static void Write(Message*, const paramType&) {}
+  static bool Read(const Message*, void**, paramType*) { return false; }
+};
 #endif // MOZ_WIDGET_GONK && ANDROID_VERSION >= 17
 
 } // namespace IPC
 
 #endif // IPC_FencerUtils_h
--- a/gfx/layers/ipc/FenceUtilsGonk.cpp
+++ b/gfx/layers/ipc/FenceUtilsGonk.cpp
@@ -104,20 +104,123 @@ ParamTraits<FenceHandle>::Read(const Mes
   if (NO_ERROR == flattenable->unflatten(data, nbytes, fds, nfds)) {
 #endif
     aResult->mFence = buffer;
     return true;
   }
   return false;
 }
 
+void
+ParamTraits<FenceHandleFromChild>::Write(Message* aMsg,
+                                         const paramType& aParam)
+{
+#if ANDROID_VERSION >= 19
+  sp<Fence> flattenable = aParam.mFence;
+#else
+  Flattenable *flattenable = aParam.mFence.get();
+#endif
+  size_t nbytes = flattenable->getFlattenedSize();
+  size_t nfds = flattenable->getFdCount();
+
+  char data[nbytes];
+  int fds[nfds];
+
+#if ANDROID_VERSION >= 19
+  // Make a copy of "data" and "fds" for flatten() to avoid casting problem
+  void *pdata = (void *)data;
+  int *pfds = fds;
+
+  flattenable->flatten(pdata, nbytes, pfds, nfds);
+
+  // In Kitkat, flatten() will change the value of nbytes and nfds, which dues
+  // to multiple parcelable object consumption. The actual size and fd count
+  // which returned by getFlattenedSize() and getFdCount() are not changed.
+  // So we change nbytes and nfds back by call corresponding calls.
+  nbytes = flattenable->getFlattenedSize();
+  nfds = flattenable->getFdCount();
+#else
+  flattenable->flatten(data, nbytes, fds, nfds);
+#endif
+  aMsg->WriteSize(nbytes);
+  aMsg->WriteSize(nfds);
+
+  aMsg->WriteBytes(data, nbytes);
+  for (size_t n = 0; n < nfds; ++n) {
+    // If the Fence was shared cross-process, SCM_RIGHTS does
+    // the right thing and dup's the fd.  If it's shared cross-thread,
+    // SCM_RIGHTS doesn't dup the fd.  That's surprising, but we just
+    // deal with it here.  NB: only the "default" (master) process can
+    // alloc gralloc buffers.
+    bool sameProcess = (XRE_GetProcessType() == GeckoProcessType_Default);
+    int dupFd = sameProcess ? dup(fds[n]) : fds[n];
+    //int dupFd = fds[n];
+
+    // These buffers can't die in transit because they're created
+    // synchonously and the parent-side buffer can only be dropped if
+    // there's a crash.
+    aMsg->WriteFileDescriptor(FileDescriptor(dupFd, false));
+  }
+}
+
+bool
+ParamTraits<FenceHandleFromChild>::Read(const Message* aMsg,
+                                        void** aIter, paramType* aResult)
+{
+  size_t nbytes;
+  size_t nfds;
+  const char* data;
+
+  if (!aMsg->ReadSize(aIter, &nbytes) ||
+      !aMsg->ReadSize(aIter, &nfds) ||
+      !aMsg->ReadBytes(aIter, &data, nbytes)) {
+    return false;
+  }
+
+  int fds[nfds];
+
+  for (size_t n = 0; n < nfds; ++n) {
+    FileDescriptor fd;
+    if (!aMsg->ReadFileDescriptor(aIter, &fd)) {
+      return false;
+    }
+    fds[n] = fd.fd;
+  }
+
+  sp<Fence> buffer(new Fence());
+#if ANDROID_VERSION >= 19
+  // Make a copy of "data" and "fds" for unflatten() to avoid casting problem
+  void const *pdata = (void const *)data;
+  int const *pfds = fds;
+
+  if (NO_ERROR == buffer->unflatten(pdata, nbytes, pfds, nfds)) {
+#else
+  Flattenable *flattenable = buffer.get();
+
+  if (NO_ERROR == flattenable->unflatten(data, nbytes, fds, nfds)) {
+#endif
+    aResult->mFence = buffer;
+    return true;
+  }
+  return false;
+}
+
 } // namespace IPC
 
 namespace mozilla {
 namespace layers {
 
 FenceHandle::FenceHandle(const sp<Fence>& aFence)
   : mFence(aFence)
 {
 }
 
+FenceHandle::FenceHandle(const FenceHandleFromChild& aFenceHandle) {
+  mFence = aFenceHandle.mFence;
+}
+
+FenceHandleFromChild::FenceHandleFromChild(const sp<Fence>& aFence)
+  : mFence(aFence)
+{
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/FenceUtilsGonk.h
+++ b/gfx/layers/ipc/FenceUtilsGonk.h
@@ -11,43 +11,82 @@
 #include <unistd.h>
 #include <ui/Fence.h>
 
 #include "ipc/IPCMessageUtils.h"
 
 namespace mozilla {
 namespace layers {
 
+struct FenceHandleFromChild;
+
 struct FenceHandle {
   typedef android::Fence Fence;
 
   FenceHandle()
   { }
   FenceHandle(const android::sp<Fence>& aFence);
 
+  FenceHandle(const FenceHandleFromChild& aFenceHandle);
+
   bool operator==(const FenceHandle& aOther) const {
     return mFence.get() == aOther.mFence.get();
   }
 
   bool IsValid() const
   {
     return mFence.get() && mFence->isValid();
   }
 
   android::sp<Fence> mFence;
 };
 
+struct FenceHandleFromChild {
+  typedef android::Fence Fence;
+
+  FenceHandleFromChild()
+  { }
+  FenceHandleFromChild(const android::sp<Fence>& aFence);
+
+  FenceHandleFromChild(const FenceHandle& aFence) {
+    mFence = aFence.mFence;
+  }
+
+  bool operator==(const FenceHandle& aOther) const {
+    return mFence.get() == aOther.mFence.get();
+  }
+
+  bool operator==(const FenceHandleFromChild& aOther) const {
+    return mFence.get() == aOther.mFence.get();
+  }
+
+  bool IsValid() const
+  {
+    return mFence.get() && mFence->isValid();
+  }
+
+  android::sp<Fence> mFence;
+};
+
 } // namespace layers
 } // namespace mozilla
 
 namespace IPC {
 
 template <>
 struct ParamTraits<mozilla::layers::FenceHandle> {
   typedef mozilla::layers::FenceHandle paramType;
 
   static void Write(Message* aMsg, const paramType& aParam);
   static bool Read(const Message* aMsg, void** aIter, paramType* aResult);
 };
 
+template <>
+struct ParamTraits<mozilla::layers::FenceHandleFromChild> {
+  typedef mozilla::layers::FenceHandleFromChild paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam);
+  static bool Read(const Message* aMsg, void** aIter, paramType* aResult);
+};
+
 } // namespace IPC
 
 #endif  // mozilla_layers_FenceUtilsGonk_h
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -147,16 +147,29 @@ ImageBridgeChild::UpdatedTexture(Composi
   MaybeRegion region = aRegion ? MaybeRegion(*aRegion)
                                : MaybeRegion(null_t());
   mTxn->AddNoSwapEdit(OpUpdateTexture(nullptr, aCompositable->GetIPDLActor(),
                                       nullptr, aTexture->GetIPDLActor(),
                                       region));
 }
 
 void
+ImageBridgeChild::SendFenceHandle(AsyncTransactionTracker* aTracker,
+                                  PTextureChild* aTexture,
+                                  const FenceHandle& aFence)
+{
+  HoldUntilComplete(aTracker);
+  InfallibleTArray<AsyncChildMessageData> messages;
+  messages.AppendElement(OpDeliverFenceFromChild(aTracker->GetId(),
+                                                 nullptr, aTexture,
+                                                 FenceHandleFromChild(aFence)));
+  SendChildAsyncMessages(messages);
+}
+
+void
 ImageBridgeChild::UpdatePictureRect(CompositableClient* aCompositable,
                                     const nsIntRect& aRect)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aCompositable->GetIPDLActor());
   mTxn->AddNoSwapEdit(OpUpdatePictureRect(nullptr, aCompositable->GetIPDLActor(), aRect));
 }
 
@@ -528,16 +541,18 @@ ImageBridgeChild::EndTransaction()
 
 
 PImageBridgeChild*
 ImageBridgeChild::StartUpInChildProcess(Transport* aTransport,
                                         ProcessId aOtherProcess)
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
 
+  gfxPlatform::GetPlatform();
+
   ProcessHandle processHandle;
   if (!base::OpenProcessHandle(aOtherProcess, &processHandle)) {
     return nullptr;
   }
 
   sImageBridgeChildThread = new Thread("ImageBridgeChild");
   if (!sImageBridgeChildThread->Start()) {
     return nullptr;
@@ -797,17 +812,17 @@ ImageBridgeChild::AllocPTextureChild(con
 
 bool
 ImageBridgeChild::DeallocPTextureChild(PTextureChild* actor)
 {
   return TextureClient::DestroyIPDLActor(actor);
 }
 
 bool
-ImageBridgeChild::RecvParentAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessages)
+ImageBridgeChild::RecvParentAsyncMessages(const InfallibleTArray<AsyncParentMessageData>& aMessages)
 {
   for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) {
     const AsyncParentMessageData& message = aMessages[i];
 
     switch (message.type()) {
       case AsyncParentMessageData::TOpDeliverFence: {
         const OpDeliverFence& op = message.get_OpDeliverFence();
         FenceHandle fence = op.fence();
@@ -828,23 +843,28 @@ ImageBridgeChild::RecvParentAsyncMessage
                                                               op.destHolderId(),
                                                               op.destTransactionId());
         // Send back a response.
         InfallibleTArray<AsyncChildMessageData> replies;
         replies.AppendElement(OpReplyDeliverFence(op.transactionId()));
         SendChildAsyncMessages(replies);
         break;
       }
+      case AsyncParentMessageData::TOpReplyDeliverFence: {
+        const OpReplyDeliverFence& op = message.get_OpReplyDeliverFence();
+        TransactionCompleteted(op.transactionId());
+        break;
+      }
       case AsyncParentMessageData::TOpReplyRemoveTexture: {
         const OpReplyRemoveTexture& op = message.get_OpReplyRemoveTexture();
 
         AsyncTransactionTrackersHolder::TransactionCompleteted(op.holderId(),
                                                                op.transactionId());
-      break;
-    }
+        break;
+      }
       default:
         NS_ERROR("unknown AsyncParentMessageData type");
         return false;
     }
   }
   return true;
 }
 
--- a/gfx/layers/ipc/ImageBridgeChild.h
+++ b/gfx/layers/ipc/ImageBridgeChild.h
@@ -6,16 +6,17 @@
 #ifndef MOZILLA_GFX_IMAGEBRIDGECHILD_H
 #define MOZILLA_GFX_IMAGEBRIDGECHILD_H
 
 #include <stddef.h>                     // for size_t
 #include <stdint.h>                     // for uint32_t, uint64_t
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/RefPtr.h"             // for TemporaryRef
 #include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
+#include "mozilla/layers/AsyncTransactionTracker.h" // for AsyncTransactionTrackerHolder
 #include "mozilla/layers/CompositableForwarder.h"
 #include "mozilla/layers/CompositorTypes.h"  // for TextureIdentifier, etc
 #include "mozilla/layers/PImageBridgeChild.h"
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsRegion.h"                   // for nsIntRegion
 class MessageLoop;
 struct nsIntPoint;
 struct nsIntRect;
@@ -96,16 +97,17 @@ bool InImageBridgeChildThread();
  * until the layer transaction happens. This means this scenario is not harmful.
  *
  * Since sending an image through imageBridge triggers compositing, the main thread is
  * not used at all (except for the very first transaction that provides the
  * CompositableHost with an AsyncID).
  */
 class ImageBridgeChild : public PImageBridgeChild
                        , public CompositableForwarder
+                       , public AsyncTransactionTrackersHolder
 {
   friend class ImageContainer;
   typedef InfallibleTArray<AsyncParentMessageData> AsyncParentMessageArray;
 public:
 
   /**
    * Creates the image bridge with a dedicated thread for ImageBridgeChild.
    *
@@ -181,17 +183,17 @@ public:
 
   virtual PTextureChild*
   AllocPTextureChild(const SurfaceDescriptor& aSharedData, const TextureFlags& aFlags) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE;
 
   virtual bool
-  RecvParentAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessages) MOZ_OVERRIDE;
+  RecvParentAsyncMessages(const InfallibleTArray<AsyncParentMessageData>& aMessages) MOZ_OVERRIDE;
 
   TemporaryRef<ImageClient> CreateImageClient(CompositableType aType);
   TemporaryRef<ImageClient> CreateImageClientNow(CompositableType aType);
 
   static void DispatchReleaseImageClient(ImageClient* aClient);
   static void DispatchReleaseTextureClient(TextureClient* aClient);
   static void DispatchImageClientUpdate(ImageClient* aClient, ImageContainer* aContainer);
 
@@ -217,16 +219,20 @@ public:
    * See CompositableForwarder::UseTexture
    */
   virtual void UseTexture(CompositableClient* aCompositable,
                           TextureClient* aClient) MOZ_OVERRIDE;
   virtual void UseComponentAlphaTextures(CompositableClient* aCompositable,
                                          TextureClient* aClientOnBlack,
                                          TextureClient* aClientOnWhite) MOZ_OVERRIDE;
 
+  virtual void SendFenceHandle(AsyncTransactionTracker* aTracker,
+                               PTextureChild* aTexture,
+                               const FenceHandle& aFence) MOZ_OVERRIDE;
+
   virtual void RemoveTextureFromCompositable(CompositableClient* aCompositable,
                                              TextureClient* aTexture) MOZ_OVERRIDE;
 
   virtual void RemoveTextureFromCompositableAsync(AsyncTransactionTracker* aAsyncTransactionTracker,
                                                   CompositableClient* aCompositable,
                                                   TextureClient* aTexture) MOZ_OVERRIDE;
 
   virtual void RemoveTexture(TextureClient* aTexture) MOZ_OVERRIDE;
--- a/gfx/layers/ipc/ImageBridgeParent.cpp
+++ b/gfx/layers/ipc/ImageBridgeParent.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/ipc/Transport.h"      // for Transport
 #include "mozilla/layers/CompositableTransactionParent.h"
 #include "mozilla/layers/CompositorParent.h"  // for CompositorParent
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply
 #include "mozilla/layers/LayersSurfaces.h"  // for PGrallocBufferParent
 #include "mozilla/layers/PCompositableParent.h"
 #include "mozilla/layers/PImageBridgeParent.h"
+#include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/mozalloc.h"           // for operator new, etc
 #include "mozilla/unused.h"
 #include "nsAutoPtr.h"                  // for nsRefPtr
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT, etc
 #include "nsISupportsImpl.h"            // for ImageBridgeParent::Release, etc
 #include "nsTArray.h"                   // for nsTArray, nsTArray_Impl
 #include "nsTArrayForwardDeclare.h"     // for InfallibleTArray
@@ -203,32 +204,53 @@ ImageBridgeParent::SendFenceHandle(Async
                                    PTextureParent* aTexture,
                                    const FenceHandle& aFence)
 {
   HoldUntilComplete(aTracker);
   InfallibleTArray<AsyncParentMessageData> messages;
   messages.AppendElement(OpDeliverFence(aTracker->GetId(),
                                         aTexture, nullptr,
                                         aFence));
-  mozilla::unused << SendParentAsyncMessage(messages);
+  mozilla::unused << SendParentAsyncMessages(messages);
 }
 
 void
 ImageBridgeParent::SendAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessage)
 {
-  mozilla::unused << SendParentAsyncMessage(aMessage);
+  mozilla::unused << SendParentAsyncMessages(aMessage);
 }
 
 bool
 ImageBridgeParent::RecvChildAsyncMessages(const InfallibleTArray<AsyncChildMessageData>& aMessages)
 {
   for (AsyncChildMessageArray::index_type i = 0; i < aMessages.Length(); ++i) {
     const AsyncChildMessageData& message = aMessages[i];
 
     switch (message.type()) {
+      case AsyncChildMessageData::TOpDeliverFenceFromChild: {
+        const OpDeliverFenceFromChild& op = message.get_OpDeliverFenceFromChild();
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+        FenceHandle fence = FenceHandle(op.fence());
+        PTextureParent* parent = op.textureParent();
+
+        TextureHostOGL* hostOGL = nullptr;
+        RefPtr<TextureHost> texture = TextureHost::AsTextureHost(parent);
+        if (texture) {
+          hostOGL = texture->AsHostOGL();
+        }
+        if (hostOGL) {
+          hostOGL->SetAcquireFence(fence.mFence);
+        }
+#endif
+        // Send back a response.
+        InfallibleTArray<AsyncParentMessageData> replies;
+        replies.AppendElement(OpReplyDeliverFence(op.transactionId()));
+        mozilla::unused << SendParentAsyncMessages(replies);
+        break;
+      }
       case AsyncChildMessageData::TOpReplyDeliverFence: {
         const OpReplyDeliverFence& op = message.get_OpReplyDeliverFence();
         TransactionCompleteted(op.transactionId());
         break;
       }
       default:
         NS_ERROR("unknown AsyncChildMessageData type");
         return false;
@@ -299,17 +321,17 @@ bool ImageBridgeParent::IsSameProcess() 
   return OtherProcess() == ipc::kInvalidProcessHandle;
 }
 
 void
 ImageBridgeParent::ReplyRemoveTexture(const OpReplyRemoveTexture& aReply)
 {
   InfallibleTArray<AsyncParentMessageData> messages;
   messages.AppendElement(aReply);
-  mozilla::unused << SendParentAsyncMessage(messages);
+  mozilla::unused << SendParentAsyncMessages(messages);
 }
 
 /*static*/ void
 ImageBridgeParent::ReplyRemoveTexture(base::ProcessId aChildProcessId,
                                       const OpReplyRemoveTexture& aReply)
 {
   ImageBridgeParent* imageBridge = ImageBridgeParent::GetInstance(aChildProcessId);
   if (!imageBridge) {
@@ -334,17 +356,17 @@ ImageBridgeParent::SendFenceHandleToTrac
 
   RefPtr<FenceDeliveryTracker> tracker = new FenceDeliveryTracker(fence);
   HoldUntilComplete(tracker);
   InfallibleTArray<AsyncParentMessageData> messages;
   messages.AppendElement(OpDeliverFenceToTracker(tracker->GetId(),
                                                  aDestHolderId,
                                                  aTransactionId,
                                                  fence));
-  mozilla::unused << SendParentAsyncMessage(messages);
+  mozilla::unused << SendParentAsyncMessages(messages);
 }
 
 /*static*/ void
 ImageBridgeParent::SendFenceHandleToTrackerIfPresent(base::ProcessId aChildProcessId,
                                                      uint64_t aDestHolderId,
                                                      uint64_t aTransactionId,
                                                      PTextureParent* aTexture)
 {
--- a/gfx/layers/ipc/LayerTransactionChild.cpp
+++ b/gfx/layers/ipc/LayerTransactionChild.cpp
@@ -61,17 +61,17 @@ LayerTransactionChild::AllocPCompositabl
 
 bool
 LayerTransactionChild::DeallocPCompositableChild(PCompositableChild* actor)
 {
   return CompositableClient::DestroyIPDLActor(actor);
 }
 
 bool
-LayerTransactionChild::RecvParentAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessages)
+LayerTransactionChild::RecvParentAsyncMessages(const InfallibleTArray<AsyncParentMessageData>& aMessages)
 {
   for (AsyncParentMessageArray::index_type i = 0; i < aMessages.Length(); ++i) {
     const AsyncParentMessageData& message = aMessages[i];
 
     switch (message.type()) {
       case AsyncParentMessageData::TOpDeliverFence: {
         const OpDeliverFence& op = message.get_OpDeliverFence();
         FenceHandle fence = op.fence();
@@ -86,27 +86,46 @@ LayerTransactionChild::RecvParentAsyncMe
         } else {
           // Send back a response.
           InfallibleTArray<AsyncChildMessageData> replies;
           replies.AppendElement(OpReplyDeliverFence(op.transactionId()));
           SendChildAsyncMessages(replies);
         }
         break;
       }
+      case AsyncParentMessageData::TOpReplyDeliverFence: {
+        const OpReplyDeliverFence& op = message.get_OpReplyDeliverFence();
+        TransactionCompleteted(op.transactionId());
+        break;
+      }
       default:
         NS_ERROR("unknown AsyncParentMessageData type");
         return false;
     }
   }
   return true;
 }
 
 void
+LayerTransactionChild::SendFenceHandle(AsyncTransactionTracker* aTracker,
+                                       PTextureChild* aTexture,
+                                       const FenceHandle& aFence)
+{
+  HoldUntilComplete(aTracker);
+  InfallibleTArray<AsyncChildMessageData> messages;
+  messages.AppendElement(OpDeliverFenceFromChild(aTracker->GetId(),
+                                                 nullptr, aTexture,
+                                                 FenceHandleFromChild(aFence)));
+  SendChildAsyncMessages(messages);
+}
+
+void
 LayerTransactionChild::ActorDestroy(ActorDestroyReason why)
 {
+  DestroyAsyncTransactionTrackersHolder();
 #ifdef MOZ_B2G
   // Due to poor lifetime management of gralloc (and possibly shmems) we will
   // crash at some point in the future when we get destroyed due to abnormal
   // shutdown. Its better just to crash here. On desktop though, we have a chance
   // of recovering.
   if (why == AbnormalShutdown) {
     NS_RUNTIMEABORT("ActorDestroy by IPC channel failure at LayerTransactionChild");
   }
--- a/gfx/layers/ipc/LayerTransactionChild.h
+++ b/gfx/layers/ipc/LayerTransactionChild.h
@@ -6,29 +6,31 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H
 #define MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H
 
 #include <stdint.h>                     // for uint32_t
 #include "mozilla/Attributes.h"         // for MOZ_OVERRIDE
 #include "mozilla/ipc/ProtocolUtils.h"
+#include "mozilla/layers/AsyncTransactionTracker.h" // for AsyncTransactionTracker
 #include "mozilla/layers/PLayerTransactionChild.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 
 namespace layout {
 class RenderFrameChild;
 class ShadowLayerForwarder;
 }
 
 namespace layers {
 
 class LayerTransactionChild : public PLayerTransactionChild
+                            , public AsyncTransactionTrackersHolder
 {
   typedef InfallibleTArray<AsyncParentMessageData> AsyncParentMessageArray;
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(LayerTransactionChild)
   /**
    * Clean this up, finishing with Send__delete__().
    *
    * It is expected (checked with an assert) that all shadow layers
@@ -42,16 +44,20 @@ public:
   void SetHasNoCompositor() { mHasNoCompositor = true; }
   bool HasNoCompositor() { return mHasNoCompositor; }
 
   void SetForwarder(ShadowLayerForwarder* aForwarder)
   {
     mForwarder = aForwarder;
   }
 
+  virtual void SendFenceHandle(AsyncTransactionTracker* aTracker,
+                               PTextureChild* aTexture,
+                               const FenceHandle& aFence);
+
 protected:
   LayerTransactionChild()
     : mForwarder(nullptr)
     , mIPCOpen(false)
     , mDestroyed(false)
     , mHasNoCompositor(false)
   {}
   ~LayerTransactionChild() { }
@@ -62,17 +68,17 @@ protected:
   virtual PCompositableChild* AllocPCompositableChild(const TextureInfo& aInfo) MOZ_OVERRIDE;
   virtual bool DeallocPCompositableChild(PCompositableChild* actor) MOZ_OVERRIDE;
 
   virtual PTextureChild* AllocPTextureChild(const SurfaceDescriptor& aSharedData,
                                             const TextureFlags& aFlags) MOZ_OVERRIDE;
   virtual bool DeallocPTextureChild(PTextureChild* actor) MOZ_OVERRIDE;
 
   virtual bool
-  RecvParentAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessages) MOZ_OVERRIDE;
+  RecvParentAsyncMessages(const InfallibleTArray<AsyncParentMessageData>& aMessages) MOZ_OVERRIDE;
 
   virtual void ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE;
 
   void AddIPDLReference() {
     MOZ_ASSERT(mIPCOpen == false);
     mIPCOpen = true;
     AddRef();
   }
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -22,16 +22,17 @@
 #include "mozilla/layers/ContainerLayerComposite.h"
 #include "mozilla/layers/ImageLayerComposite.h"
 #include "mozilla/layers/LayerManagerComposite.h"
 #include "mozilla/layers/LayersMessages.h"  // for EditReply, etc
 #include "mozilla/layers/LayersSurfaces.h"  // for PGrallocBufferParent
 #include "mozilla/layers/LayersTypes.h"  // for MOZ_LAYERS_LOG
 #include "mozilla/layers/PCompositableParent.h"
 #include "mozilla/layers/PLayerParent.h"  // for PLayerParent
+#include "mozilla/layers/TextureHostOGL.h"  // for TextureHostOGL
 #include "mozilla/layers/ThebesLayerComposite.h"
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "mozilla/unused.h"
 #include "nsCoord.h"                    // for NSAppUnitsToFloatPixels
 #include "nsDebug.h"                    // for NS_RUNTIMEABORT
 #include "nsDeviceContext.h"            // for AppUnitsPerCSSPixel
 #include "nsISupportsImpl.h"            // for Layer::Release, etc
 #include "nsLayoutUtils.h"              // for nsLayoutUtils
@@ -345,16 +346,17 @@ LayerTransactionParent::RecvUpdate(const
           return false;
         }
         const ContainerLayerAttributes& attrs =
           specific.get_ContainerLayerAttributes();
         containerLayer->SetFrameMetrics(attrs.metrics());
         containerLayer->SetScrollHandoffParentId(attrs.scrollParentId());
         containerLayer->SetPreScale(attrs.preXScale(), attrs.preYScale());
         containerLayer->SetInheritedScale(attrs.inheritedXScale(), attrs.inheritedYScale());
+        containerLayer->SetBackgroundColor(attrs.backgroundColor().value());
         break;
       }
       case Specific::TColorLayerAttributes: {
         MOZ_LAYERS_LOG(("[ParentSide]   color layer"));
 
         ColorLayerComposite* colorLayer = layerParent->AsColorLayerComposite();
         if (!colorLayer) {
           return false;
@@ -797,16 +799,37 @@ LayerTransactionParent::DeallocPTextureP
 
 bool
 LayerTransactionParent::RecvChildAsyncMessages(const InfallibleTArray<AsyncChildMessageData>& aMessages)
 {
   for (AsyncChildMessageArray::index_type i = 0; i < aMessages.Length(); ++i) {
     const AsyncChildMessageData& message = aMessages[i];
 
     switch (message.type()) {
+      case AsyncChildMessageData::TOpDeliverFenceFromChild: {
+        const OpDeliverFenceFromChild& op = message.get_OpDeliverFenceFromChild();
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+        FenceHandle fence = FenceHandle(op.fence());
+        PTextureParent* parent = op.textureParent();
+
+        TextureHostOGL* hostOGL = nullptr;
+        RefPtr<TextureHost> texture = TextureHost::AsTextureHost(parent);
+        if (texture) {
+          hostOGL = texture->AsHostOGL();
+        }
+        if (hostOGL) {
+          hostOGL->SetAcquireFence(fence.mFence);
+        }
+#endif
+        // Send back a response.
+        InfallibleTArray<AsyncParentMessageData> replies;
+        replies.AppendElement(OpReplyDeliverFence(op.transactionId()));
+        mozilla::unused << SendParentAsyncMessages(replies);
+        break;
+      }
       case AsyncChildMessageData::TOpReplyDeliverFence: {
         const OpReplyDeliverFence& op = message.get_OpReplyDeliverFence();
         TransactionCompleteted(op.transactionId());
         break;
       }
       default:
         NS_ERROR("unknown AsyncChildMessageData type");
         return false;
@@ -831,19 +854,19 @@ LayerTransactionParent::SendFenceHandle(
                                         PTextureParent* aTexture,
                                         const FenceHandle& aFence)
 {
   HoldUntilComplete(aTracker);
   InfallibleTArray<AsyncParentMessageData> messages;
   messages.AppendElement(OpDeliverFence(aTracker->GetId(),
                                         aTexture, nullptr,
                                         aFence));
-  mozilla::unused << SendParentAsyncMessage(messages);
+  mozilla::unused << SendParentAsyncMessages(messages);
 }
 
 void
 LayerTransactionParent::SendAsyncMessage(const InfallibleTArray<AsyncParentMessageData>& aMessage)
 {
-  mozilla::unused << SendParentAsyncMessage(aMessage);
+  mozilla::unused << SendParentAsyncMessages(aMessage);
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -35,16 +35,17 @@ using mozilla::LayerMargin from "Units.h
 using mozilla::LayerPoint from "Units.h";
 using mozilla::LayerRect from "Units.h";
 using mozilla::layers::ScaleMode from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::EventRegions from "mozilla/layers/LayersTypes.h";
 using mozilla::layers::DiagnosticTypes from "mozilla/layers/CompositorTypes.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using struct mozilla::layers::FenceHandle from "mozilla/layers/FenceUtils.h";
+using struct mozilla::layers::FenceHandleFromChild from "mozilla/layers/FenceUtils.h";
 using mozilla::layers::TextureIdentifier from "mozilla/layers/CompositorTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 struct TargetConfig {
   nsIntRect naturalBounds;
   ScreenRotation rotation;
@@ -221,16 +222,17 @@ struct ThebesLayerAttributes {
 };
 struct ContainerLayerAttributes {
   FrameMetrics metrics;
   ViewID scrollParentId;
   float preXScale;
   float preYScale;
   float inheritedXScale;
   float inheritedYScale;
+  LayerColor backgroundColor;
 };
 struct ColorLayerAttributes     { LayerColor color; nsIntRect bounds; };
 struct CanvasLayerAttributes    { GraphicsFilterType filter; nsIntRect bounds; };
 struct RefLayerAttributes       { int64_t id; };
 struct ImageLayerAttributes     { GraphicsFilterType filter; IntSize scaleToSize; ScaleMode scaleMode; };
 
 union SpecificLayerAttributes {
   null_t;
@@ -382,16 +384,22 @@ struct OpDeliverFence {
 
 struct OpDeliverFenceToTracker {
   uint64_t transactionId;
   uint64_t destHolderId;
   uint64_t destTransactionId;
   FenceHandle fence;
 };
 
+struct OpDeliverFenceFromChild {
+  uint64_t transactionId;
+  PTexture texture;
+  FenceHandleFromChild fence;
+};
+
 struct OpReplyDeliverFence {
   uint64_t transactionId;
 };
 
 union CompositableOperation {
   OpUpdatePictureRect;
 
   OpCreatedIncrementalTexture;
@@ -461,17 +469,19 @@ union EditReply {
   OpTextureSwap;
 
   ReturnReleaseFence;
 };
 
 union AsyncParentMessageData {
   OpDeliverFence;
   OpDeliverFenceToTracker;
+  OpReplyDeliverFence;
   OpReplyRemoveTexture;
 };
 
 union AsyncChildMessageData {
+  OpDeliverFenceFromChild;
   OpReplyDeliverFence;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/ipc/PImageBridge.ipdl
+++ b/gfx/layers/ipc/PImageBridge.ipdl
@@ -24,17 +24,17 @@ namespace layers {
  * which might be too busy dealing with content script.
  */
 intr protocol PImageBridge
 {
   manages PCompositable;
   manages PTexture;
 
 child:
-  async ParentAsyncMessage(AsyncParentMessageData[] aMessages);
+  async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
 parent:
 
   sync Update(CompositableOperation[] ops) returns (EditReply[] reply);
   async UpdateNoSwap(CompositableOperation[] ops);
 
   // First step of the destruction sequence. This puts ImageBridge
   // in a state in which it can't send asynchronous messages
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -38,17 +38,17 @@ union MaybeTransform {
 
 sync protocol PLayerTransaction {
   manager PRenderFrame or PCompositor;
   manages PLayer;
   manages PCompositable;
   manages PTexture;
 
 child:
-  async ParentAsyncMessage(AsyncParentMessageData[] aMessages);
+  async ParentAsyncMessages(AsyncParentMessageData[] aMessages);
 
 parent:
   async PLayer();
   async PCompositable(TextureInfo aTextureInfo);
   async PTexture(SurfaceDescriptor aSharedData, TextureFlags aTextureFlags);
 
   // The isFirstPaint flag can be used to indicate that this is the first update
   // for a particular document.
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -406,16 +406,27 @@ ShadowLayerForwarder::UseComponentAlphaT
   MOZ_ASSERT(aTextureOnWhite->GetIPDLActor());
   MOZ_ASSERT(aTextureOnBlack->GetSize() == aTextureOnWhite->GetSize());
   mTxn->AddEdit(OpUseComponentAlphaTextures(nullptr, aCompositable->GetIPDLActor(),
                                             nullptr, aTextureOnBlack->GetIPDLActor(),
                                             nullptr, aTextureOnWhite->GetIPDLActor()));
 }
 
 void
+ShadowLayerForwarder::SendFenceHandle(AsyncTransactionTracker* aTracker,
+                                        PTextureChild* aTexture,
+                                        const FenceHandle& aFence)
+{
+  if (!HasShadowManager() || !mShadowManager->IPCOpen()) {
+    return;
+  }
+  mShadowManager->SendFenceHandle(aTracker, aTexture, aFence);
+}
+
+void
 ShadowLayerForwarder::RemoveTextureFromCompositable(CompositableClient* aCompositable,
                                                     TextureClient* aTexture)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aTexture);
   MOZ_ASSERT(aCompositable->GetIPDLActor());
   MOZ_ASSERT(aTexture->GetIPDLActor());
   mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
--- a/gfx/layers/ipc/ShadowLayers.h
+++ b/gfx/layers/ipc/ShadowLayers.h
@@ -283,16 +283,20 @@ public:
    * See CompositableForwarder::UseTexture
    */
   virtual void UseTexture(CompositableClient* aCompositable,
                           TextureClient* aClient) MOZ_OVERRIDE;
   virtual void UseComponentAlphaTextures(CompositableClient* aCompositable,
                                          TextureClient* aClientOnBlack,
                                          TextureClient* aClientOnWhite) MOZ_OVERRIDE;
 
+  virtual void SendFenceHandle(AsyncTransactionTracker* aTracker,
+                               PTextureChild* aTexture,
+                               const FenceHandle& aFence) MOZ_OVERRIDE;
+
   /**
    * End the current transaction and forward it to LayerManagerComposite.
    * |aReplies| are directions from the LayerManagerComposite to the
    * caller of EndTransaction().
    */
   bool EndTransaction(InfallibleTArray<EditReply>* aReplies,
                       const nsIntRegion& aRegionToClear,
                       uint64_t aId,
--- a/gfx/layers/opengl/GrallocTextureClient.cpp
+++ b/gfx/layers/opengl/GrallocTextureClient.cpp
@@ -71,22 +71,16 @@ GrallocTextureClientOGL::ToSurfaceDescri
     return false;
   }
 
   aOutDescriptor = NewSurfaceDescriptorGralloc(mGrallocHandle, mSize);
   return true;
 }
 
 void
-GrallocTextureClientOGL::SetReleaseFenceHandle(FenceHandle aReleaseFenceHandle)
-{
-  mReleaseFenceHandle = aReleaseFenceHandle;
-}
-
-void
 GrallocTextureClientOGL::SetRemoveFromCompositableTracker(AsyncTransactionTracker* aTracker)
 {
   mRemoveFromCompositableTracker = aTracker;
 }
 
 void
 GrallocTextureClientOGL::WaitForBufferOwnership()
 {
--- a/gfx/layers/opengl/GrallocTextureClient.h
+++ b/gfx/layers/opengl/GrallocTextureClient.h
@@ -55,18 +55,16 @@ public:
   virtual bool ImplementsLocking() const MOZ_OVERRIDE { return true; }
 
   virtual bool HasInternalBuffer() const MOZ_OVERRIDE { return false; }
 
   virtual bool IsAllocated() const MOZ_OVERRIDE;
 
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) MOZ_OVERRIDE;
 
-  virtual void SetReleaseFenceHandle(FenceHandle aReleaseFenceHandle) MOZ_OVERRIDE;
-
   virtual void SetRemoveFromCompositableTracker(AsyncTransactionTracker* aTracker) MOZ_OVERRIDE;
 
   virtual void WaitForBufferOwnership() MOZ_OVERRIDE;
 
   void InitWith(MaybeMagicGrallocBufferHandle aDesc, gfx::IntSize aSize);
 
   void SetTextureFlags(TextureFlags aFlags) { AddFlags(aFlags); }
 
--- a/gfx/layers/opengl/GrallocTextureHost.cpp
+++ b/gfx/layers/opengl/GrallocTextureHost.cpp
@@ -85,19 +85,21 @@ TextureTargetForAndroidPixelFormat(andro
       // like undesirable behaviour. We'd rather have a subtle artifact.
       MOZ_ASSERT(false, "Unknown Android pixel format.");
       return LOCAL_GL_TEXTURE_EXTERNAL;
     }
   }
 }
 
 GrallocTextureSourceOGL::GrallocTextureSourceOGL(CompositorOGL* aCompositor,
+                                                 GrallocTextureHostOGL* aTextureHost,
                                                  android::GraphicBuffer* aGraphicBuffer,
                                                  gfx::SurfaceFormat aFormat)
   : mCompositor(aCompositor)
+  , mTextureHost(aTextureHost)
   , mGraphicBuffer(aGraphicBuffer)
   , mEGLImage(0)
   , mFormat(aFormat)
   , mNeedsReset(true)
 {
   MOZ_ASSERT(mGraphicBuffer.get());
 }
 
@@ -295,16 +297,17 @@ GrallocTextureHostOGL::GrallocTextureHos
   if (graphicBuffer) {
     format =
       SurfaceFormatForAndroidPixelFormat(graphicBuffer->getPixelFormat(),
                                          aFlags & TextureFlags::RB_SWAPPED);
   } else {
     NS_WARNING("gralloc buffer is nullptr");
   }
   mTextureSource = new GrallocTextureSourceOGL(nullptr,
+                                               this,
                                                graphicBuffer,
                                                format);
 }
 
 GrallocTextureHostOGL::~GrallocTextureHostOGL()
 {
   mTextureSource = nullptr;
 }
@@ -435,16 +438,22 @@ GrallocTextureSourceOGL::GetGLTexture()
   }
 
   return mTexture;
 }
 
 void
 GrallocTextureSourceOGL::BindEGLImage()
 {
+#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
+  if (mTextureHost) {
+    mTextureHost->WaitAcquireFenceSyncComplete();
+  }
+#endif
+
   if (mCompositableBackendData) {
     CompositableDataGonkOGL* backend = static_cast<CompositableDataGonkOGL*>(mCompositableBackendData.get());
     backend->BindEGLImage(GetTextureTarget(), mEGLImage);
   } else {
     gl()->fEGLImageTargetTexture2D(GetTextureTarget(), mEGLImage);
   }
 }
 
--- a/gfx/layers/opengl/GrallocTextureHost.h
+++ b/gfx/layers/opengl/GrallocTextureHost.h
@@ -18,16 +18,17 @@ class GrallocTextureHostOGL;
 
 class GrallocTextureSourceOGL : public NewTextureSource
                               , public TextureSourceOGL
 {
 public:
   friend class GrallocTextureHostOGL;
 
   GrallocTextureSourceOGL(CompositorOGL* aCompositor,
+                          GrallocTextureHostOGL* aTextureHost,
                           android::GraphicBuffer* aGraphicBuffer,
                           gfx::SurfaceFormat aFormat);
 
   virtual ~GrallocTextureSourceOGL();
 
   virtual bool IsValid() const MOZ_OVERRIDE;
 
   virtual void BindTexture(GLenum aTextureUnit, gfx::Filter aFilter) MOZ_OVERRIDE;
@@ -51,28 +52,30 @@ public:
 
   gl::GLContext* gl() const;
 
   virtual void SetCompositor(Compositor* aCompositor) MOZ_OVERRIDE;
 
   void ForgetBuffer()
   {
     mGraphicBuffer = nullptr;
+    mTextureHost = nullptr;
   }
 
   TemporaryRef<gfx::DataSourceSurface> GetAsSurface();
 
   GLuint GetGLTexture();
 
   void BindEGLImage();
 
   void Lock();
 
 protected:
   CompositorOGL* mCompositor;
+  GrallocTextureHostOGL* mTextureHost;
   android::sp<android::GraphicBuffer> mGraphicBuffer;
   EGLImage mEGLImage;
   GLuint mTexture;
   gfx::SurfaceFormat mFormat;
   bool mNeedsReset;
 };
 
 class GrallocTextureHostOGL : public TextureHost
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 20; 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 "TextureHostOGL.h"
 #include "GLContext.h"                  // for GLContext, etc
+#include "GLLibraryEGL.h"               // for GLLibraryEGL
 #include "GLSharedHandleHelpers.h"
 #include "GLUploadHelpers.h"
 #include "GLReadTexImageHelper.h"
 #include "SharedSurface.h"              // for SharedSurface
 #include "SharedSurfaceEGL.h"           // for SharedSurface_EGLImage
 #include "SharedSurfaceGL.h"            // for SharedSurface_GLTexture, etc
 #include "SurfaceStream.h"              // for SurfaceStream
 #include "SurfaceTypes.h"               // for SharedSurfaceType, etc
@@ -215,16 +216,66 @@ android::sp<android::Fence>
 TextureHostOGL::GetAndResetReleaseFence()
 {
   // Hold previous ReleaseFence to prevent Fence delivery failure via gecko IPC.
   mPrevReleaseFence = mReleaseFence;
   // Reset current ReleaseFence.
   mReleaseFence = android::Fence::NO_FENCE;
   return mPrevReleaseFence;
 }
+
+void
+TextureHostOGL::SetAcquireFence(const android::sp<android::Fence>& aAcquireFence)
+{
+  mAcquireFence = aAcquireFence;
+}
+
+android::sp<android::Fence>
+TextureHostOGL::GetAcquireFence()
+{
+  return mAcquireFence;
+}
+
+void
+TextureHostOGL::WaitAcquireFenceSyncComplete()
+{
+  if (!mAcquireFence.get() || !mAcquireFence->isValid()) {
+    return;
+  }
+
+  int fenceFd = mAcquireFence->dup();
+  if (fenceFd == -1) {
+    NS_WARNING("failed to dup fence fd");
+    return;
+  }
+
+  EGLint attribs[] = {
+              LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd,
+              LOCAL_EGL_NONE
+          };
+
+  EGLSync sync = sEGLLibrary.fCreateSync(EGL_DISPLAY(),
+                                         LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID,
+                                         attribs);
+  if (!sync) {
+    NS_WARNING("failed to create native fence sync");
+    return;
+  }
+
+  EGLint status = sEGLLibrary.fClientWaitSync(EGL_DISPLAY(),
+                                              sync,
+                                              0,
+                                              LOCAL_EGL_FOREVER);
+  if (status != LOCAL_EGL_CONDITION_SATISFIED) {
+    NS_WARNING("failed to wait native fence sync");
+  }
+  MOZ_ALWAYS_TRUE( sEGLLibrary.fDestroySync(EGL_DISPLAY(), sync) );
+  mAcquireFence = nullptr;
+}
+
 #endif
 
 bool
 TextureImageTextureSourceOGL::Update(gfx::DataSourceSurface* aSurface,
                                      nsIntRegion* aDestRegion,
                                      gfx::IntPoint* aSrcOffset)
 {
   MOZ_ASSERT(mGL);
--- a/gfx/layers/opengl/TextureHostOGL.h
+++ b/gfx/layers/opengl/TextureHostOGL.h
@@ -167,19 +167,27 @@ public:
    */
   virtual bool SetReleaseFence(const android::sp<android::Fence>& aReleaseFence);
 
   /**
    * Return a releaseFence's Fence and clear a reference to the Fence.
    */
   virtual android::sp<android::Fence> GetAndResetReleaseFence();
 
+  virtual void SetAcquireFence(const android::sp<android::Fence>& aAcquireFence);
+
+  virtual android::sp<android::Fence> GetAcquireFence();
+
+  virtual void WaitAcquireFenceSyncComplete();
+
 protected:
   android::sp<android::Fence> mReleaseFence;
 
+  android::sp<android::Fence> mAcquireFence;
+
   /**
    * Hold previous ReleaseFence to prevent Fence delivery failure via gecko IPC.
    * Fence is a kernel object and its lifetime is managed by a reference count.
    * Until the Fence is delivered to client side, need to hold Fence on host side.
    */
   android::sp<android::Fence> mPrevReleaseFence;
 #endif
 };
--- a/js/public/GCAPI.h
+++ b/js/public/GCAPI.h
@@ -370,17 +370,17 @@ ShrinkGCBuffers(JSRuntime *rt);
 
 /*
  * Assert if a GC occurs while this class is live. This class does not disable
  * the static rooting hazard analysis.
  */
 class JS_PUBLIC_API(AutoAssertOnGC)
 {
 #ifdef DEBUG
-    JSRuntime *runtime;
+    js::gc::GCRuntime *gc;
     size_t gcNumber;
 
   public:
     AutoAssertOnGC();
     explicit AutoAssertOnGC(JSRuntime *rt);
     ~AutoAssertOnGC();
 
     static void VerifyIsSafeToGC(JSRuntime *rt);
--- a/js/public/Id.h
+++ b/js/public/Id.h
@@ -166,17 +166,16 @@ IsPoisonedId(jsid iden)
     if (JSID_IS_OBJECT(iden))
         return JS::IsPoisonedPtr(JSID_TO_OBJECT(iden));
     return false;
 }
 
 template <> struct GCMethods<jsid>
 {
     static jsid initial() { return JSID_VOID; }
-    static ThingRootKind kind() { return THING_ROOT_ID; }
     static bool poisoned(jsid id) { return IsPoisonedId(id); }
     static bool needsPostBarrier(jsid id) { return false; }
 #ifdef JSGC_GENERATIONAL
     static void postBarrier(jsid *idp) {}
     static void relocate(jsid *idp) {}
 #endif
 };
 
--- a/js/public/RootingAPI.h
+++ b/js/public/RootingAPI.h
@@ -649,30 +649,28 @@ struct RootKind<T *>
 {
     static ThingRootKind rootKind() { return T::rootKind(); }
 };
 
 template <typename T>
 struct GCMethods<T *>
 {
     static T *initial() { return nullptr; }
-    static ThingRootKind kind() { return RootKind<T *>::rootKind(); }
     static bool poisoned(T *v) { return JS::IsPoisonedPtr(v); }
     static bool needsPostBarrier(T *v) { return false; }
 #ifdef JSGC_GENERATIONAL
     static void postBarrier(T **vp) {}
     static void relocate(T **vp) {}
 #endif
 };
 
 template <>
 struct GCMethods<JSObject *>
 {
     static JSObject *initial() { return nullptr; }
-    static ThingRootKind kind() { return RootKind<JSObject *>::rootKind(); }
     static bool poisoned(JSObject *v) { return JS::IsPoisonedPtr(v); }
     static bool needsPostBarrier(JSObject *v) {
         return v != nullptr && gc::IsInsideNursery(reinterpret_cast<gc::Cell *>(v));
     }
 #ifdef JSGC_GENERATIONAL
     static void postBarrier(JSObject **vp) {
         JS::HeapCellPostBarrier(reinterpret_cast<js::gc::Cell **>(vp));
     }
@@ -702,17 +700,17 @@ namespace JS {
  */
 template <typename T>
 class MOZ_STACK_CLASS Rooted : public js::RootedBase<T>
 {
     /* Note: CX is a subclass of either ContextFriendFields or PerThreadDataFriendFields. */
     template <typename CX>
     void init(CX *cx) {
 #ifdef JSGC_TRACK_EXACT_ROOTS
-        js::ThingRootKind kind = js::GCMethods<T>::kind();
+        js::ThingRootKind kind = js::RootKind<T>::rootKind();
         this->stack = &cx->thingGCRooters[kind];
         this->prev = *stack;
         *stack = reinterpret_cast<Rooted<void*>*>(this);
 
         MOZ_ASSERT(!js::GCMethods<T>::poisoned(ptr));
 #endif
     }
 
--- a/js/public/Value.h
+++ b/js/public/Value.h
@@ -1545,24 +1545,22 @@ JS_PUBLIC_API(void) HeapValueRelocate(Va
 }
 #endif
 
 namespace js {
 
 template <> struct GCMethods<const JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
-    static ThingRootKind kind() { return THING_ROOT_VALUE; }
     static bool poisoned(const JS::Value &v) { return JS::IsPoisonedValue(v); }
 };
 
 template <> struct GCMethods<JS::Value>
 {
     static JS::Value initial() { return JS::UndefinedValue(); }
-    static ThingRootKind kind() { return THING_ROOT_VALUE; }
     static bool poisoned(const JS::Value &v) { return JS::IsPoisonedValue(v); }
     static bool needsPostBarrier(const JS::Value &v) {
         return v.isObject() && gc::IsInsideNursery(reinterpret_cast<gc::Cell*>(&v.toObject()));
     }
 #ifdef JSGC_GENERATIONAL
     static void postBarrier(JS::Value *v) { JS::HeapValuePostBarrier(v); }
     static void relocate(JS::Value *v) { JS::HeapValueRelocate(v); }
 #endif
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -515,17 +515,17 @@ SelectForGC(JSContext *cx, unsigned argc
      * start to detect missing pre-barriers. It is invalid for nursery things
      * to be in the set, so evict the nursery before adding items.
      */
     JSRuntime *rt = cx->runtime();
     MinorGC(rt, JS::gcreason::EVICT_NURSERY);
 
     for (unsigned i = 0; i < args.length(); i++) {
         if (args[i].isObject()) {
-            if (!rt->gc.selectedForMarking.append(&args[i].toObject()))
+            if (!rt->gc.selectForMarking(&args[i].toObject()))
                 return false;
         }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
@@ -594,17 +594,17 @@ DeterministicGC(JSContext *cx, unsigned 
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 1) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return false;
     }
 
-    gc::SetDeterministicGC(cx, ToBoolean(args[0]));
+    cx->runtime()->gc.setDeterministic(ToBoolean(args[0]));
     args.rval().setUndefined();
     return true;
 }
 #endif /* JS_GC_ZEAL */
 
 static bool
 GCSlice(JSContext *cx, unsigned argc, Value *vp)
 {
@@ -636,33 +636,33 @@ ValidateGC(JSContext *cx, unsigned argc,
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 1) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return false;
     }
 
-    gc::SetValidateGC(cx, ToBoolean(args[0]));
+    cx->runtime()->gc.setValidate(ToBoolean(args[0]));
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 FullCompartmentChecks(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (args.length() != 1) {
         RootedObject callee(cx, &args.callee());
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return false;
     }
 
-    gc::SetFullCompartmentChecks(cx, ToBoolean(args[0]));
+    cx->runtime()->gc.setFullCompartmentChecks(ToBoolean(args[0]));
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 NondeterministicGetWeakMapKeys(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -417,25 +417,21 @@ case "$target" in
             AC_MSG_ERROR([\$(CXX) test failed.  You must have MS VC++ in your path to build.]) )
         AC_LANG_RESTORE
 
         changequote(,)
         _MSVC_VER_FILTER='s|.*[^!-~]([0-9]+\.[0-9]+\.[0-9]+(\.[0-9]+)?).*|\1|p'
         changequote([,])
 
         # Determine compiler version
-        CC_VERSION=`${CC} -v 2>&1 | sed -nre "$_MSVC_VER_FILTER"`
-        _CC_MAJOR_VERSION=`echo ${CC_VERSION} | $AWK -F\. '{ print $1 }'`
-        _CC_MINOR_VERSION=`echo ${CC_VERSION} | $AWK -F\. '{ print $2 }'`
-        _CC_RELEASE=`echo ${CC_VERSION} | $AWK -F\. '{ print $3 }'`
-        _CC_BUILD=`echo ${CC_VERSION} | $AWK -F\. '{ print $4 }'`
-        _MSC_VER=${_CC_MAJOR_VERSION}${_CC_MINOR_VERSION}
-
-        CXX_VERSION=`${CXX} -v 2>&1 | sed -nre "$_MSVC_VER_FILTER"`
-        _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | $AWK -F\. '{ print $1 }'`
+        _CC_MAJOR_VERSION=`echo ${CC_VERSION} | cut -c 1-2`
+        _CC_MINOR_VERSION=`echo ${CC_VERSION} | cut -c 3-4`
+        _MSC_VER=${CC_VERSION}
+
+        _CXX_MAJOR_VERSION=`echo ${CXX_VERSION} | cut -c 1-2`
 
         if test "$_CC_MAJOR_VERSION" != "$_CXX_MAJOR_VERSION"; then
             AC_MSG_ERROR([The major versions of \$CC and \$CXX do not match.])
         fi
 
         AC_DEFINE(_CRT_SECURE_NO_WARNINGS)
         AC_DEFINE(_CRT_NONSTDC_NO_WARNINGS)
 
@@ -1673,17 +1669,21 @@ ia64*-hpux*)
         CFLAGS="$CFLAGS -we4553"
         CXXFLAGS="$CXXFLAGS -we4553"
         LIBS="$LIBS kernel32.lib user32.lib gdi32.lib winmm.lib wsock32.lib advapi32.lib psapi.lib"
         MOZ_DEBUG_LDFLAGS='-DEBUG -DEBUGTYPE:CV'
         WARNINGS_AS_ERRORS='-WX'
         MOZ_OPTIMIZE_FLAGS="-O2"
         MOZ_FIX_LINK_PATHS=
         MOZ_COMPONENT_NSPR_LIBS='$(NSPR_LIBS)'
-        LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
+        # Disable these flags on clang-cl since it doesn't ignore unknown arguments by default, and
+        # autoconf insists on passing $LDFLAGS to the compiler.
+        if test -z "$CLANG_CL"; then
+            LDFLAGS="$LDFLAGS -LARGEADDRESSAWARE -NXCOMPAT"
+        fi
         if test -z "$DEVELOPER_OPTIONS"; then
             LDFLAGS="$LDFLAGS -RELEASE"
         fi
         dnl For profile-guided optimization
         PROFILE_GEN_CFLAGS="-GL"
         PROFILE_GEN_LDFLAGS="-LTCG:PGINSTRUMENT"
         dnl XXX: PGO builds can fail with warnings treated as errors,
         dnl specifically "no profile data available" appears to be
@@ -1753,17 +1753,21 @@ ia64*-hpux*)
         fi
 
         if test -n "$GNU_CC"; then
             CFLAGS="$CFLAGS -mstackrealign -fno-keep-inline-dllexport"
             CXXFLAGS="$CXXFLAGS -mstackrealign -fno-keep-inline-dllexport"
             LDFLAGS="$LDFLAGS -Wl,--large-address-aware"
         else
             DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X86"
-            LDFLAGS="$LDFLAGS -SAFESEH"
+            # Disable this flag on clang-cl since it doesn't ignore unknown arguments by default, and
+            # autoconf insists on passing $LDFLAGS to the compiler.
+            if test -z "$CLANG_CL"; then
+                LDFLAGS="$LDFLAGS -SAFESEH"
+            fi
         fi
 
     	AC_DEFINE(_X86_)
 	;;
     x86_64-*)
         if test -n "$_WIN32_MSVC"; then
             DSO_LDOPTS="$DSO_LDOPTS -MACHINE:X64"
         fi
--- a/js/src/gc/GCRuntime.h
+++ b/js/src/gc/GCRuntime.h
@@ -21,33 +21,16 @@
 
 /* Perform validation of incremental marking in debug builds but not on B2G. */
 #if defined(DEBUG) && !defined(MOZ_B2G)
 #define JS_GC_MARKING_VALIDATION
 #endif
 
 namespace js {
 
-struct ScriptAndCounts
-{
-    /* This structure is stored and marked from the JSRuntime. */
-    JSScript *script;
-    ScriptCounts scriptCounts;
-
-    PCCounts &getPCCounts(jsbytecode *pc) const {
-        return scriptCounts.pcCountsVector[script->pcToOffset(pc)];
-    }
-
-    jit::IonScriptCounts *getIonCounts() const {
-        return scriptCounts.ionCounts;
-    }
-};
-
-typedef Vector<ScriptAndCounts, 0, SystemAllocPolicy> ScriptAndCountsVector;
-
 namespace gc {
 
 typedef Vector<JS::Zone *, 4, SystemAllocPolicy> ZoneVector;
 
 class MarkingValidator;
 class AutoPrepareForTracing;
 
 struct ConservativeGCData
@@ -108,17 +91,20 @@ class CallbackVector : public Vector<Cal
 
 class GCRuntime
 {
   public:
     explicit GCRuntime(JSRuntime *rt);
     bool init(uint32_t maxbytes);
     void finish();
 
-    void setGCZeal(uint8_t zeal, uint32_t frequency);
+    inline int zeal();
+    inline bool upcomingZealousGC();
+    inline bool needZealousGC();
+
     template <typename T> bool addRoot(T *rp, const char *name, JSGCRootType rootType);
     void removeRoot(void *rp);
     void setMarkStackLimit(size_t limit);
 
     bool isHeapBusy() { return heapState != js::Idle; }
     bool isHeapMajorCollecting() { return heapState == js::MajorCollecting; }
     bool isHeapMinorCollecting() { return heapState == js::MinorCollecting; }
     bool isHeapCollecting() { return isHeapMajorCollecting() || isHeapMinorCollecting(); }
@@ -128,24 +114,31 @@ class GCRuntime
     void maybeGC(Zone *zone);
     void minorGC(JS::gcreason::Reason reason);
     void minorGC(JSContext *cx, JS::gcreason::Reason reason);
     void gcIfNeeded(JSContext *cx);
     void collect(bool incremental, int64_t budget, JSGCInvocationKind gckind,
                  JS::gcreason::Reason reason);
     void gcSlice(JSGCInvocationKind gckind, JS::gcreason::Reason reason, int64_t millis);
     void runDebugGC();
+    inline void poke();
 
     void markRuntime(JSTracer *trc, bool useSavedRoots = false);
 
 #ifdef JS_GC_ZEAL
+    const void *addressOfZealMode() { return &zealMode; }
+    void setZeal(uint8_t zeal, uint32_t frequency);
+    void setNextScheduled(uint32_t count);
     void verifyPreBarriers();
     void verifyPostBarriers();
     void maybeVerifyPreBarriers(bool always);
     void maybeVerifyPostBarriers(bool always);
+    bool selectForMarking(JSObject *object);
+    void clearSelectedForMarking();
+    void setDeterministic(bool enable);
 #endif
 
   public:
     // Internal public interface
     void recordNativeStackTop();
 #ifdef JS_THREADSAFE
     void notifyRequestEnd() { conservativeGC.updateForRequestEnd(); }
 #endif
@@ -196,40 +189,65 @@ class GCRuntime
 
 #ifdef DEBUG
     bool isAllocAllowed() { return noGCOrAllocationCheck == 0; }
     void disallowAlloc() { ++noGCOrAllocationCheck; }
     void allowAlloc() {
         JS_ASSERT(!isAllocAllowed());
         --noGCOrAllocationCheck;
     }
+
+    bool isInsideUnsafeRegion() { return inUnsafeRegion != 0; }
+    void enterUnsafeRegion() { ++inUnsafeRegion; }
+    void leaveUnsafeRegion() {
+        JS_ASSERT(inUnsafeRegion > 0);
+        --inUnsafeRegion;
+    }
 #endif
 
     void setAlwaysPreserveCode() { alwaysPreserveCode = true; }
 
     bool isGenerationalGCEnabled() { return generationalDisabled == 0; }
     void disableGenerationalGC();
     void enableGenerationalGC();
 
+    void setGrayRootsTracer(JSTraceDataOp traceOp, void *data);
+    bool addBlackRootsTracer(JSTraceDataOp traceOp, void *data);
+    void removeBlackRootsTracer(JSTraceDataOp traceOp, void *data);
+
+    void setMaxMallocBytes(size_t value);
+    void resetMallocBytes();
+    bool isTooMuchMalloc() const { return mallocBytes <= 0; }
+    void updateMallocCounter(JS::Zone *zone, size_t nbytes);
+    void onTooMuchMalloc();
+
+    void setGCCallback(JSGCCallback callback, void *data);
+    bool addFinalizeCallback(JSFinalizeCallback callback, void *data);
+    void removeFinalizeCallback(JSFinalizeCallback func);
+    JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
+
+    void setValidate(bool enable);
+    void setFullCompartmentChecks(bool enable);
+
 #ifdef JS_GC_ZEAL
     void startVerifyPreBarriers();
     bool endVerifyPreBarriers();
     void startVerifyPostBarriers();
     bool endVerifyPostBarriers();
     void finishVerifier();
 #endif
 
   private:
     // For ArenaLists::allocateFromArenaInline()
     friend class ArenaLists;
     Chunk *pickChunk(Zone *zone, AutoMaybeStartBackgroundAllocation &maybeStartBackgroundAllocation);
 
     inline bool wantBackgroundAllocation() const;
 
-    bool initGCZeal();
+    bool initZeal();
     void requestInterrupt(JS::gcreason::Reason reason);
     bool gcCycle(bool incremental, int64_t budget, JSGCInvocationKind gckind,
                  JS::gcreason::Reason reason);
     void budgetIncrementalGC(int64_t *budget);
     void resetIncrementalGC(const char *reason);
     void incrementalCollectSlice(int64_t budget, JS::gcreason::Reason reason,
                                  JSGCInvocationKind gckind);
     void pushZealSelectedObjects();
@@ -452,25 +470,26 @@ class GCRuntime
      * zone with no incoming cross-compartment pointers. Typically if
      * this happens it signals that an incremental GC is marking too much
      * stuff. At various times we check this counter and, if it has changed, we
      * run an immediate, non-incremental GC to clean up the dead
      * zones. This should happen very rarely.
      */
     unsigned              objectsMarkedInDeadZones;
 
-    bool                  poke;
+    bool                  poked;
 
     volatile js::HeapState heapState;
 
 #ifdef JSGC_GENERATIONAL
     js::Nursery           nursery;
     js::gc::StoreBuffer   storeBuffer;
 #endif
 
+  private:
     /*
      * These options control the zealousness of the GC. The fundamental values
      * are   nextScheduled and gcDebugCompartmentGC. At every allocation,
      *   nextScheduled is decremented. When it reaches zero, we do either a
      * full or a compartmental GC, based on   debugCompartmentGC.
      *
      * At this point, if   zeal_ is one of the types that trigger periodic
      * collection, then   nextScheduled is reset to the value of
@@ -498,82 +517,97 @@ class GCRuntime
     int                   incrementalLimit;
 
     js::Vector<JSObject *, 0, js::SystemAllocPolicy>   selectedForMarking;
 #endif
 
     bool                  validate;
     bool                  fullCompartmentChecks;
 
-    JSGCCallback          gcCallback;
-    void                  *gcCallbackData;
-
-    JS::GCSliceCallback   sliceCallback;
+    Callback<JSGCCallback>  gcCallback;
     CallbackVector<JSFinalizeCallback> finalizeCallbacks;
 
     /*
      * Malloc counter to measure memory pressure for GC scheduling. It runs
      * from   maxMallocBytes down to zero.
      */
     mozilla::Atomic<ptrdiff_t, mozilla::ReleaseAcquire>   mallocBytes;
 
     /*
-     * Whether a GC has been triggered as a result of   mallocBytes falling
+     * Whether a GC has been triggered as a result of mallocBytes falling
      * below zero.
      */
     mozilla::Atomic<bool, mozilla::ReleaseAcquire>   mallocGCTriggered;
 
     /*
      * The trace operations to trace embedding-specific GC roots. One is for
      * tracing through black roots and the other is for tracing through gray
      * roots. The black/gray distinction is only relevant to the cycle
      * collector.
      */
     CallbackVector<JSTraceDataOp> blackRootTracers;
     Callback<JSTraceDataOp> grayRootTracer;
 
-    /*
-     * The GC can only safely decommit memory when the page size of the
-     * running process matches the compiled arena size.
-     */
-    size_t                systemPageSize;
-
-    /* The OS allocation granularity may not match the page size. */
-    size_t                systemAllocGranularity;
-
-    /* Strong references on scripts held for PCCount profiling API. */
-    js::ScriptAndCountsVector *scriptAndCountsVector;
-
 #ifdef DEBUG
     /*
      * Some regions of code are hard for the static rooting hazard analysis to
      * understand. In those cases, we trade the static analysis for a dynamic
      * analysis. When this is non-zero, we should assert if we trigger, or
      * might trigger, a GC.
      */
     int inUnsafeRegion;
 #endif
 
-  private:
     /* Always preserve JIT code during GCs, for testing. */
     bool                  alwaysPreserveCode;
 
 #ifdef DEBUG
     size_t                noGCOrAllocationCheck;
 #endif
 
     /* Synchronize GC heap access between main thread and GCHelperState. */
     PRLock                *lock;
     mozilla::DebugOnly<PRThread *>   lockOwner;
 
     GCHelperState helperState;
 
     ConservativeGCData conservativeGC;
 
-    //friend class js::gc::Chunk; // todo: remove
     friend class js::GCHelperState;
     friend class js::gc::MarkingValidator;
 };
 
+#ifdef JS_GC_ZEAL
+inline int
+GCRuntime::zeal() {
+    return zealMode;
+}
+
+inline bool
+GCRuntime::upcomingZealousGC() {
+    return nextScheduled == 1;
+}
+
+inline bool
+GCRuntime::needZealousGC() {
+    if (nextScheduled > 0 && --nextScheduled == 0) {
+        if (zealMode == ZealAllocValue ||
+            zealMode == ZealGenerationalGCValue ||
+            (zealMode >= ZealIncrementalRootsThenFinish &&
+             zealMode <= ZealIncrementalMultipleSlices))
+        {
+            nextScheduled = zealFrequency;
+        }
+        return true;
+    }
+    return false;
+}
+#else
+inline int GCRuntime::zeal() { return 0; }
+inline bool GCRuntime::upcomingZealousGC() { return false; }
+inline bool GCRuntime::needZealousGC() { return false; }
+#endif
+
+
 } /* namespace gc */
 } /* namespace js */
 
 #endif
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -102,17 +102,17 @@ js::Nursery::enable()
 {
     JS_ASSERT(isEmpty());
     if (isEnabled())
         return;
     numActiveChunks_ = 1;
     setCurrentChunk(0);
     currentStart_ = position();
 #ifdef JS_GC_ZEAL
-    if (runtime()->gc.zealMode == ZealGenerationalGCValue)
+    if (runtime()->gcZeal() == ZealGenerationalGCValue)
         enterZealMode();
 #endif
 }
 
 void
 js::Nursery::disable()
 {
     JS_ASSERT(isEmpty());
@@ -124,17 +124,17 @@ js::Nursery::disable()
 }
 
 bool
 js::Nursery::isEmpty() const
 {
     JS_ASSERT(runtime_);
     if (!isEnabled())
         return true;
-    JS_ASSERT_IF(runtime_->gc.zealMode != ZealGenerationalGCValue, currentStart_ == start());
+    JS_ASSERT_IF(runtime_->gcZeal() != ZealGenerationalGCValue, currentStart_ == start());
     return position() == currentStart_;
 }
 
 #ifdef JS_GC_ZEAL
 void
 js::Nursery::enterZealMode() {
     if (isEnabled())
         numActiveChunks_ = NumNurseryChunks;
@@ -925,17 +925,17 @@ void
 js::Nursery::sweep()
 {
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
     JS_POISON((void *)start(), JS_SWEPT_NURSERY_PATTERN, NurserySize);
     for (int i = 0; i < NumNurseryChunks; ++i)
         initChunk(i);
 
-    if (runtime()->gc.zealMode == ZealGenerationalGCValue) {
+    if (runtime()->gcZeal() == ZealGenerationalGCValue) {
         MOZ_ASSERT(numActiveChunks_ == NumNurseryChunks);
 
         /* Only reset the alloc point when we are close to the end. */
         if (currentChunk_ + 1 == NumNurseryChunks)
             setCurrentChunk(0);
     } else
 #endif
     {
@@ -950,26 +950,26 @@ js::Nursery::sweep()
     /* Set current start position for isEmpty checks. */
     currentStart_ = position();
 }
 
 void
 js::Nursery::growAllocableSpace()
 {
 #ifdef JS_GC_ZEAL
-    MOZ_ASSERT_IF(runtime()->gc.zealMode == ZealGenerationalGCValue,
+    MOZ_ASSERT_IF(runtime()->gcZeal() == ZealGenerationalGCValue,
                   numActiveChunks_ == NumNurseryChunks);
 #endif
     numActiveChunks_ = Min(numActiveChunks_ * 2, NumNurseryChunks);
 }
 
 void
 js::Nursery::shrinkAllocableSpace()
 {
 #ifdef JS_GC_ZEAL
-    if (runtime()->gc.zealMode == ZealGenerationalGCValue)
+    if (runtime()->gcZeal() == ZealGenerationalGCValue)
         return;
 #endif
     numActiveChunks_ = Max(numActiveChunks_ - 1, 1);
     updateDecommittedRegion();
 }
 
 #endif /* JSGC_GENERATIONAL */
--- a/js/src/gc/RootMarking.cpp
+++ b/js/src/gc/RootMarking.cpp
@@ -725,18 +725,18 @@ js::gc::GCRuntime::markRuntime(JSTracer 
                 MarkScriptRoot(trc, reinterpret_cast<JSScript **>(key), name);
             else
                 MOZ_ASSUME_UNREACHABLE("unexpected js::RootInfo::type value");
         }
     }
 
     MarkPersistentRootedChains(trc);
 
-    if (scriptAndCountsVector) {
-        ScriptAndCountsVector &vec = *scriptAndCountsVector;
+    if (rt->scriptAndCountsVector) {
+        ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
         for (size_t i = 0; i < vec.length(); i++)
             MarkScriptRoot(trc, &vec[i].script, "scriptAndCountsVector");
     }
 
     if (!rt->isBeingDestroyed() && !trc->runtime()->isHeapMinorCollecting()) {
         if (!IS_GC_MARKING_TRACER(trc) || rt->atomsCompartment()->zone()->isCollecting()) {
             MarkPermanentAtoms(trc);
             MarkAtoms(trc);
--- a/js/src/gc/Statistics.cpp
+++ b/js/src/gc/Statistics.cpp
@@ -441,17 +441,18 @@ Statistics::Statistics(JSRuntime *rt)
     fp(nullptr),
     fullFormat(false),
     gcDepth(0),
     collectedCount(0),
     zoneCount(0),
     compartmentCount(0),
     nonincrementalReason(nullptr),
     preBytes(0),
-    phaseNestingDepth(0)
+    phaseNestingDepth(0),
+    sliceCallback(nullptr)
 {
     PodArrayZero(phaseTotals);
     PodArrayZero(counts);
 
     char *env = getenv("MOZ_GCTIMER");
     if (!env || strcmp(env, "none") == 0) {
         fp = nullptr;
         return;
@@ -484,16 +485,23 @@ Statistics::~Statistics()
             }
         }
 
         if (fp != stdout && fp != stderr)
             fclose(fp);
     }
 }
 
+JS::GCSliceCallback
+Statistics::setSliceCallback(JS::GCSliceCallback newCallback) {
+    JS::GCSliceCallback oldCallback = sliceCallback;
+    sliceCallback = newCallback;
+    return oldCallback;
+}
+
 void
 Statistics::printStats()
 {
     if (fullFormat) {
         StatisticsSerializer ss(StatisticsSerializer::AsText);
         formatData(ss, 0);
         char *msg = ss.finishCString();
         if (msg) {
@@ -576,19 +584,19 @@ Statistics::beginSlice(int collectedCoun
     (void) slices.append(data); /* Ignore any OOMs here. */
 
     if (JSAccumulateTelemetryDataCallback cb = runtime->telemetryCallback)
         (*cb)(JS_TELEMETRY_GC_REASON, reason);
 
     // Slice callbacks should only fire for the outermost level
     if (++gcDepth == 1) {
         bool wasFullGC = collectedCount == zoneCount;
-        if (JS::GCSliceCallback cb = runtime->gc.sliceCallback)
-            (*cb)(runtime, first ? JS::GC_CYCLE_BEGIN : JS::GC_SLICE_BEGIN,
-                  JS::GCDescription(!wasFullGC));
+        if (sliceCallback)
+            (*sliceCallback)(runtime, first ? JS::GC_CYCLE_BEGIN : JS::GC_SLICE_BEGIN,
+                             JS::GCDescription(!wasFullGC));
     }
 }
 
 void
 Statistics::endSlice()
 {
     slices.back().end = PRMJ_Now();
     slices.back().endFaults = SystemPageAllocator::GetPageFaultCount();
@@ -600,19 +608,19 @@ Statistics::endSlice()
 
     bool last = runtime->gc.incrementalState == gc::NO_INCREMENTAL;
     if (last)
         endGC();
 
     // Slice callbacks should only fire for the outermost level
     if (--gcDepth == 0) {
         bool wasFullGC = collectedCount == zoneCount;
-        if (JS::GCSliceCallback cb = runtime->gc.sliceCallback)
-            (*cb)(runtime, last ? JS::GC_CYCLE_END : JS::GC_SLICE_END,
-                  JS::GCDescription(!wasFullGC));
+        if (sliceCallback)
+            (*sliceCallback)(runtime, last ? JS::GC_CYCLE_END : JS::GC_SLICE_END,
+                             JS::GCDescription(!wasFullGC));
     }
 
     /* Do this after the slice callback since it uses these values. */
     if (last)
         PodArrayZero(counts);
 }
 
 void
--- a/js/src/gc/Statistics.h
+++ b/js/src/gc/Statistics.h
@@ -93,16 +93,18 @@ struct Statistics {
     }
 
     int64_t beginSCC();
     void endSCC(unsigned scc, int64_t start);
 
     jschar *formatMessage();
     jschar *formatJSON(uint64_t timestamp);
 
+    JS::GCSliceCallback setSliceCallback(JS::GCSliceCallback callback);
+
   private:
     JSRuntime *runtime;
 
     int64_t startupTime;
 
     FILE *fp;
     bool fullFormat;
 
@@ -155,16 +157,18 @@ struct Statistics {
     static const size_t MAX_NESTING = 8;
     Phase phaseNesting[MAX_NESTING];
 #endif
     mozilla::DebugOnly<size_t> phaseNestingDepth;
 
     /* Sweep times for SCCs of compartments. */
     Vector<int64_t, 0, SystemAllocPolicy> sccTimes;
 
+    JS::GCSliceCallback sliceCallback;
+
     void beginGC();
     void endGC();
 
     void gcDuration(int64_t *total, int64_t *maxPause);
     void sccDurations(int64_t *total, int64_t *maxPause);
     void printStats();
     bool formatData(StatisticsSerializer &ss, uint64_t timestamp);
 
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -187,16 +187,74 @@ function rsub_object(i) {
     var o = { valueOf: function () { return t; } };
     var x = o - i; /* computed with t == i, not 1000 */
     t = 1000;
     if (uceFault_sub_object(i) || uceFault_sub_object(i))
         assertEq(x, 0);
     return i;
 }
 
+var uceFault_mul_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_mul_number'));
+function rmul_number(i) {
+    var x = 2 * i;
+    if (uceFault_mul_number(i) || uceFault_mul_number(i))
+        assertEq(x, 198  /* = 1 * 99 */);
+    return i;
+}
+
+var uceFault_mul_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_mul_float'));
+function rmul_float(i) {
+    var t = Math.fround(1/3);
+    var fi = Math.fround(i);
+    var x = Math.fround(Math.fround(Math.fround(Math.fround(t * fi) * t) * fi) * t);
+    if (uceFault_mul_float(i) || uceFault_mul_float(i))
+        assertEq(x, 363); /* != 363.0000324547301 (when computed with double multiplications) */
+    return i;
+}
+
+var uceFault_mul_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_mul_object'));
+function rmul_object(i) {
+    var t = i;
+    var o = { valueOf: function () { return t; } };
+    var x = o * i; /* computed with t == i, not 1000 */
+    t = 1000;
+    if (uceFault_mul_object(i) || uceFault_mul_object(i))
+        assertEq(x, 9801);
+    return i;
+}
+
+var uceFault_div_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_div_number'));
+function rdiv_number(i) {
+    var x = 1 / i;
+    if (uceFault_div_number(i) || uceFault_div_number(i))
+        assertEq(x, 0.010101010101010102  /* = 1 / 99 */);
+    return i;
+}
+
+var uceFault_div_float = eval(uneval(uceFault).replace('uceFault', 'uceFault_div_float'));
+function rdiv_float(i) {
+    var t = Math.fround(1/3);
+    var fi = Math.fround(i);
+    var x = Math.fround(Math.fround(Math.fround(Math.fround(t / fi) / t) / fi) / t);
+    if (uceFault_div_float(i) || uceFault_div_float(i))
+        assertEq(x, 0.0003060912131331861); /* != 0.0003060912060598955 (when computed with double divisions) */
+    return i;
+}
+
+var uceFault_div_object = eval(uneval(uceFault).replace('uceFault', 'uceFault_div_object'));
+function rdiv_object(i) {
+    var t = i;
+    var o = { valueOf: function () { return t; } };
+    var x = o / i; /* computed with t == i, not 1000 */
+    t = 1000;
+    if (uceFault_div_object(i) || uceFault_div_object(i))
+        assertEq(x, 1);
+    return i;
+}
+
 var uceFault_mod_number = eval(uneval(uceFault).replace('uceFault', 'uceFault_mod_number'));
 function rmod_number(i) {
     var x = i % 98;
     if (uceFault_mod_number(i) || uceFault_mod_number(i))
         assertEq(x, 1); /* 99 % 98 = 1 */
     return i;
 }
 
@@ -226,16 +284,22 @@ for (i = 0; i < 100; i++) {
     rursh_object(i);
     radd_number(i);
     radd_float(i);
     radd_string(i);
     radd_object(i);
     rsub_number(i);
     rsub_float(i);
     rsub_object(i);
+    rmul_number(i);
+    rmul_float(i);
+    rmul_object(i);
+    rdiv_number(i);
+    rdiv_float(i);
+    rdiv_object(i);
     rmod_number(i);
     rmod_object(i);
 }
 
 // Test that we can refer multiple time to the same recover instruction, as well
 // as chaining recover instructions.
 
 function alignedAlloc($size, $alignment) {
--- a/js/src/jit/CompileWrappers.cpp
+++ b/js/src/jit/CompileWrappers.cpp
@@ -63,17 +63,17 @@ CompileRuntime::addressOfLastCachedNativ
 {
     return &runtime()->nativeIterCache.last;
 }
 
 #ifdef JS_GC_ZEAL
 const void *
 CompileRuntime::addressOfGCZeal()
 {
-    return &runtime()->gc.zealMode;
+    return runtime()->gc.addressOfZealMode();
 }
 #endif
 
 const void *
 CompileRuntime::addressOfInterrupt()
 {
     return &runtime()->interrupt;
 }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -4338,17 +4338,17 @@ class MSub : public MBinaryArithInstruct
 
     bool fallible() const;
     void computeRange(TempAllocator &alloc);
     bool truncate(TruncateKind kind);
     TruncateKind operandTruncateKind(size_t index) const;
 
     bool writeRecoverData(CompactBufferWriter &writer) const;
     bool canRecoverOnBailout() const {
-        return specialization_ != MIRType_None;
+        return specialization_ < MIRType_Object;
     }
 };
 
 class MMul : public MBinaryArithInstruction
 {
   public:
     enum Mode {
         Normal,
@@ -4435,16 +4435,21 @@ class MMul : public MBinaryArithInstruct
 
     bool isFloat32Commutative() const { return true; }
 
     void computeRange(TempAllocator &alloc);
     bool truncate(TruncateKind kind);
     TruncateKind operandTruncateKind(size_t index) const;
 
     Mode mode() const { return mode_; }
+
+    bool writeRecoverData(CompactBufferWriter &writer) const;
+    bool canRecoverOnBailout() const {
+        return specialization_ < MIRType_Object;
+    }
 };
 
 class MDiv : public MBinaryArithInstruction
 {
     bool canBeNegativeZero_;
     bool canBeNegativeOverflow_;
     bool canBeDivideByZero_;
     bool canBeNegativeDividend_;
@@ -4531,16 +4536,21 @@ class MDiv : public MBinaryArithInstruct
 
     bool isFloat32Commutative() const { return true; }
 
     void computeRange(TempAllocator &alloc);
     bool fallible() const;
     bool truncate(TruncateKind kind);
     void collectRangeInfoPreTrunc();
     TruncateKind operandTruncateKind(size_t index) const;
+
+    bool writeRecoverData(CompactBufferWriter &writer) const;
+    bool canRecoverOnBailout() const {
+        return specialization_ < MIRType_Object;
+    }
 };
 
 class MMod : public MBinaryArithInstruction
 {
     bool unsigned_;
     bool canBeNegativeDividend_;
 
     MMod(MDefinition *left, MDefinition *right, MIRType type)
--- a/js/src/jit/Recover.cpp
+++ b/js/src/jit/Recover.cpp
@@ -359,16 +359,81 @@ RSub::recover(JSContext *cx, SnapshotIte
     // rounded to a Float32.
     if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
         return false;
 
     iter.storeInstructionResult(result);
     return true;
 }
 
+bool
+MMul::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_Mul));
+    writer.writeByte(specialization_ == MIRType_Float32);
+    return true;
+}
+
+RMul::RMul(CompactBufferReader &reader)
+{
+    isFloatOperation_ = reader.readByte();
+}
+
+bool
+RMul::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedValue lhs(cx, iter.read());
+    RootedValue rhs(cx, iter.read());
+    RootedValue result(cx);
+
+    if (!js::MulValues(cx, &lhs, &rhs, &result))
+        return false;
+
+    // MIRType_Float32 is a specialization embedding the fact that the result is
+    // rounded to a Float32.
+    if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
+        return false;
+
+    iter.storeInstructionResult(result);
+    return true;
+}
+
+bool
+MDiv::writeRecoverData(CompactBufferWriter &writer) const
+{
+    MOZ_ASSERT(canRecoverOnBailout());
+    writer.writeUnsigned(uint32_t(RInstruction::Recover_Div));
+    writer.writeByte(specialization_ == MIRType_Float32);
+    return true;
+}
+
+RDiv::RDiv(CompactBufferReader &reader)
+{
+    isFloatOperation_ = reader.readByte();
+}
+
+bool
+RDiv::recover(JSContext *cx, SnapshotIterator &iter) const
+{
+    RootedValue lhs(cx, iter.read());
+    RootedValue rhs(cx, iter.read());
+    RootedValue result(cx);
+
+    if (!js::DivValues(cx, &lhs, &rhs, &result))
+        return false;
+
+    // MIRType_Float32 is a specialization embedding the fact that the result is
+    // rounded to a Float32.
+    if (isFloatOperation_ && !RoundFloat32(cx, result, &result))
+        return false;
+
+    iter.storeInstructionResult(result);
+    return true;
+}
 
 bool
 MMod::writeRecoverData(CompactBufferWriter &writer) const
 {
 	MOZ_ASSERT(canRecoverOnBailout());
 	writer.writeUnsigned(uint32_t(RInstruction::Recover_Mod));
 	return true;
 }
--- a/js/src/jit/Recover.h
+++ b/js/src/jit/Recover.h
@@ -21,16 +21,18 @@ namespace jit {
     _(BitNot)                                   \
     _(BitOr)                                    \
     _(BitXor)                                   \
     _(Lsh)                                      \
     _(Rsh)                                      \
     _(Ursh)                                     \
     _(Add)                                      \
     _(Sub)                                      \
+    _(Mul)                                      \
+    _(Div)                                      \
     _(Mod)                                      \
     _(NewObject)                                \
     _(NewDerivedTypedObject)
 
 class RResumePoint;
 class SnapshotIterator;
 
 class RInstruction
@@ -205,16 +207,46 @@ class RMod MOZ_FINAL : public RInstructi
 
     virtual uint32_t numOperands() const {
         return 2;
     }
 
     bool recover(JSContext *cx, SnapshotIterator &iter) const;
 };
 
+class RMul MOZ_FINAL : public RInstruction
+{
+  private:
+    bool isFloatOperation_;
+
+  public:
+    RINSTRUCTION_HEADER_(Mul)
+
+    virtual uint32_t numOperands() const {
+        return 2;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
+class RDiv MOZ_FINAL : public RInstruction
+{
+  private:
+    bool isFloatOperation_;
+
+  public:
+    RINSTRUCTION_HEADER_(Div)
+
+    virtual uint32_t numOperands() const {
+        return 2;
+    }
+
+    bool recover(JSContext *cx, SnapshotIterator &iter) const;
+};
+
 class RNewObject MOZ_FINAL : public RInstruction
 {
   private:
     bool templateObjectIsClassPrototype_;
 
   public:
     RINSTRUCTION_HEADER_(NewObject)
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1610,30 +1610,23 @@ JS::RemoveScriptRootRT(JSRuntime *rt, JS
 {
     RemoveRoot(rt, (void *)rp);
     *rp = nullptr;
 }
 
 JS_PUBLIC_API(bool)
 JS_AddExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
-    AssertHeapIsIdle(rt);
-    return !!rt->gc.blackRootTracers.append(Callback<JSTraceDataOp>(traceOp, data));
+    return rt->gc.addBlackRootsTracer(traceOp, data);
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveExtraGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
-    for (size_t i = 0; i < rt->gc.blackRootTracers.length(); i++) {
-        Callback<JSTraceDataOp> *e = &rt->gc.blackRootTracers[i];
-        if (e->op == traceOp && e->data == data) {
-            rt->gc.blackRootTracers.erase(e);
-            break;
-        }
-    }
+    return rt->gc.removeBlackRootsTracer(traceOp, data);
 }
 
 #ifdef DEBUG
 
 typedef struct JSHeapDumpNode JSHeapDumpNode;
 
 struct JSHeapDumpNode {
     void            *thing;
@@ -1894,38 +1887,30 @@ JS_MaybeGC(JSContext *cx)
 {
     MaybeGC(cx);
 }
 
 JS_PUBLIC_API(void)
 JS_SetGCCallback(JSRuntime *rt, JSGCCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
-    rt->gc.gcCallback = cb;
-    rt->gc.gcCallbackData = data;
+    rt->gc.setGCCallback(cb, data);
 }
 
 JS_PUBLIC_API(bool)
 JS_AddFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb, void *data)
 {
     AssertHeapIsIdle(rt);
-    return rt->gc.finalizeCallbacks.append(Callback<JSFinalizeCallback>(cb, data));
+    return rt->gc.addFinalizeCallback(cb, data);
 }
 
 JS_PUBLIC_API(void)
 JS_RemoveFinalizeCallback(JSRuntime *rt, JSFinalizeCallback cb)
 {
-    for (Callback<JSFinalizeCallback> *p = rt->gc.finalizeCallbacks.begin();
-         p < rt->gc.finalizeCallbacks.end(); p++)
-    {
-        if (p->op == cb) {
-            rt->gc.finalizeCallbacks.erase(p);
-            break;
-        }
-    }
+    rt->gc.removeFinalizeCallback(cb);
 }
 
 JS_PUBLIC_API(bool)
 JS_IsAboutToBeFinalized(JS::Heap<JSObject *> *objp)
 {
     return IsObjectAboutToBeFinalized(objp->unsafeGet());
 }
 
@@ -1940,17 +1925,17 @@ JS_SetGCParameter(JSRuntime *rt, JSGCPar
 {
     switch (key) {
       case JSGC_MAX_BYTES: {
         JS_ASSERT(value >= rt->gc.bytes);
         rt->gc.maxBytes = value;
         break;
       }
       case JSGC_MAX_MALLOC_BYTES:
-        rt->setGCMaxMallocBytes(value);
+        rt->gc.setMaxMallocBytes(value);
         break;
       case JSGC_SLICE_TIME_BUDGET:
         rt->gc.sliceBudget = SliceBudget::TimeBudget(value);
         break;
       case JSGC_MARK_STACK_LIMIT:
         js::SetMarkStackLimit(rt, value);
         break;
       case JSGC_HIGH_FREQUENCY_TIME_LIMIT:
@@ -6215,23 +6200,23 @@ JS_AbortIfWrongThread(JSRuntime *rt)
     if (!js::TlsPerThreadData.get()->associatedWith(rt))
         MOZ_CRASH();
 }
 
 #ifdef JS_GC_ZEAL
 JS_PUBLIC_API(void)
 JS_SetGCZeal(JSContext *cx, uint8_t zeal, uint32_t frequency)
 {
-    SetGCZeal(cx->runtime(), zeal, frequency);
+    cx->runtime()->gc.setZeal(zeal, frequency);
 }
 
 JS_PUBLIC_API(void)
 JS_ScheduleGC(JSContext *cx, uint32_t count)
 {
-    cx->runtime()->gc.nextScheduled = count;
+    cx->runtime()->gc.setNextScheduled(count);
 }
 #endif
 
 JS_PUBLIC_API(void)
 JS_SetParallelParsingEnabled(JSRuntime *rt, bool enabled)
 {
 #ifdef JS_ION
     rt->setParallelParsingEnabled(enabled);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -3004,17 +3004,16 @@ class MutablePropertyDescriptorOperation
 
 } /* namespace JS */
 
 namespace js {
 
 template <>
 struct GCMethods<JSPropertyDescriptor> {
     static JSPropertyDescriptor initial() { return JSPropertyDescriptor(); }
-    static ThingRootKind kind() { return THING_ROOT_PROPERTY_DESCRIPTOR; }
     static bool poisoned(const JSPropertyDescriptor &desc) {
         return (desc.obj && JS::IsPoisonedPtr(desc.obj)) ||
                (desc.attrs & JSPROP_GETTER && desc.getter && JS::IsPoisonedPtr(desc.getter)) ||
                (desc.attrs & JSPROP_SETTER && desc.setter && JS::IsPoisonedPtr(desc.setter)) ||
                (desc.value.isGCThing() && JS::IsPoisonedPtr(desc.value.toGCThing()));
     }
 };
 
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -58,18 +58,17 @@ JS_FRIEND_API(SourceHook *)
 js::ForgetSourceHook(JSRuntime *rt)
 {
     return rt->sourceHook.forget();
 }
 
 JS_FRIEND_API(void)
 JS_SetGrayGCRootsTracer(JSRuntime *rt, JSTraceDataOp traceOp, void *data)
 {
-    rt->gc.grayRootTracer.op = traceOp;
-    rt->gc.grayRootTracer.data = data;
+    rt->gc.setGrayRootsTracer(traceOp, data);
 }
 
 JS_FRIEND_API(JSString *)
 JS_GetAnonymousString(JSRuntime *rt)
 {
     JS_ASSERT(rt->hasContexts());
     return rt->commonNames->anonymous;
 }
@@ -859,19 +858,17 @@ JS_FRIEND_API(bool)
 js::IsContextRunningJS(JSContext *cx)
 {
     return cx->currentlyRunning();
 }
 
 JS_FRIEND_API(JS::GCSliceCallback)
 JS::SetGCSliceCallback(JSRuntime *rt, GCSliceCallback callback)
 {
-    JS::GCSliceCallback old = rt->gc.sliceCallback;
-    rt->gc.sliceCallback = callback;
-    return old;
+    return rt->gc.setSliceCallback(callback);
 }
 
 JS_FRIEND_API(bool)
 JS::WasIncrementalGC(JSRuntime *rt)
 {
     return rt->gc.isIncremental;
 }
 
@@ -1021,17 +1018,17 @@ JS_FRIEND_API(void)
 JS::IncrementalValueBarrier(const Value &v)
 {
     js::HeapValue::writeBarrierPre(v);
 }
 
 JS_FRIEND_API(void)
 JS::PokeGC(JSRuntime *rt)
 {
-    rt->gc.poke = true;
+    rt->gc.poke();
 }
 
 JS_FRIEND_API(JSCompartment *)
 js::GetAnyCompartmentInZone(JS::Zone *zone)
 {
     CompartmentsInZoneIter comp(zone);
     JS_ASSERT(!comp.done());
     return comp.get();
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1090,59 +1090,50 @@ GCRuntime::GCRuntime(JSRuntime *rt) :
     markingValidator(nullptr),
 #endif
     interFrameGC(0),
     sliceBudget(SliceBudget::Unlimited),
     incrementalEnabled(true),
     generationalDisabled(0),
     manipulatingDeadZones(false),
     objectsMarkedInDeadZones(0),
-    poke(false),
+    poked(false),
     heapState(Idle),
 #ifdef JSGC_GENERATIONAL
     nursery(rt),
     storeBuffer(rt, nursery),
 #endif
 #ifdef JS_GC_ZEAL
     zealMode(0),
     zealFrequency(0),
     nextScheduled(0),
     deterministicOnly(false),
     incrementalLimit(0),
 #endif
     validate(true),
     fullCompartmentChecks(false),
-    gcCallback(nullptr),
-    sliceCallback(nullptr),
     mallocBytes(0),
     mallocGCTriggered(false),
-    scriptAndCountsVector(nullptr),
 #ifdef DEBUG
     inUnsafeRegion(0),
 #endif
     alwaysPreserveCode(false),
 #ifdef DEBUG
     noGCOrAllocationCheck(0),
 #endif
     lock(nullptr),
     lockOwner(nullptr),
     helperState(rt)
 {
 }
 
 #ifdef JS_GC_ZEAL
 
-extern void
-js::SetGCZeal(JSRuntime *rt, uint8_t zeal, uint32_t frequency)
-{
-    rt->gc.setGCZeal(zeal, frequency);
-}
-
 void
-GCRuntime::setGCZeal(uint8_t zeal, uint32_t frequency)
+GCRuntime::setZeal(uint8_t zeal, uint32_t frequency)
 {
     if (verifyPreData)
         VerifyBarriers(rt, PreBarrierVerifier);
     if (verifyPostData)
         VerifyBarriers(rt, PostBarrierVerifier);
 
 #ifdef JSGC_GENERATIONAL
     if (zealMode == ZealGenerationalGCValue) {
@@ -1155,18 +1146,24 @@ GCRuntime::setGCZeal(uint8_t zeal, uint3
 #endif
 
     bool schedule = zeal >= js::gc::ZealAllocValue;
     zealMode = zeal;
     zealFrequency = frequency;
     nextScheduled = schedule ? frequency : 0;
 }
 
+void
+GCRuntime::setNextScheduled(uint32_t count)
+{
+    nextScheduled = count;
+}
+
 bool
-GCRuntime::initGCZeal()
+GCRuntime::initZeal()
 {
     const char *env = getenv("JS_GC_ZEAL");
     if (!env)
         return true;
 
     int zeal = -1;
     int frequency = JS_DEFAULT_ZEAL_FREQ;
     if (strcmp(env, "help") != 0) {
@@ -1192,17 +1189,17 @@ GCRuntime::initGCZeal()
                 "  9: Incremental GC in two slices: 1) mark all 2) new marking and finish\n"
                 " 10: Incremental GC in multiple slices\n"
                 " 11: Verify post write barriers between instructions\n"
                 " 12: Verify post write barriers between paints\n"
                 " 13: Purge analysis state every F allocations (default: 100)\n");
         return false;
     }
 
-    setGCZeal(zeal, frequency);
+    setZeal(zeal, frequency);
     return true;
 }
 
 #endif
 
 /* Lifetime for type sets attached to scripts containing observed types. */
 static const int64_t JIT_SCRIPT_RELEASE_TYPES_INTERVAL = 60 * 1000 * 1000;
 
@@ -1224,32 +1221,32 @@ GCRuntime::init(uint32_t maxbytes)
     if (!helperState.init())
         return false;
 
     /*
      * Separate gcMaxMallocBytes from gcMaxBytes but initialize to maxbytes
      * for default backward API compatibility.
      */
     maxBytes = maxbytes;
-    rt->setGCMaxMallocBytes(maxbytes);
+    setMaxMallocBytes(maxbytes);
 
 #ifndef JS_MORE_DETERMINISTIC
     jitReleaseTime = PRMJ_Now() + JIT_SCRIPT_RELEASE_TYPES_INTERVAL;
 #endif
 
 #ifdef JSGC_GENERATIONAL
     if (!nursery.init())
         return false;
 
     if (!storeBuffer.enable())
         return false;
 #endif
 
 #ifdef JS_GC_ZEAL
-    if (!initGCZeal())
+    if (!initZeal())
         return false;
 #endif
 
     if (!marker.init(mode))
         return false;
 
     return true;
 }
@@ -1324,16 +1321,73 @@ js::gc::FinishPersistentRootedChains(JSR
     rt->stringPersistentRooteds.clear();
     rt->valuePersistentRooteds.clear();
 }
 
 template <typename T> struct BarrierOwner {};
 template <typename T> struct BarrierOwner<T *> { typedef T result; };
 template <> struct BarrierOwner<Value> { typedef HeapValue result; };
 
+bool
+GCRuntime::addBlackRootsTracer(JSTraceDataOp traceOp, void *data)
+{
+    AssertHeapIsIdle(rt);
+    return !!blackRootTracers.append(Callback<JSTraceDataOp>(traceOp, data));
+}
+
+void
+GCRuntime::removeBlackRootsTracer(JSTraceDataOp traceOp, void *data)
+{
+    // Can be called from finalizers
+    for (size_t i = 0; i < blackRootTracers.length(); i++) {
+        Callback<JSTraceDataOp> *e = &blackRootTracers[i];
+        if (e->op == traceOp && e->data == data) {
+            blackRootTracers.erase(e);
+        }
+    }
+}
+
+void
+GCRuntime::setGrayRootsTracer(JSTraceDataOp traceOp, void *data)
+{
+    AssertHeapIsIdle(rt);
+    grayRootTracer.op = traceOp;
+    grayRootTracer.data = data;
+}
+
+void
+GCRuntime::setGCCallback(JSGCCallback callback, void *data)
+{
+    gcCallback.op = callback;
+    gcCallback.data = data;
+}
+
+bool
+GCRuntime::addFinalizeCallback(JSFinalizeCallback callback, void *data)
+{
+    return finalizeCallbacks.append(Callback<JSFinalizeCallback>(callback, data));
+}
+
+void
+GCRuntime::removeFinalizeCallback(JSFinalizeCallback callback)
+{
+    for (Callback<JSFinalizeCallback> *p = finalizeCallbacks.begin();
+         p < finalizeCallbacks.end(); p++) {
+        if (p->op == callback) {
+            finalizeCallbacks.erase(p);
+            break;
+        }
+    }
+}
+
+JS::GCSliceCallback
+GCRuntime::setSliceCallback(JS::GCSliceCallback callback) {
+    return stats.setSliceCallback(callback);
+}
+
 template <typename T>
 bool
 GCRuntime::addRoot(T *rp, const char *name, JSGCRootType rootType)
 {
     /*
      * Sometimes Firefox will hold weak references to objects and then convert
      * them to strong references by calling AddRoot (e.g., via PreserveWrapper,
      * or ModifyBusyCount in workers). We need a read barrier to cover these
@@ -1344,17 +1398,17 @@ GCRuntime::addRoot(T *rp, const char *na
 
     return rt->gc.rootsHash.put((void *)rp, RootInfo(name, rootType));
 }
 
 void
 GCRuntime::removeRoot(void *rp)
 {
     rootsHash.remove(rp);
-    poke = true;
+    poke();
 }
 
 template <typename T>
 static bool
 AddRoot(JSRuntime *rt, T *rp, const char *name, JSGCRootType rootType)
 {
     return rt->gc.addRoot(rp, name, rootType);
 }
@@ -1418,19 +1472,52 @@ js::RemoveRawValueRoot(JSContext *cx, Va
 }
 
 void
 js::RemoveRoot(JSRuntime *rt, void *rp)
 {
     rt->gc.removeRoot(rp);
 }
 
-typedef RootedValueMap::Range RootRange;
-typedef RootedValueMap::Entry RootEntry;
-typedef RootedValueMap::Enum RootEnum;
+void
+GCRuntime::setMaxMallocBytes(size_t value)
+{
+    /*
+     * For compatibility treat any value that exceeds PTRDIFF_T_MAX to
+     * mean that value.
+     */
+    maxMallocBytes = (ptrdiff_t(value) >= 0) ? value : size_t(-1) >> 1;
+    resetMallocBytes();
+    for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next())
+        zone->setGCMaxMallocBytes(value);
+}
+
+void
+GCRuntime::resetMallocBytes()
+{
+    mallocBytes = ptrdiff_t(maxMallocBytes);
+    mallocGCTriggered = false;
+}
+
+void
+GCRuntime::updateMallocCounter(JS::Zone *zone, size_t nbytes)
+{
+    mallocBytes -= ptrdiff_t(nbytes);
+    if (MOZ_UNLIKELY(isTooMuchMalloc()))
+        onTooMuchMalloc();
+    else if (zone)
+        zone->updateMallocCounter(nbytes);
+}
+
+void
+GCRuntime::onTooMuchMalloc()
+{
+    if (!mallocGCTriggered)
+        mallocGCTriggered = triggerGC(JS::gcreason::TOO_MUCH_MALLOC);
+}
 
 static size_t
 ComputeTriggerBytes(Zone *zone, size_t lastBytes, size_t maxBytes, JSGCInvocationKind gckind)
 {
     size_t base = gckind == GC_SHRINK ? lastBytes : Max(lastBytes, zone->runtimeFromMainThread()->gc.allocationThreshold);
     double trigger = double(base) * zone->gcHeapGrowthFactor;
     return size_t(Min(double(maxBytes), trigger));
 }
@@ -4349,26 +4436,26 @@ AutoGCSession::~AutoGCSession()
 #ifndef JS_MORE_DETERMINISTIC
     gc->nextFullGCTime = PRMJ_Now() + GC_IDLE_FULL_SPAN;
 #endif
 
     gc->chunkAllocationSinceLastGC = false;
 
 #ifdef JS_GC_ZEAL
     /* Keeping these around after a GC is dangerous. */
-    gc->selectedForMarking.clearAndFree();
+    gc->clearSelectedForMarking();
 #endif
 
     /* Clear gcMallocBytes for all compartments */
     for (ZonesIter zone(gc->rt, WithAtoms); !zone.done(); zone.next()) {
         zone->resetGCMallocBytes();
         zone->unscheduleGC();
     }
 
-    gc->rt->resetGCMallocBytes();
+    gc->resetMallocBytes();
 }
 
 AutoCopyFreeListToArenas::AutoCopyFreeListToArenas(JSRuntime *rt, ZoneSelector selector)
   : runtime(rt),
     selector(selector)
 {
     for (ZonesIter zone(rt, selector); !zone.done(); zone.next())
         zone->allocator.arenas.copyFreeListsToArenas();
@@ -4691,17 +4778,17 @@ GCRuntime::budgetIncrementalGC(int64_t *
 
     if (mode != JSGC_MODE_INCREMENTAL) {
         resetIncrementalGC("GC mode change");
         *budget = SliceBudget::Unlimited;
         stats.nonincremental("GC mode");
         return;
     }
 
-    if (rt->isTooMuchMalloc()) {
+    if (isTooMuchMalloc()) {
         *budget = SliceBudget::Unlimited;
         stats.nonincremental("malloc bytes trigger");
     }
 
     bool reset = false;
     for (ZonesIter zone(rt, WithAtoms); !zone.done(); zone.next()) {
         if (zone->gcBytes >= zone->gcTriggerBytes) {
             *budget = SliceBudget::Unlimited;
@@ -4890,31 +4977,31 @@ GCRuntime::collect(bool incremental, int
         gcstats::AutoGCSlice agc(stats, collectedCount, zoneCount, compartmentCount, reason);
 
         /*
          * Let the API user decide to defer a GC if it wants to (unless this
          * is the last context). Invoke the callback regardless.
          */
         if (incrementalState == NO_INCREMENTAL) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_BEGIN);
-            if (gcCallback)
-                gcCallback(rt, JSGC_BEGIN, gcCallbackData);
+            if (gcCallback.op)
+                gcCallback.op(rt, JSGC_BEGIN, gcCallback.data);
         }
 
-        poke = false;
+        poked = false;
         bool wasReset = gcCycle(incremental, budget, gckind, reason);
 
         if (incrementalState == NO_INCREMENTAL) {
             gcstats::AutoPhase ap(stats, gcstats::PHASE_GC_END);
-            if (gcCallback)
-                gcCallback(rt, JSGC_END, gcCallbackData);
+            if (gcCallback.op)
+                gcCallback.op(rt, JSGC_END, gcCallback.data);
         }
 
         /* Need to re-schedule all zones for GC. */
-        if (poke && shouldCleanUpEverything)
+        if (poked && shouldCleanUpEverything)
             JS::PrepareForFullGC(rt);
 
         /*
          * This code makes an extra effort to collect compartments that we
          * thought were dead at the start of the GC. See the large comment in
          * beginMarkPhase.
          */
         bool repeatForDeadZone = false;
@@ -4930,17 +5017,17 @@ GCRuntime::collect(bool incremental, int
         }
 
         /*
          * If we reset an existing GC, we need to start a new one. Also, we
          * repeat GCs that happen during shutdown (the gcShouldCleanUpEverything
          * case) until we can be sure that no additional garbage is created
          * (which typically happens if roots are dropped during finalizers).
          */
-        repeat = (poke && shouldCleanUpEverything) || wasReset || repeatForDeadZone;
+        repeat = (poked && shouldCleanUpEverything) || wasReset || repeatForDeadZone;
     } while (repeat);
 
     if (incrementalState == NO_INCREMENTAL) {
 #ifdef JS_THREADSAFE
         EnqueuePendingParseTasksAfterGC(rt);
 #endif
     }
 }
@@ -5290,37 +5377,50 @@ GCRuntime::runDebugGC()
     } else {
         collect(false, SliceBudget::Unlimited, GC_NORMAL, JS::gcreason::DEBUG_GC);
     }
 
 #endif
 }
 
 void
-gc::SetDeterministicGC(JSContext *cx, bool enabled)
-{
-#ifdef JS_GC_ZEAL
-    JSRuntime *rt = cx->runtime();
-    rt->gc.deterministicOnly = enabled;
-#endif
+GCRuntime::setValidate(bool enabled)
+{
+    JS_ASSERT(!isHeapMajorCollecting());
+    validate = enabled;
 }
 
 void
-gc::SetValidateGC(JSContext *cx, bool enabled)
-{
-    JSRuntime *rt = cx->runtime();
-    rt->gc.validate = enabled;
+GCRuntime::setFullCompartmentChecks(bool enabled)
+{
+    JS_ASSERT(!isHeapMajorCollecting());
+    fullCompartmentChecks = enabled;
+}
+
+#ifdef JS_GC_ZEAL
+bool
+GCRuntime::selectForMarking(JSObject *object)
+{
+    JS_ASSERT(!isHeapMajorCollecting());
+    return selectedForMarking.append(object);
 }
 
 void
-gc::SetFullCompartmentChecks(JSContext *cx, bool enabled)
-{
-    JSRuntime *rt = cx->runtime();
-    rt->gc.fullCompartmentChecks = enabled;
-}
+GCRuntime::clearSelectedForMarking()
+{
+    selectedForMarking.clearAndFree();
+}
+
+void
+GCRuntime::setDeterministic(bool enabled)
+{
+    JS_ASSERT(!isHeapMajorCollecting());
+    deterministicOnly = enabled;
+}
+#endif
 
 #ifdef DEBUG
 
 /* Should only be called manually under gdb */
 void PreventGCDuringInteractiveDebug()
 {
     TlsPerThreadData.get()->suppressGC++;
 }
@@ -5369,115 +5469,16 @@ js::ReleaseAllJITCode(FreeOp *fop)
             jit::FinishDiscardBaselineScript(fop, script);
         }
 
         zone->jitZone()->optimizedStubSpace()->free();
     }
 #endif
 }
 
-/*
- * There are three possible PCCount profiling states:
- *
- * 1. None: Neither scripts nor the runtime have count information.
- * 2. Profile: Active scripts have count information, the runtime does not.
- * 3. Query: Scripts do not have count information, the runtime does.
- *
- * When starting to profile scripts, counting begins immediately, with all JIT
- * code discarded and recompiled with counts as necessary. Active interpreter
- * frames will not begin profiling until they begin executing another script
- * (via a call or return).
- *
- * The below API functions manage transitions to new states, according
- * to the table below.
- *
- *                                  Old State
- *                          -------------------------
- * Function                 None      Profile   Query
- * --------
- * StartPCCountProfiling    Profile   Profile   Profile
- * StopPCCountProfiling     None      Query     Query
- * PurgePCCounts            None      None      None
- */
-
-static void
-ReleaseScriptCounts(FreeOp *fop)
-{
-    JSRuntime *rt = fop->runtime();
-    JS_ASSERT(rt->gc.scriptAndCountsVector);
-
-    ScriptAndCountsVector &vec = *rt->gc.scriptAndCountsVector;
-
-    for (size_t i = 0; i < vec.length(); i++)
-        vec[i].scriptCounts.destroy(fop);
-
-    fop->delete_(rt->gc.scriptAndCountsVector);
-    rt->gc.scriptAndCountsVector = nullptr;
-}
-
-JS_FRIEND_API(void)
-js::StartPCCountProfiling(JSContext *cx)
-{
-    JSRuntime *rt = cx->runtime();
-
-    if (rt->profilingScripts)
-        return;
-
-    if (rt->gc.scriptAndCountsVector)
-        ReleaseScriptCounts(rt->defaultFreeOp());
-
-    ReleaseAllJITCode(rt->defaultFreeOp());
-
-    rt->profilingScripts = true;
-}
-
-JS_FRIEND_API(void)
-js::StopPCCountProfiling(JSContext *cx)
-{
-    JSRuntime *rt = cx->runtime();
-
-    if (!rt->profilingScripts)
-        return;
-    JS_ASSERT(!rt->gc.scriptAndCountsVector);
-
-    ReleaseAllJITCode(rt->defaultFreeOp());
-
-    ScriptAndCountsVector *vec = cx->new_<ScriptAndCountsVector>(SystemAllocPolicy());
-    if (!vec)
-        return;
-
-    for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
-        for (ZoneCellIter i(zone, FINALIZE_SCRIPT); !i.done(); i.next()) {
-            JSScript *script = i.get<JSScript>();
-            if (script->hasScriptCounts() && script->types) {
-                ScriptAndCounts sac;
-                sac.script = script;
-                sac.scriptCounts.set(script->releaseScriptCounts());
-                if (!vec->append(sac))
-                    sac.scriptCounts.destroy(rt->defaultFreeOp());
-            }
-        }
-    }
-
-    rt->profilingScripts = false;
-    rt->gc.scriptAndCountsVector = vec;
-}
-
-JS_FRIEND_API(void)
-js::PurgePCCounts(JSContext *cx)
-{
-    JSRuntime *rt = cx->runtime();
-
-    if (!rt->gc.scriptAndCountsVector)
-        return;
-    JS_ASSERT(!rt->profilingScripts);
-
-    ReleaseScriptCounts(rt->defaultFreeOp());
-}
-
 void
 js::PurgeJITCaches(Zone *zone)
 {
 #ifdef JS_ION
     for (ZoneCellIterUnderGC i(zone, FINALIZE_SCRIPT); !i.done(); i.next()) {
         JSScript *script = i.get<JSScript>();
 
         /* Discard Ion caches. */
@@ -5596,74 +5597,72 @@ JS::AssertGCThingMustBeTenured(JSObject 
 {
     JS_ASSERT((!IsNurseryAllocable(obj->tenuredGetAllocKind()) || obj->getClass()->finalize) &&
               obj->isTenured());
 }
 
 JS_FRIEND_API(void)
 js::gc::AssertGCThingHasType(js::gc::Cell *cell, JSGCTraceKind kind)
 {
-#ifdef DEBUG
     JS_ASSERT(cell);
     if (IsInsideNursery(cell))
         JS_ASSERT(kind == JSTRACE_OBJECT);
     else
         JS_ASSERT(MapAllocToTraceKind(cell->tenuredGetAllocKind()) == kind);
-#endif
 }
 
 JS_FRIEND_API(size_t)
 JS::GetGCNumber()
 {
     JSRuntime *rt = js::TlsPerThreadData.get()->runtimeFromMainThread();
     if (!rt)
         return 0;
     return rt->gc.number;
 }
 #endif
 
 #ifdef DEBUG
 JS::AutoAssertOnGC::AutoAssertOnGC()
-  : runtime(nullptr), gcNumber(0)
+  : gc(nullptr), gcNumber(0)
 {
     js::PerThreadData *data = js::TlsPerThreadData.get();
     if (data) {
         /*
          * GC's from off-thread will always assert, so off-thread is implicitly
          * AutoAssertOnGC. We still need to allow AutoAssertOnGC to be used in
          * code that works from both threads, however. We also use this to
          * annotate the off thread run loops.
          */
-        runtime = data->runtimeIfOnOwnerThread();
+        JSRuntime *runtime = data->runtimeIfOnOwnerThread();
         if (runtime) {
-            gcNumber = runtime->gc.number;
-            ++runtime->gc.inUnsafeRegion;
+            gc = &runtime->gc;
+            gcNumber = gc->number;
+            gc->enterUnsafeRegion();
         }
     }
 }
 
 JS::AutoAssertOnGC::AutoAssertOnGC(JSRuntime *rt)
-  : runtime(rt), gcNumber(rt->gc.number)
-{
-    ++rt->gc.inUnsafeRegion;
+  : gc(&rt->gc), gcNumber(rt->gc.number)
+{
+    gc->enterUnsafeRegion();
 }
 
 JS::AutoAssertOnGC::~AutoAssertOnGC()
 {
-    if (runtime) {
-        --runtime->gc.inUnsafeRegion;
-        MOZ_ASSERT(runtime->gc.inUnsafeRegion >= 0);
+    if (gc) {
+        gc->leaveUnsafeRegion();
 
         /*
          * The following backstop assertion should never fire: if we bumped the
          * gcNumber, we should have asserted because inUnsafeRegion was true.
          */
-        MOZ_ASSERT(gcNumber == runtime->gc.number, "GC ran inside an AutoAssertOnGC scope.");
+        MOZ_ASSERT(gcNumber == gc->number, "GC ran inside an AutoAssertOnGC scope.");
     }
 }
 
 /* static */ void
 JS::AutoAssertOnGC::VerifyIsSafeToGC(JSRuntime *rt)
 {
-    if (rt->gc.inUnsafeRegion > 0)
+    if (rt->gc.isInsideUnsafeRegion())
         MOZ_CRASH("[AutoAssertOnGC] possible GC in GC-unsafe region");
 }
 #endif
--- a/js/src/jsgc.h
+++ b/js/src/jsgc.h
@@ -895,21 +895,16 @@ extern void
 PrepareForDebugGC(JSRuntime *rt);
 
 extern void
 MinorGC(JSRuntime *rt, JS::gcreason::Reason reason);
 
 extern void
 MinorGC(JSContext *cx, JS::gcreason::Reason reason);
 
-#ifdef JS_GC_ZEAL
-extern void
-SetGCZeal(JSRuntime *rt, uint8_t zeal, uint32_t frequency);
-#endif
-
 /* Functions for managing cross compartment gray pointers. */
 
 extern void
 DelayCrossCompartmentGrayMarking(JSObject *src);
 
 extern void
 NotifyGCNukeWrapper(JSObject *o);
 
@@ -1156,25 +1151,16 @@ namespace gc {
 
 extern void
 GCIfNeeded(JSContext *cx);
 
 /* Tries to run a GC no matter what (used for GC zeal). */
 void
 RunDebugGC(JSContext *cx);
 
-void
-SetDeterministicGC(JSContext *cx, bool enabled);
-
-void
-SetValidateGC(JSContext *cx, bool enabled);
-
-void
-SetFullCompartmentChecks(JSContext *cx, bool enabled);
-
 /* Wait for the background thread to finish sweeping if it is running. */
 void
 FinishBackgroundFinalize(JSRuntime *rt);
 
 /*
  * Merge all contents of source into target. This can only be used if source is
  * the only compartment in its zone.
  */
--- a/js/src/jsgcinlines.h
+++ b/js/src/jsgcinlines.h
@@ -71,25 +71,25 @@ GetGCThingTraceKind(const void *thing)
     const Cell *cell = static_cast<const Cell *>(thing);
 #ifdef JSGC_GENERATIONAL
     if (IsInsideNursery(cell))
         return JSTRACE_OBJECT;
 #endif
     return MapAllocToTraceKind(cell->tenuredGetAllocKind());
 }
 
-static inline void
-GCPoke(JSRuntime *rt)
+inline void
+GCRuntime::poke()
 {
-    rt->gc.poke = true;
+    poked = true;
 
 #ifdef JS_GC_ZEAL
     /* Schedule a GC to happen "soon" after a GC poke. */
-    if (rt->gcZeal() == js::gc::ZealPokeValue)
-        rt->gc.nextScheduled = 1;
+    if (zealMode == ZealPokeValue)
+        nextScheduled = 1;
 #endif
 }
 
 class ArenaIter
 {
     ArenaHeader *aheader;
     ArenaHeader *remainingHeader;
 
@@ -484,17 +484,17 @@ CheckAllocatorState(ThreadSafeContext *c
     // For testing out of memory conditions
     if (!PossiblyFail()) {
         js_ReportOutOfMemory(cx);
         return false;
     }
 
     if (allowGC) {
 #ifdef JS_GC_ZEAL
-        if (rt->needZealousGC())
+        if (rt->gc.needZealousGC())
             js::gc::RunDebugGC(ncx);
 #endif
 
         if (rt->interrupt) {
             // Invoking the interrupt callback can fail and we can't usefully
             // handle that here. Just check in case we need to collect instead.
             js::gc::GCIfNeeded(ncx);
         }
--- a/js/src/jsinfer.h
+++ b/js/src/jsinfer.h
@@ -66,24 +66,22 @@ template <>
 struct RootKind<TaggedProto>
 {
     static ThingRootKind rootKind() { return THING_ROOT_OBJECT; }
 };
 
 template <> struct GCMethods<const TaggedProto>
 {
     static TaggedProto initial() { return TaggedProto(); }
-    static ThingRootKind kind() { return THING_ROOT_OBJECT; }
     static bool poisoned(const TaggedProto &v) { return IsPoisonedPtr(v.raw()); }
 };
 
 template <> struct GCMethods<TaggedProto>
 {
     static TaggedProto initial() { return TaggedProto(); }
-    static ThingRootKind kind() { return THING_ROOT_OBJECT; }
     static bool poisoned(const TaggedProto &v) { return IsPoisonedPtr(v.raw()); }
 };
 
 template<class Outer>
 class TaggedProtoOperations
 {
     const TaggedProto *value() const {
         return static_cast<const Outer*>(this)->extract();
--- a/js/src/jsinferinlines.h
+++ b/js/src/jsinferinlines.h
@@ -1266,28 +1266,26 @@ JSScript::ensureHasTypes(JSContext *cx)
 }
 
 namespace js {
 
 template <>
 struct GCMethods<const types::Type>
 {
     static types::Type initial() { return types::Type::UnknownType(); }
-    static ThingRootKind kind() { return THING_ROOT_TYPE; }
     static bool poisoned(const types::Type &v) {
         return (v.isTypeObject() && IsPoisonedPtr(v.typeObject()))
             || (v.isSingleObject() && IsPoisonedPtr(v.singleObject()));
     }
 };
 
 template <>
 struct GCMethods<types::Type>
 {
     static types::Type initial() { return types::Type::UnknownType(); }
-    static ThingRootKind kind() { return THING_ROOT_TYPE; }
     static bool poisoned(const types::Type &v) {
         return (v.isTypeObject() && IsPoisonedPtr(v.typeObject()))
             || (v.isSingleObject() && IsPoisonedPtr(v.singleObject()));
     }
 };
 
 } // namespace js
 
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -5269,17 +5269,17 @@ baseops::DeleteGeneric(JSContext *cx, Ha
     if (!shape || proto != obj) {
         /*
          * If no property, or the property comes from a prototype, call the
          * class's delProperty hook, passing succeeded as the result parameter.
          */
         return CallJSDeletePropertyOp(cx, obj->getClass()->delProperty, obj, id, succeeded);
     }
 
-    GCPoke(cx->runtime());
+    cx->runtime()->gc.poke();
 
     if (IsImplicitDenseOrTypedArrayElement(shape)) {
         if (obj->is<TypedArrayObject>()) {
             // Don't delete elements from typed arrays.
             *succeeded = false;
             return true;
         }
 
--- a/js/src/jsopcode.cpp
+++ b/js/src/jsopcode.cpp
@@ -1931,25 +1931,124 @@ js::IsValidBytecodeOffset(JSContext *cx,
     for (BytecodeRange r(cx, script); !r.empty(); r.popFront()) {
         size_t here = r.frontOffset();
         if (here >= offset)
             return here == offset;
     }
     return false;
 }
 
+/*
+ * There are three possible PCCount profiling states:
+ *
+ * 1. None: Neither scripts nor the runtime have count information.
+ * 2. Profile: Active scripts have count information, the runtime does not.
+ * 3. Query: Scripts do not have count information, the runtime does.
+ *
+ * When starting to profile scripts, counting begins immediately, with all JIT
+ * code discarded and recompiled with counts as necessary. Active interpreter
+ * frames will not begin profiling until they begin executing another script
+ * (via a call or return).
+ *
+ * The below API functions manage transitions to new states, according
+ * to the table below.
+ *
+ *                                  Old State
+ *                          -------------------------
+ * Function                 None      Profile   Query
+ * --------
+ * StartPCCountProfiling    Profile   Profile   Profile
+ * StopPCCountProfiling     None      Query     Query
+ * PurgePCCounts            None      None      None
+ */
+
+static void
+ReleaseScriptCounts(FreeOp *fop)
+{
+    JSRuntime *rt = fop->runtime();
+    JS_ASSERT(rt->scriptAndCountsVector);
+
+    ScriptAndCountsVector &vec = *rt->scriptAndCountsVector;
+
+    for (size_t i = 0; i < vec.length(); i++)
+        vec[i].scriptCounts.destroy(fop);
+
+    fop->delete_(rt->scriptAndCountsVector);
+    rt->scriptAndCountsVector = nullptr;
+}
+
+JS_FRIEND_API(void)
+js::StartPCCountProfiling(JSContext *cx)
+{
+    JSRuntime *rt = cx->runtime();
+
+    if (rt->profilingScripts)
+        return;
+
+    if (rt->scriptAndCountsVector)
+        ReleaseScriptCounts(rt->defaultFreeOp());
+
+    ReleaseAllJITCode(rt->defaultFreeOp());
+
+    rt->profilingScripts = true;
+}
+
+JS_FRIEND_API(void)
+js::StopPCCountProfiling(JSContext *cx)
+{
+    JSRuntime *rt = cx->runtime();
+
+    if (!rt->profilingScripts)
+        return;
+    JS_ASSERT(!rt->scriptAndCountsVector);
+
+    ReleaseAllJITCode(rt->defaultFreeOp());
+
+    ScriptAndCountsVector *vec = cx->new_<ScriptAndCountsVector>(SystemAllocPolicy());
+    if (!vec)
+        return;
+
+    for (ZonesIter zone(rt, SkipAtoms); !zone.done(); zone.next()) {
+        for (ZoneCellIter i(zone, FINALIZE_SCRIPT); !i.done(); i.next()) {
+            JSScript *script = i.get<JSScript>();
+            if (script->hasScriptCounts() && script->types) {
+                ScriptAndCounts sac;
+                sac.script = script;
+                sac.scriptCounts.set(script->releaseScriptCounts());
+                if (!vec->append(sac))
+                    sac.scriptCounts.destroy(rt->defaultFreeOp());
+            }
+        }
+    }
+
+    rt->profilingScripts = false;
+    rt->scriptAndCountsVector = vec;
+}
+
+JS_FRIEND_API(void)
+js::PurgePCCounts(JSContext *cx)
+{
+    JSRuntime *rt = cx->runtime();
+
+    if (!rt->scriptAndCountsVector)
+        return;
+    JS_ASSERT(!rt->profilingScripts);
+
+    ReleaseScriptCounts(rt->defaultFreeOp());
+}
+
 JS_FRIEND_API(size_t)
 js::GetPCCountScriptCount(JSContext *cx)
 {
     JSRuntime *rt = cx->runtime();
 
-    if (!rt->gc.scriptAndCountsVector)
+    if (!rt->scriptAndCountsVector)
         return 0;
 
-    return rt->gc.scriptAndCountsVector->length();
+    return rt->scriptAndCountsVector->length();
 }
 
 enum MaybeComma {NO_COMMA, COMMA};
 
 static void
 AppendJSONProperty(StringBuffer &buf, const char *name, MaybeComma comma = COMMA)
 {
     if (comma)
@@ -1974,22 +2073,22 @@ AppendArrayJSONProperties(JSContext *cx,
     }
 }
 
 JS_FRIEND_API(JSString *)
 js::GetPCCountScriptSummary(JSContext *cx, size_t index)
 {
     JSRuntime *rt = cx->runtime();
 
-    if (!rt->gc.scriptAndCountsVector || index >= rt->gc.scriptAndCountsVector->length()) {
+    if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
         return nullptr;
     }
 
-    const ScriptAndCounts &sac = (*rt->gc.scriptAndCountsVector)[index];
+    const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
     RootedScript script(cx, sac.script);
 
     /*
      * OOM on buffer appends here will not be caught immediately, but since
      * StringBuffer uses a ContextAllocPolicy will trigger an exception on the
      * context if they occur, which we'll catch before returning.
      */
     StringBuffer buf(cx);
@@ -2234,22 +2333,22 @@ GetPCCountJSON(JSContext *cx, const Scri
     return !cx->isExceptionPending();
 }
 
 JS_FRIEND_API(JSString *)
 js::GetPCCountScriptContents(JSContext *cx, size_t index)
 {
     JSRuntime *rt = cx->runtime();
 
-    if (!rt->gc.scriptAndCountsVector || index >= rt->gc.scriptAndCountsVector->length()) {
+    if (!rt->scriptAndCountsVector || index >= rt->scriptAndCountsVector->length()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_BUFFER_TOO_SMALL);
         return nullptr;
     }
 
-    const ScriptAndCounts &sac = (*rt->gc.scriptAndCountsVector)[index];
+    const ScriptAndCounts &sac = (*rt->scriptAndCountsVector)[index];
     JSScript *script = sac.script;
 
     StringBuffer buf(cx);
 
     if (!script->functionNonDelazifying() && !script->compileAndGo())
         return buf.finishString();
 
     {
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -268,17 +268,16 @@ class Bindings
 
     static js::ThingRootKind rootKind() { return js::THING_ROOT_BINDINGS; }
     void trace(JSTracer *trc);
 };
 
 template <>
 struct GCMethods<Bindings> {
     static Bindings initial();
-    static ThingRootKind kind() { return THING_ROOT_BINDINGS; }
     static bool poisoned(const Bindings &bindings) {
         return IsPoisonedPtr(static_cast<Shape *>(bindings.callObjShape()));
     }
 };
 
 class ScriptCounts
 {
     friend class ::JSScript;
@@ -1923,16 +1922,31 @@ extern void
 UnmarkScriptData(JSRuntime *rt);
 
 extern void
 SweepScriptData(JSRuntime *rt);
 
 extern void
 FreeScriptData(JSRuntime *rt);
 
+struct ScriptAndCounts
+{
+    /* This structure is stored and marked from the JSRuntime. */
+    JSScript *script;
+    ScriptCounts scriptCounts;
+
+    PCCounts &getPCCounts(jsbytecode *pc) const {
+        return scriptCounts.pcCountsVector[script->pcToOffset(pc)];
+    }
+
+    jit::IonScriptCounts *getIonCounts() const {
+        return scriptCounts.ionCounts;
+    }
+};
+
 struct GSNCache;
 
 jssrcnote *
 GetSrcNote(GSNCache &cache, JSScript *script, jsbytecode *pc);
 
 } /* namespace js */
 
 extern jssrcnote *
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/Casting.h"
 #include "mozilla/CheckedInt.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TypeTraits.h"
 
 #include <ctype.h>
 #include <string.h>
+#include <wchar.h>
 
 #include "jsapi.h"
 #include "jsarray.h"
 #include "jsatom.h"
 #include "jsbool.h"
 #include "jscntxt.h"
 #include "jsgc.h"
 #include "jsnum.h"
@@ -1092,63 +1093,143 @@ struct ManualCmp {
         for (; p != extent; ++p, ++t) {
             if (*p != *t)
                 return false;
         }
         return true;
     }
 };
 
+template <typename TextChar, typename PatChar>
+static const TextChar*
+FirstCharMatcherUnrolled(const TextChar *text, uint32_t n, const PatChar pat)
+{
+    const TextChar *textend = text + n;
+    const TextChar *t = text;
+
+    switch ((textend - t) & 7) {
+        case 0: if (*t++ == pat) return t - 1;
+        case 7: if (*t++ == pat) return t - 1;
+        case 6: if (*t++ == pat) return t - 1;
+        case 5: if (*t++ == pat) return t - 1;
+        case 4: if (*t++ == pat) return t - 1;
+        case 3: if (*t++ == pat) return t - 1;
+        case 2: if (*t++ == pat) return t - 1;
+        case 1: if (*t++ == pat) return t - 1;
+    }
+    while (textend != t) {
+        if (t[0] == pat) return t;
+        if (t[1] == pat) return t + 1;
+        if (t[2] == pat) return t + 2;
+        if (t[3] == pat) return t + 3;
+        if (t[4] == pat) return t + 4;
+        if (t[5] == pat) return t + 5;
+        if (t[6] == pat) return t + 6;
+        if (t[7] == pat) return t + 7;
+        t += 8;
+    }
+    return nullptr;
+}
+
+static const char*
+FirstCharMatcher8bit(const char *text, uint32_t n, const char pat)
+{
+#if  defined(__clang__)
+    return FirstCharMatcherUnrolled<char, char>(text, n, pat);
+#else
+    return reinterpret_cast<const char *>(memchr(text, pat, n));
+#endif
+}
+
+static const jschar *
+FirstCharMatcher16bit (const jschar *text, uint32_t n, const jschar pat)
+{
+    /* Some platforms define wchar_t as signed and others not. */
+#if (WCHAR_MIN == 0 && WCHAR_MAX == UINT16_MAX) || (WCHAR_MIN == INT16_MIN && WCHAR_MAX == INT16_MAX)
+    /*
+     * Wmemchr works the best.
+     * But only possible to use this when,
+     * size of jschar = size of wchar_t.
+     */
+    const wchar_t *wtext = (const wchar_t *) text;
+    const wchar_t wpat = (const wchar_t) pat;
+    return (jschar *) (wmemchr(wtext, wpat, n));
+#elif defined(__clang__)
+    /*
+     * Performance under memchr is horrible in clang.
+     * Hence it is best to use UnrolledMatcher in this case
+     */
+    return FirstCharMatcherUnrolled<jschar, jschar>(text, n, pat);
+#else
+    /*
+     * For linux the best performance is obtained by slightly hacking memchr.
+     * memchr works only on 8bit char but jschar is 16bit. So we treat jschar
+     * in blocks of 8bit and use memchr.
+     */
+
+    const char *text8 = (const char *) text;
+    const char *pat8 = reinterpret_cast<const char *>(&pat);
+
+    JS_ASSERT(n < UINT32_MAX/2);
+    n *= 2;
+
+    uint32_t i = 0;
+    while (i < n) {
+        /* Find the first 8 bits of 16bit character in text. */
+        const char *pos8 = FirstCharMatcher8bit(text8 + i, n - i, pat8[0]);
+        if (pos8 == nullptr)
+            return nullptr;
+        i = static_cast<uint32_t>(pos8 - text8);
+
+        /* Incorrect match if it matches the last 8 bits of 16bit char. */
+        if (i % 2 != 0) {
+            i++;
+            continue;
+        }
+
+        /* Test if last 8 bits match last 8 bits of 16bit char. */
+        if (pat8[1] == text8[i + 1])
+            return (text + (i/2));
+
+        i += 2;
+    }
+    return nullptr;
+#endif
+}
+
 template <class InnerMatch, typename TextChar, typename PatChar>
 static int
-UnrolledMatch(const TextChar *text, uint32_t textLen, const PatChar *pat, uint32_t patLen)
+Matcher(const TextChar *text, uint32_t textlen, const PatChar *pat, uint32_t patlen)
 {
-    JS_ASSERT(patLen > 0);
-    JS_ASSERT(textLen > 0);
-
-    const TextChar *textend = text + textLen - (patLen - 1);
-    const PatChar p0 = *pat;
-    const PatChar *const patNext = pat + 1;
-    const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patLen);
-    uint8_t fixup;
-
-    const TextChar *t = text;
-    switch ((textend - t) & 7) {
-      case 0: if (*t++ == p0) { fixup = 8; goto match; }
-      case 7: if (*t++ == p0) { fixup = 7; goto match; }
-      case 6: if (*t++ == p0) { fixup = 6; goto match; }
-      case 5: if (*t++ == p0) { fixup = 5; goto match; }
-      case 4: if (*t++ == p0) { fixup = 4; goto match; }
-      case 3: if (*t++ == p0) { fixup = 3; goto match; }
-      case 2: if (*t++ == p0) { fixup = 2; goto match; }
-      case 1: if (*t++ == p0) { fixup = 1; goto match; }
-    }
-    while (t != textend) {
-      if (t[0] == p0) { t += 1; fixup = 8; goto match; }
-      if (t[1] == p0) { t += 2; fixup = 7; goto match; }
-      if (t[2] == p0) { t += 3; fixup = 6; goto match; }
-      if (t[3] == p0) { t += 4; fixup = 5; goto match; }
-      if (t[4] == p0) { t += 5; fixup = 4; goto match; }
-      if (t[5] == p0) { t += 6; fixup = 3; goto match; }
-      if (t[6] == p0) { t += 7; fixup = 2; goto match; }
-      if (t[7] == p0) { t += 8; fixup = 1; goto match; }
-        t += 8;
-        continue;
-        do {
-            if (*t++ == p0) {
-              match:
-                if (!InnerMatch::match(patNext, t, extent))
-                    goto failed_match;
-                return t - text - 1;
-            }
-          failed_match:;
-        } while (--fixup > 0);
-    }
-    return -1;
-}
+    const typename InnerMatch::Extent extent = InnerMatch::computeExtent(pat, patlen);
+
+    uint32_t i = 0;
+    uint32_t n = textlen - patlen + 1;
+    while (i < n) {
+        const TextChar *pos;
+
+        if (sizeof(TextChar) == 2 && sizeof(PatChar) == 2)
+            pos = (TextChar *) FirstCharMatcher16bit((jschar *)text + i, n - i, pat[0]);
+        else if (sizeof(TextChar) == 1 && sizeof(PatChar) == 1)
+            pos = (TextChar *) FirstCharMatcher8bit((char *) text + i, n - i, pat[0]);
+        else
+            pos = (TextChar *) FirstCharMatcherUnrolled<TextChar, PatChar>(text + i, n - i, pat[0]);
+
+        if (pos == nullptr)
+            return -1;
+
+        i = static_cast<uint32_t>(pos - text);
+        if (InnerMatch::match(pat + 1, text + i + 1, extent))
+            return i;
+
+        i += 1;
+     }
+     return -1;
+ }
+
 
 template <typename TextChar, typename PatChar>
 static MOZ_ALWAYS_INLINE int
 StringMatch(const TextChar *text, uint32_t textLen, const PatChar *pat, uint32_t patLen)
 {
     if (patLen == 0)
         return 0;
     if (textLen < patLen)
@@ -1193,20 +1274,20 @@ StringMatch(const TextChar *text, uint32
      * speed of memcmp. For small patterns, a simple loop is faster. We also can't
      * use memcmp if one of the strings is TwoByte and the other is Latin1.
      *
      * FIXME: Linux memcmp performance is sad and the manual loop is faster.
      */
     return
 #if !defined(__linux__)
         (patLen > 128 && IsSame<TextChar, PatChar>::value)
-            ? UnrolledMatch<MemCmp<TextChar, PatChar>>(text, textLen, pat, patLen)
+            ? Matcher<MemCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen)
             :
 #endif
-              UnrolledMatch<ManualCmp<TextChar, PatChar>>(text, textLen, pat, patLen);
+              Matcher<ManualCmp<TextChar, PatChar>, TextChar, PatChar>(text, textLen, pat, patLen);
 }
 
 static int32_t
 StringMatch(JSLinearString *text, JSLinearString *pat, uint32_t start = 0)
 {
     MOZ_ASSERT(start <= text->length());
     uint32_t textLen = text->length() - start;
     uint32_t patLen = pat->length();
--- a/js/src/vm/PropDesc.h
+++ b/js/src/vm/PropDesc.h
@@ -331,17 +331,16 @@ class MutablePropDescOperations : public
 
 } /* namespace JS */
 
 namespace js {
 
 template <>
 struct GCMethods<PropDesc> {
     static PropDesc initial() { return PropDesc(); }
-    static ThingRootKind kind() { return THING_ROOT_PROP_DESC; }
     static bool poisoned(const PropDesc &desc) {
         return JS::IsPoisonedPtr(desc.descObj_) ||
                (desc.value_.isGCThing() &&
                 JS::IsPoisonedPtr(desc.value_.toGCThing())) ||
                (desc.get_.isGCThing() &&
                 JS::IsPoisonedPtr(desc.get_.toGCThing())) ||
                (desc.set_.isGCThing() &&
                 JS::IsPoisonedPtr(desc.set_.toGCThing()));
--- a/js/src/vm/Runtime-inl.h
+++ b/js/src/vm/Runtime-inl.h
@@ -51,17 +51,17 @@ NewObjectCache::newObjectFromHit(JSConte
 
     // Do an end run around JSObject::type() to avoid doing AutoUnprotectCell
     // on the templateObj, which is not a GC thing and can't use runtimeFromAnyThread.
     types::TypeObject *type = templateObj->type_;
 
     if (type->shouldPreTenure())
         heap = gc::TenuredHeap;
 
-    if (cx->runtime()->upcomingZealousGC())
+    if (cx->runtime()->gc.upcomingZealousGC())
         return nullptr;
 
     // Trigger an identical allocation to the one that notified us of OOM
     // so that we trigger the right kind of GC automatically.
     if (allowGC) {
         mozilla::DebugOnly<JSObject *> obj =
             js::gc::AllocateObjectForCacheHit<allowGC>(cx, entry->kind, heap);
         JS_ASSERT(!obj);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -176,16 +176,17 @@ JSRuntime::JSRuntime(JSRuntime *parentRu
 #ifdef DEBUG
     activeContext(nullptr),
 #endif
     gc(thisFromCtor()),
     gcInitialized(false),
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     simulatorRuntime_(nullptr),
 #endif
+    scriptAndCountsVector(nullptr),
     NaNValue(DoubleNaNValue()),
     negativeInfinityValue(DoubleValue(NegativeInfinity<double>())),
     positiveInfinityValue(DoubleValue(PositiveInfinity<double>())),
     emptyString(nullptr),
     debugMode(false),
     spsProfiler(thisFromCtor()),
     profilingScripts(false),
     hadOutOfMemory(false),
@@ -690,53 +691,33 @@ JSRuntime::triggerActivityCallback(bool 
      * property and ensures that it remains true in the future.
      */
     AutoSuppressGC suppress(this);
 
     activityCallback(activityCallbackArg, active);
 }
 
 void
-JSRuntime::setGCMaxMallocBytes(size_t value)
-{
-    /*
-     * For compatibility treat any value that exceeds PTRDIFF_T_MAX to
-     * mean that value.
-     */
-    gc.maxMallocBytes = (ptrdiff_t(value) >= 0) ? value : size_t(-1) >> 1;
-    resetGCMallocBytes();
-    for (ZonesIter zone(this, WithAtoms); !zone.done(); zone.next())
-        zone->setGCMaxMallocBytes(value);
-}
-
-void
 JSRuntime::updateMallocCounter(size_t nbytes)
 {
     updateMallocCounter(nullptr, nbytes);
 }
 
 void
 JSRuntime::updateMallocCounter(JS::Zone *zone, size_t nbytes)
 {
-    /* We tolerate any thread races when updating gcMallocBytes. */
-    gc.mallocBytes -= ptrdiff_t(nbytes);
-    if (MOZ_UNLIKELY(gc.mallocBytes <= 0))
-        onTooMuchMalloc();
-    else if (zone)
-        zone->updateMallocCounter(nbytes);
+    gc.updateMallocCounter(zone, nbytes);
 }
 
 JS_FRIEND_API(void)
 JSRuntime::onTooMuchMalloc()
 {
     if (!CurrentThreadCanAccessRuntime(this))
         return;
-
-    if (!gc.mallocGCTriggered)
-        gc.mallocGCTriggered = TriggerGC(this, JS::gcreason::TOO_MUCH_MALLOC);
+    gc.onTooMuchMalloc();
 }
 
 JS_FRIEND_API(void *)
 JSRuntime::onOutOfMemory(void *p, size_t nbytes)
 {
     return onOutOfMemory(p, nbytes, nullptr);
 }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -129,16 +129,18 @@ struct ScopeCoordinateNameCache {
 
     Shape *shape;
     Map map;
 
     ScopeCoordinateNameCache() : shape(nullptr) {}
     void purge();
 };
 
+typedef Vector<ScriptAndCounts, 0, SystemAllocPolicy> ScriptAndCountsVector;
+
 struct EvalCacheEntry
 {
     JSScript *script;
     JSScript *callerScript;
     jsbytecode *pc;
 };
 
 struct EvalCacheLookup
@@ -948,41 +950,17 @@ struct JSRuntime : public JS::shadow::Ru
         gc.marker.setGCMode(mode);
     }
 
     bool isHeapBusy() { return gc.isHeapBusy(); }
     bool isHeapMajorCollecting() { return gc.isHeapMajorCollecting(); }
     bool isHeapMinorCollecting() { return gc.isHeapMinorCollecting(); }
     bool isHeapCollecting() { return gc.isHeapCollecting(); }
 
-#ifdef JS_GC_ZEAL
-    int gcZeal() { return gc.zealMode; }
-
-    bool upcomingZealousGC() {
-        return gc.nextScheduled == 1;
-    }
-
-    bool needZealousGC() {
-        if (gc.nextScheduled > 0 && --gc.nextScheduled == 0) {
-            if (gcZeal() == js::gc::ZealAllocValue ||
-                gcZeal() == js::gc::ZealGenerationalGCValue ||
-                (gcZeal() >= js::gc::ZealIncrementalRootsThenFinish &&
-                 gcZeal() <= js::gc::ZealIncrementalMultipleSlices))
-            {
-                gc.nextScheduled = gc.zealFrequency;
-            }
-            return true;
-        }
-        return false;
-    }
-#else
-    int gcZeal() { return 0; }
-    bool upcomingZealousGC() { return false; }
-    bool needZealousGC() { return false; }
-#endif
+    int gcZeal() { return gc.zeal(); }
 
     void lockGC() {
         assertCanLock(js::GCLock);
         gc.lockGC();
     }
 
     void unlockGC() {
         gc.unlockGC();
@@ -997,16 +975,19 @@ struct JSRuntime : public JS::shadow::Ru
         needsBarrier_ = needs;
     }
 
 #if defined(JS_ARM_SIMULATOR) || defined(JS_MIPS_SIMULATOR)
     js::jit::SimulatorRuntime *simulatorRuntime() const;
     void setSimulatorRuntime(js::jit::SimulatorRuntime *srt);
 #endif
 
+    /* Strong references on scripts held for PCCount profiling API. */
+    js::ScriptAndCountsVector *scriptAndCountsVector;
+
     /* Well-known numbers held for use by this runtime's contexts. */
     const js::Value     NaNValue;
     const js::Value     negativeInfinityValue;
     const js::Value     positiveInfinityValue;
 
     js::PropertyName    *emptyString;
 
     /* List of active contexts sharing this runtime. */
@@ -1271,40 +1252,29 @@ struct JSRuntime : public JS::shadow::Ru
 
     JSRuntime(JSRuntime *parentRuntime);
     ~JSRuntime();
 
     bool init(uint32_t maxbytes);
 
     JSRuntime *thisFromCtor() { return this; }
 
-    void setGCMaxMallocBytes(size_t value);
-
-    void resetGCMallocBytes() {
-        gc.mallocBytes = ptrdiff_t(gc.maxMallocBytes);
-        gc.mallocGCTriggered = false;
-    }
-
     /*
      * Call this after allocating memory held by GC things, to update memory
      * pressure counters or report the OOM error if necessary. If oomError and
      * cx is not null the function also reports OOM error.
      *
      * The function must be called outside the GC lock and in case of OOM error
      * the caller must ensure that no deadlock possible during OOM reporting.
      */
     void updateMallocCounter(size_t nbytes);
     void updateMallocCounter(JS::Zone *zone, size_t nbytes);
 
     void reportAllocationOverflow() { js_ReportAllocationOverflow(nullptr); }
 
-    bool isTooMuchMalloc() const {
-        return gc.mallocBytes <= 0;
-    }
-
     /*
      * The function must be called outside the GC lock.
      */
     JS_FRIEND_API(void) onTooMuchMalloc();
 
     /*
      * This should be called after system malloc/realloc returns nullptr to try
      * to recove some memory or to report an error. Failures in malloc and
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -734,27 +734,27 @@ static void RecordFrameMetrics(nsIFrame*
   // If aScrollFrame is null, we are in a document without a root scroll frame,
   // so it's a xul document. In this case, use the size of the viewport frame.
   nsIFrame* frameForCompositionBoundsCalculation = aScrollFrame ? aScrollFrame : aForFrame;
   nsRect compositionBounds(frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aReferenceFrame),
                            frameForCompositionBoundsCalculation->GetSize());
   metrics.mCompositionBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel)
                                             * layoutToParentLayerScale);
 
-
   // For the root scroll frame of the root content document, the above calculation
   // will yield the size of the viewport frame as the composition bounds, which
   // doesn't actually correspond to what is visible when
   // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible area of
   // the prescontext that the viewport frame is reflowed into. In that case if our
   // document has a widget then the widget's bounds will correspond to what is
   // visible. If we don't have a widget the root view's bounds correspond to what
   // would be visible because they don't get modified by setCSSViewport.
-  bool isRootContentDocRootScrollFrame = presContext->IsRootContentDocument()
-                                      && aScrollFrame == presShell->GetRootScrollFrame();
+  bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
+  bool isRootContentDocRootScrollFrame = isRootScrollFrame
+                                      && presContext->IsRootContentDocument();
   if (isRootContentDocRootScrollFrame) {
     if (nsIFrame* rootFrame = presShell->GetRootFrame()) {
       if (nsView* view = rootFrame->GetView()) {
         nsRect viewBoundsAppUnits = view->GetBounds() + rootFrame->GetOffsetToCrossDoc(aReferenceFrame);
         ParentLayerIntRect viewBounds = RoundedToInt(LayoutDeviceRect::FromAppUnits(viewBoundsAppUnits, auPerDevPixel)
                                                      * layoutToParentLayerScale);
 
         // On Android, we need to do things a bit differently to get things
@@ -813,16 +813,28 @@ static void RecordFrameMetrics(nsIFrame*
   // If the scroll frame's content is marked 'scrollgrab', record this
   // in the FrameMetrics so APZ knows to provide the scroll grabbing
   // behaviour.
   if (aScrollFrame && nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
     metrics.mHasScrollgrab = true;
   }
 
   aRoot->SetFrameMetrics(metrics);
+
+  // Also compute and set the background color on the container.
+  // This is needed for APZ overscrolling support.
+  if (aScrollFrame) {
+    // FindBackground() does not work for a root scroll frame, need to use the
+    // root frame instead.
+    nsIFrame* backgroundFrame = isRootScrollFrame ? presShell->GetRootFrame() : aScrollFrame;
+    nsStyleContext* backgroundStyle;
+    if (nsCSSRendering::FindBackground(backgroundFrame, &backgroundStyle)) {
+      aRoot->SetBackgroundColor(backgroundStyle->StyleBackground()->mBackgroundColor);
+    }
+  }
 }
 
 nsDisplayListBuilder::~nsDisplayListBuilder() {
   NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0,
                "All frames should have been unmarked");
   NS_ASSERTION(mPresShellStates.Length() == 0,
                "All presshells should have been exited");
   NS_ASSERTION(!mCurrentTableItem, "No table item should be active");
--- a/layout/generic/nsVideoFrame.h
+++ b/layout/generic/nsVideoFrame.h
@@ -21,18 +21,16 @@ class Layer;
 class LayerManager;
 }
 }
 
 class nsAString;
 class nsPresContext;
 class nsDisplayItem;
 
-nsIFrame* NS_NewVideoFrame (nsIPresShell* aPresShell, nsStyleContext* aContext);
-
 class nsVideoFrame : public nsContainerFrame, public nsIAnonymousContentCreator
 {
 public:
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layers::LayerManager LayerManager;
   typedef mozilla::ContainerLayerParameters ContainerLayerParameters;
 
   nsVideoFrame(nsStyleContext* aContext);
--- a/layout/reftests/mathml/reftest.list
+++ b/layout/reftests/mathml/reftest.list
@@ -213,33 +213,33 @@ fails-if(B2G&&browserIsRemote) == menclo
 fails-if(B2G&&browserIsRemote) == menclose-5-radical.html menclose-5-radical-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-right.html menclose-5-right-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-roundedbox.html menclose-5-roundedbox-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-top.html menclose-5-top-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-updiagonalarrow.html menclose-5-updiagonalarrow-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-updiagonalstrike.html menclose-5-updiagonalstrike-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-verticalstrike.html menclose-5-verticalstrike-ref.html
 fails-if(B2G&&browserIsRemote) == menclose-5-phasorangle.html menclose-5-phasorangle-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-actuarial.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-bottom.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-box.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-circle.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-downdiagonalstrike.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-horizontalstrike.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-left.html menclose-6-ref.html
-== menclose-6-longdiv.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-madruwb.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-radical.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-right.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-roundedbox.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-top.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-updiagonalarrow.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-updiagonalstrike.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-verticalstrike.html menclose-6-ref.html
-fails-if(B2G&&browserIsRemote) == menclose-6-phasorangle.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-actuarial.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-bottom.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-box.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-circle.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-downdiagonalstrike.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-horizontalstrike.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-left.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-longdiv.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-madruwb.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-radical.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-right.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-roundedbox.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-top.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-updiagonalarrow.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-updiagonalstrike.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-verticalstrike.html menclose-6-ref.html
+random-if(B2G&&browserIsRemote) == menclose-6-phasorangle.html menclose-6-ref.html
 == mmultiscript-align.html mmultiscript-align-ref.html
 fails-if(winWidget&&!/^Windows\x20NT\x205\.1/.test(http.oscpu)) == subscript-italic-correction.html subscript-italic-correction-ref.html # Windows versions with Cambria Math, bug 961482
 fails-if(B2G||Android||/^Windows\x20NT\x205\.1/.test(http.oscpu)||OSX==10.6) == mathvariant-1a.html mathvariant-1a-ref.html # Bug 1010679
 fails-if(B2G||Android||/^Windows\x20NT\x205\.1/.test(http.oscpu)||OSX==10.6) == mathvariant-1b.html mathvariant-1b-ref.html # Bug 1010679
 fails-if(B2G||Android||/^Windows\x20NT\x205\.1/.test(http.oscpu)||OSX==10.6) == mathvariant-1c.html mathvariant-1c-ref.html # Bug 1010679
 == mathvariant-1d.html mathvariant-1d-ref.html
 fails-if(B2G||Android||/^Windows\x20NT\x205\.1/.test(http.oscpu)||OSX) == mathvariant-2.html mathvariant-2-ref.html # Bugs 1010678, 1010679
 == mathvariant-3.html mathvariant-3-ref.html
--- a/media/webrtc/moz.build
+++ b/media/webrtc/moz.build
@@ -57,16 +57,17 @@ if CONFIG['MOZ_WEBRTC_SIGNALING']:
         'signaling/src/sipcc/core/gsm/lsm.c',        # Because of name clash in the logTag variable
         'signaling/src/sipcc/core/sdp/sdp_base64.c', # Because of name clash with the macro PADDING
     ]
     # These files cannot be built in unified mode because they force NSPR logging.
     signaling_non_unified_sources_2 = [
         'signaling/src/callcontrol/CallControlManagerImpl.cpp',
         'signaling/src/common/browser_logging/CSFLog.cpp',
         'signaling/src/media-conduit/AudioConduit.cpp',
+        'signaling/src/media-conduit/CodecStatistics.cpp',
         'signaling/src/media-conduit/VideoConduit.cpp',
         'signaling/src/media/CSFAudioControlWrapper.cpp',
         'signaling/src/media/CSFVideoControlWrapper.cpp',
         'signaling/src/media/VcmSIPCCBinding.cpp',
         'signaling/src/mediapipeline/MediaPipeline.cpp',
         'signaling/src/mediapipeline/MediaPipelineFilter.cpp',
         'signaling/src/mediapipeline/SrtpFlow.cpp',
         'signaling/src/peerconnection/MediaStreamList.cpp',
--- a/media/webrtc/signaling/signaling.gyp
+++ b/media/webrtc/signaling/signaling.gyp
@@ -79,16 +79,21 @@
       # SOURCES
       #
       'sources': [
         # Media Conduit
         './src/media-conduit/AudioConduit.h',
         './src/media-conduit/AudioConduit.cpp',
         './src/media-conduit/VideoConduit.h',
         './src/media-conduit/VideoConduit.cpp',
+        './src/media-conduit/CodecStatistics.h',
+        './src/media-conduit/CodecStatistics.cpp',
+        './src/media-conduit/RunningStat.h',
+        './src/media-conduit/GmpVideoCodec.cpp',
+        './src/media-conduit/WebrtcGmpVideoCodec.cpp',
         # Common
         './src/common/CommonTypes.h',
         './src/common/csf_common.h',
         './src/common/NullDeleter.h',
         './src/common/Wrapper.h',
         './src/common/NullTransport.h',
         './src/common/YuvStamper.cpp',
         # Browser Logging
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -173,16 +173,32 @@ public:
   virtual ~WebrtcAudioConduit();
 
   MediaConduitErrorCode Init(WebrtcAudioConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VoiceEngine* GetVoiceEngine() { return mVoiceEngine; }
   bool GetLocalSSRC(unsigned int* ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool GetVideoEncoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* droppedFrames)
+  {
+    return false;
+  }
+  bool GetVideoDecoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* discardedPackets)
+  {
+    return false;
+  }
   bool GetAVStats(int32_t* jitterBufferDelayMs,
                   int32_t* playoutBufferDelayMs,
                   int32_t* avSyncOffsetMs);
   bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost);
   bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                              uint32_t* jitterMs,
                              uint32_t* packetsReceived,
                              uint64_t* bytesReceived,
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
@@ -0,0 +1,174 @@
+/* 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 "CodecStatistics.h"
+
+#include "CSFLog.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace webrtc;
+
+// use the same tag as VideoConduit
+static const char* logTag ="WebrtcVideoSessionConduit";
+
+VideoCodecStatistics::VideoCodecStatistics(int channel,
+                                           ViECodec* codec,
+                                           bool encoder) :
+  mChannel(channel),
+  mSentRawFrames(0),
+  mPtrViECodec(codec),
+  mEncoderDroppedFrames(0),
+  mDecoderDiscardedPackets(0),
+  mEncoderMode(encoder),
+  mReceiveState(kReceiveStateInitial),
+  mRecoveredBeforeLoss(0),
+  mRecoveredLosses(0)
+{
+  MOZ_ASSERT(mPtrViECodec);
+  if (mEncoderMode) {
+    mPtrViECodec->RegisterEncoderObserver(mChannel, *this);
+  } else {
+    mPtrViECodec->RegisterDecoderObserver(mChannel, *this);
+  }
+}
+
+VideoCodecStatistics::~VideoCodecStatistics()
+{
+  if (mEncoderMode) {
+    mPtrViECodec->DeregisterEncoderObserver(mChannel);
+  } else {
+    mPtrViECodec->DeregisterDecoderObserver(mChannel);
+  }
+}
+
+void VideoCodecStatistics::OutgoingRate(const int video_channel,
+                                        const uint32_t framerate,
+                                        const uint32_t bitrate)
+{
+  unsigned int keyFrames, deltaFrames;
+  mPtrViECodec->GetSendCodecStatistics(video_channel, keyFrames, deltaFrames);
+  uint32_t dropped = mSentRawFrames - (keyFrames + deltaFrames);
+  CSFLogDebug(logTag,
+              "encoder statistics - framerate: %u, bitrate: %u, dropped frames: %u",
+              framerate, bitrate, dropped);
+  mEncoderBitRate.Push(bitrate);
+  mEncoderFps.Push(framerate);
+  mEncoderDroppedFrames += dropped;
+}
+
+void VideoCodecStatistics::IncomingCodecChanged(const int video_channel,
+                                                const VideoCodec& video_codec)
+{
+  CSFLogDebug(logTag,
+              "channel %d change codec to \"%s\" ",
+              video_channel, video_codec.plName);
+}
+
+void VideoCodecStatistics::IncomingRate(const int video_channel,
+                                        const unsigned int framerate,
+                                        const unsigned int bitrate)
+{
+  unsigned int discarded = mPtrViECodec->GetDiscardedPackets(video_channel);
+  CSFLogDebug(logTag,
+      "decoder statistics - framerate: %u, bitrate: %u, discarded packets %u",
+      framerate, bitrate, discarded);
+  mDecoderBitRate.Push(bitrate);
+  mDecoderFps.Push(framerate);
+  mDecoderDiscardedPackets += discarded;
+}
+
+void VideoCodecStatistics::ReceiveStateChange(const int aChannel,
+                                              VideoReceiveState aState)
+{
+  CSFLogDebug(logTag,"New state for %d: %d (was %d)", aChannel, aState, mReceiveState);
+#ifdef MOZILLA_INTERNAL_API
+  if (mFirstDecodeTime.IsNull()) {
+    mFirstDecodeTime = TimeStamp::Now();
+  }
+  /*
+   * Invalid transitions:
+   * WaitingKey -> PreemptiveNACK
+   * DecodingWithErrors -> PreemptiveNACK
+   */
+
+  switch (mReceiveState) {
+    case kReceiveStateNormal:
+    case kReceiveStateInitial:
+      // in a normal state
+      if (aState != kReceiveStateNormal && aState != kReceiveStateInitial) {
+        // no longer in a normal state
+        if (aState != kReceiveStatePreemptiveNACK) {
+          mReceiveFailureTime = TimeStamp::Now();
+        }
+      } // else Normal<->Initial transition
+      break;
+    default:
+      // not in a normal state
+      if (aState == kReceiveStateNormal || aState == kReceiveStateInitial) {
+
+        if (mReceiveState == kReceiveStatePreemptiveNACK) {
+          mRecoveredBeforeLoss++;
+          CSFLogError(logTag, "Video error avoided by NACK recovery");
+        } else if (!mReceiveFailureTime.IsNull()) { // safety
+          TimeDuration timeDelta = TimeStamp::Now() - mReceiveFailureTime;
+          CSFLogError(logTag, "Video error duration: %u ms",
+                      static_cast<uint32_t>(timeDelta.ToMilliseconds()));
+          Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_ERROR_RECOVERY_MS,
+                                static_cast<uint32_t>(timeDelta.ToMilliseconds()));
+
+          mRecoveredLosses++; // to calculate losses per minute
+          mTotalLossTime += timeDelta;  // To calculate % time in recovery
+        }
+      } // else non-Normal to different non-normal transition
+      break;
+  }
+
+#endif
+
+  mReceiveState = aState;
+}
+
+void VideoCodecStatistics::EndOfCallStats()
+{
+#ifdef MOZILLA_INTERNAL_API
+  if (!mFirstDecodeTime.IsNull()) {
+    TimeDuration callDelta = TimeStamp::Now() - mFirstDecodeTime;
+    if (callDelta.ToSeconds() != 0) {
+      uint32_t recovered_per_min = mRecoveredBeforeLoss/(callDelta.ToSeconds()/60);
+      CSFLogError(logTag, "Video recovery before error per min %f", recovered_per_min);
+      Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_RECOVERY_BEFORE_ERROR_PER_MIN,
+                            recovered_per_min);
+      uint32_t err_per_min = mRecoveredLosses/(callDelta.ToSeconds()/60);
+      CSFLogError(logTag, "Video recovery after error per min %u", err_per_min);
+      Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_RECOVERY_AFTER_ERROR_PER_MIN,
+                            err_per_min);
+      float percent = (mTotalLossTime.ToSeconds()*100)/callDelta.ToSeconds();
+      CSFLogError(logTag, "Video error time percentage %f%%", percent);
+      Telemetry::Accumulate(Telemetry::WEBRTC_VIDEO_DECODE_ERROR_TIME_PERMILLE,
+                            static_cast<uint32_t>(percent*10));
+    }
+  }
+#endif
+}
+
+void VideoCodecStatistics::SentFrame()
+{
+  mSentRawFrames++;
+}
+
+void VideoCodecStatistics::Dump()
+{
+  Dump(mEncoderBitRate, "encoder bitrate");
+  Dump(mEncoderFps, "encoder fps");
+  Dump(mDecoderBitRate, "decoder bitrate");
+  Dump(mDecoderFps, "decoder fps");
+}
+
+void VideoCodecStatistics::Dump(RunningStat& s, const char *name)
+{
+  CSFLogDebug(logTag,
+              "%s, mean: %f, variance: %f, standard deviation: %f",
+              name, s.Mean(), s.Variance(), s.StandardDeviation());
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.h
@@ -0,0 +1,108 @@
+/* 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 CODEC_STATISTICS_H_
+#define CODEC_STATISTICS_H_
+#include <math.h>
+
+#include "nsTArray.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/TimeStamp.h"
+#include "webrtc/common_types.h"
+#include "webrtc/video_engine/include/vie_codec.h"
+#include "MediaEngineWrapper.h"
+#include "RunningStat.h"
+
+namespace mozilla {
+
+// Statistics-gathering observer for Video Encoder and Decoder
+
+class VideoCodecStatistics : public webrtc::ViEEncoderObserver
+                           , public webrtc::ViEDecoderObserver
+{
+public:
+  VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec, bool encoder);
+  ~VideoCodecStatistics();
+
+  void SentFrame();
+  virtual void OutgoingRate(const int video_channel,
+    const unsigned int framerate, const unsigned int bitrate) MOZ_OVERRIDE;
+
+  virtual void IncomingCodecChanged(const int video_channel,
+    const webrtc::VideoCodec& video_codec) MOZ_OVERRIDE;
+
+  virtual void IncomingRate(const int video_channel,
+                            const unsigned int framerate,
+                            const unsigned int bitrate) MOZ_OVERRIDE;
+
+  void ReceiveStateChange(const int video_channel, webrtc::VideoReceiveState state) MOZ_OVERRIDE;
+
+  void EndOfCallStats();
+
+  virtual void RequestNewKeyFrame(const int video_channel) MOZ_OVERRIDE {};
+
+  virtual void SuspendChange(int video_channel, bool is_suspended) MOZ_OVERRIDE {};
+  virtual void DecoderTiming(int decode_ms,
+                             int max_decode_ms,
+                             int current_delay_ms,
+                             int target_delay_ms,
+                             int jitter_buffer_ms,
+                             int min_playout_delay_ms,
+                             int render_delay_ms) MOZ_OVERRIDE {}
+
+  bool GetEncoderStats(double* framerateMean,
+                       double* framerateStdDev,
+                       double* bitrateMean,
+                       double* bitrateStdDev,
+                       uint32_t* droppedFrames)
+  {
+    *framerateMean   = mEncoderFps.Mean();
+    *framerateStdDev = mEncoderFps.StandardDeviation();
+    *bitrateMean     = mEncoderBitRate.Mean();
+    *bitrateStdDev   = mEncoderBitRate.StandardDeviation();
+    *droppedFrames   = mEncoderDroppedFrames;
+    return true;
+  }
+
+  bool GetDecoderStats(double* framerateMean,
+                       double* framerateStdDev,
+                       double* bitrateMean,
+                       double* bitrateStdDev,
+                       uint32_t* discardedPackets)
+  {
+    *framerateMean    = mDecoderFps.Mean();
+    *framerateStdDev  = mDecoderFps.StandardDeviation();
+    *bitrateMean      = mDecoderBitRate.Mean();
+    *bitrateStdDev    = mDecoderBitRate.StandardDeviation();
+    *discardedPackets = mDecoderDiscardedPackets;
+    return true;
+  }
+
+  void Dump();
+private:
+  void Dump(RunningStat& s, const char *name);
+
+  int mChannel;
+  uint32_t mSentRawFrames;
+  ScopedCustomReleasePtr<webrtc::ViECodec> mPtrViECodec; // back-pointer
+
+  RunningStat mEncoderBitRate;
+  RunningStat mEncoderFps;
+  uint32_t mEncoderDroppedFrames;
+  RunningStat mDecoderBitRate;
+  RunningStat mDecoderFps;
+  uint32_t mDecoderDiscardedPackets;
+  const bool mEncoderMode;
+
+  webrtc::VideoReceiveState mReceiveState;
+#ifdef MOZILLA_INTERNAL_API
+  TimeStamp mFirstDecodeTime;
+  TimeStamp mReceiveFailureTime;
+  TimeDuration mTotalLossTime;
+#endif
+  uint32_t mRecoveredBeforeLoss;
+  uint32_t mRecoveredLosses;
+};
+}
+
+#endif //CODEC_STATISTICS_H_
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.cpp
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebrtcGmpVideoCodec.h"
+#include "GmpVideoCodec.h"
+
+namespace mozilla {
+
+VideoEncoder* GmpVideoCodec::CreateEncoder() {
+  return static_cast<VideoEncoder*>(new WebrtcGmpVideoEncoder());
+}
+
+VideoDecoder* GmpVideoCodec::CreateDecoder() {
+  return static_cast<VideoDecoder*>(new WebrtcGmpVideoDecoder());
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/GmpVideoCodec.h
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMPVIDEOCODEC_H_
+#define GMPVIDEOCODEC_H_
+
+#include "MediaConduitInterface.h"
+
+namespace mozilla {
+class GmpVideoCodec {
+ public:
+  static VideoEncoder* CreateEncoder();
+  static VideoDecoder* CreateDecoder();
+};
+
+}
+
+#endif
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -159,16 +159,26 @@ public:
   virtual MediaConduitErrorCode AttachTransport(RefPtr<TransportInterface> aTransport) = 0;
 
   virtual bool GetLocalSSRC(unsigned int* ssrc) = 0;
   virtual bool GetRemoteSSRC(unsigned int* ssrc) = 0;
 
   /**
    * Functions returning stats needed by w3c stats model.
    */
+  virtual bool GetVideoEncoderStats(double* framerateMean,
+                                    double* framerateStdDev,
+                                    double* bitrateMean,
+                                    double* bitrateStdDev,
+                                    uint32_t* droppedFrames) = 0;
+  virtual bool GetVideoDecoderStats(double* framerateMean,
+                                    double* framerateStdDev,
+                                    double* bitrateMean,
+                                    double* bitrateStdDev,
+                                    uint32_t* discardedPackets) = 0;
   virtual bool GetAVStats(int32_t* jitterBufferDelayMs,
                           int32_t* playoutBufferDelayMs,
                           int32_t* avSyncOffsetMs) = 0;
   virtual bool GetRTPStats(unsigned int* jitterMs,
                            unsigned int* cumulativeLost) = 0;
   virtual bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                                      uint32_t* jitterMs,
                                      uint32_t* packetsReceived,
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/RunningStat.h
@@ -0,0 +1,66 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+/* Adapted from "Accurately computing running variance - John D. Cook"
+   http://www.johndcook.com/standard_deviation.html */
+
+#ifndef RUNNING_STAT_H_
+#define RUNNING_STAT_H_
+#include <math.h>
+
+namespace mozilla {
+
+class RunningStat
+{
+public:
+  RunningStat() : mN(0) {}
+
+  void Clear()
+  {
+    mN = 0;
+  }
+
+  void Push(double x)
+  {
+    mN++;
+
+    // See Knuth TAOCP vol 2, 3rd edition, page 232
+    if (mN == 1)
+    {
+      mOldM = mNewM = x;
+      mOldS = 0.0;
+    } else {
+      mNewM = mOldM + (x - mOldM) / mN;
+      mNewS = mOldS + (x - mOldM) * (x - mNewM);
+
+      // set up for next iteration
+      mOldM = mNewM;
+      mOldS = mNewS;
+    }
+  }
+
+  int NumDataValues() const
+  {
+    return mN;
+  }
+
+  double Mean() const
+  {
+    return (mN > 0) ? mNewM : 0.0;
+  }
+
+  double Variance() const
+  {
+    return (mN > 1) ? mNewS / (mN - 1) : 0.0;
+  }
+
+  double StandardDeviation() const
+  {
+    return sqrt(Variance());
+  }
+
+private:
+  int mN;
+  double mOldM, mNewM, mOldS, mNewS;
+};
+}
+#endif //RUNNING_STAT_H_
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -75,16 +75,21 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
       mPtrViECapture->DisconnectCaptureDevice(mCapId);
       mPtrViECapture->ReleaseCaptureDevice(mCapId);
       mPtrExtCapture = nullptr;
       if (mOtherDirection)
         mOtherDirection->mPtrExtCapture = nullptr;
     }
   }
 
+   if (mPtrExtCodec) {
+     mPtrExtCodec->Release();
+     mPtrExtCodec = NULL;
+   }
+
   //Deal with External Renderer
   if(mPtrViERender)
   {
     if (!mShutDown) {
       if(mRenderer) {
         mPtrViERender->StopRender(mChannel);
       }
       mPtrViERender->RemoveRenderer(mChannel);
@@ -112,16 +117,21 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
   if (mOtherDirection)
   {
     // mOtherDirection owns these now!
     mOtherDirection->mOtherDirection = nullptr;
     // let other side we terminated the channel
     mOtherDirection->mShutDown = true;
     mVideoEngine = nullptr;
   } else {
+    // mVideoCodecStat has a back-ptr to mPtrViECodec that must be released first
+    if (mVideoCodecStat) {
+      mVideoCodecStat->EndOfCallStats();
+    }
+    mVideoCodecStat = nullptr;
     // We can't delete the VideoEngine until all these are released!
     // And we can't use a Scoped ptr, since the order is arbitrary
     mPtrViEBase = nullptr;
     mPtrViECapture = nullptr;
     mPtrViECodec = nullptr;
     mPtrViENetwork = nullptr;
     mPtrViERender = nullptr;
     mPtrRTP = nullptr;
@@ -130,24 +140,58 @@ WebrtcVideoConduit::~WebrtcVideoConduit(
     // only one opener can call Delete.  Have it be the last to close.
     if(mVideoEngine)
     {
       webrtc::VideoEngine::Delete(mVideoEngine);
     }
   }
 }
 
-bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc) {
+bool WebrtcVideoConduit::GetLocalSSRC(unsigned int* ssrc)
+{
   return !mPtrRTP->GetLocalSSRC(mChannel, *ssrc);
 }
 
-bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc) {
+bool WebrtcVideoConduit::GetRemoteSSRC(unsigned int* ssrc)
+{
   return !mPtrRTP->GetRemoteSSRC(mChannel, *ssrc);
 }
 
+bool WebrtcVideoConduit::GetVideoEncoderStats(double* framerateMean,
+                                              double* framerateStdDev,
+                                              double* bitrateMean,
+                                              double* bitrateStdDev,
+                                              uint32_t* droppedFrames)
+{
+  if (!mEngineTransmitting) {
+    return false;
+  }
+  MOZ_ASSERT(mVideoCodecStat);
+  mVideoCodecStat->GetEncoderStats(framerateMean, framerateStdDev,
+                                   bitrateMean, bitrateStdDev,
+                                   droppedFrames);
+  return true;
+}
+
+bool WebrtcVideoConduit::GetVideoDecoderStats(double* framerateMean,
+                                              double* framerateStdDev,
+                                              double* bitrateMean,
+                                              double* bitrateStdDev,
+                                              uint32_t* discardedPackets)
+{
+  if (!mEngineReceiving) {
+    return false;
+  }
+  MOZ_ASSERT(mVideoCodecStat);
+  mVideoCodecStat->GetDecoderStats(framerateMean, framerateStdDev,
+                                   bitrateMean, bitrateStdDev,
+                                   discardedPackets);
+  return true;
+}
+
 bool WebrtcVideoConduit::GetAVStats(int32_t* jitterBufferDelayMs,
                                     int32_t* playoutBufferDelayMs,
                                     int32_t* avSyncOffsetMs) {
   return false;
 }
 
 bool WebrtcVideoConduit::GetRTPStats(unsigned int* jitterMs,
                                      unsigned int* cumulativeLost) {
@@ -302,16 +346,23 @@ MediaConduitErrorCode WebrtcVideoConduit
   }
 
   if( !(mPtrViERender = ViERender::GetInterface(mVideoEngine)))
   {
     CSFLogError(logTag, "%s Unable to get video render interface ", __FUNCTION__);
     return kMediaConduitSessionNotInited;
   }
 
+  mPtrExtCodec = webrtc::ViEExternalCodec::GetInterface(mVideoEngine);
+  if (!mPtrExtCodec) {
+    CSFLogError(logTag, "%s Unable to get external codec interface: %d ",
+                __FUNCTION__,mPtrViEBase->LastError());
+    return kMediaConduitSessionNotInited;
+  }
+
   if( !(mPtrRTP = webrtc::ViERTP_RTCP::GetInterface(mVideoEngine)))
   {
     CSFLogError(logTag, "%s Unable to get video RTCP interface ", __FUNCTION__);
     return kMediaConduitSessionNotInited;
   }
 
   if ( !(mPtrExtCodec = webrtc::ViEExternalCodec::GetInterface(mVideoEngine)))
   {
@@ -474,17 +525,17 @@ WebrtcVideoConduit::AttachTransport(mozi
 
 /**
  * Note: Setting the send-codec on the Video Engine will restart the encoder,
  * sets up new SSRC and reset RTP_RTCP module with the new codec setting.
  */
 MediaConduitErrorCode
 WebrtcVideoConduit::ConfigureSendMediaCodec(const VideoCodecConfig* codecConfig)
 {
-  CSFLogDebug(logTag,  "%s for %s", __FUNCTION__, codecConfig->mName.c_str());
+  CSFLogDebug(logTag,  "%s for %s", __FUNCTION__, codecConfig ? codecConfig->mName.c_str() : "<null>");
   bool codecFound = false;
   MediaConduitErrorCode condError = kMediaConduitNoError;
   int error = 0; //webrtc engine errors
   webrtc::VideoCodec  video_codec;
   std::string payloadName;
 
   //validate basic params
   if((condError = ValidateCodecConfig(codecConfig,true)) != kMediaConduitNoError)
@@ -563,16 +614,21 @@ WebrtcVideoConduit::ConfigureSendMediaCo
     {
       CSFLogError(logTag, "%s Invalid Send Codec", __FUNCTION__);
       return kMediaConduitInvalidSendCodec;
     }
     CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__,
                 mPtrViEBase->LastError());
     return kMediaConduitUnknownError;
   }
+
+  if (!mVideoCodecStat) {
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, true);
+  }
+
   mSendingWidth = 0;
   mSendingHeight = 0;
 
   if(codecConfig->RtcpFbIsSet(SDP_RTCP_FB_NACK_BASIC)) {
     CSFLogDebug(logTag, "Enabling NACK (send) for video stream\n");
     if (mPtrRTP->SetNACKStatus(mChannel, true) != 0)
     {
       CSFLogError(logTag,  "%s NACKStatus Failed %d ", __FUNCTION__,
@@ -731,16 +787,20 @@ WebrtcVideoConduit::ConfigureRecvMediaCo
   }//end for
 
   if(!success)
   {
     CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__);
     return kMediaConduitInvalidReceiveCodec;
   }
 
+  if (!mVideoCodecStat) {
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, false);
+  }
+
   // XXX Currently, we gather up all of the feedback types that the remote
   // party indicated it supports for all video codecs and configure the entire
   // conduit based on those capabilities. This is technically out of spec,
   // as these values should be configured on a per-codec basis. However,
   // the video engine only provides this API on a per-conduit basis, so that's
   // how we have to do it. The approach of considering the remote capablities
   // for the entire conduit to be a union of all remote codec capabilities
   // (rather than the more conservative approach of using an intersection)
@@ -1005,16 +1065,17 @@ WebrtcVideoConduit::SendVideoFrame(unsig
                                    type,
                                    (unsigned long long)capture_time) == -1)
   {
     CSFLogError(logTag,  "%s IncomingFrame Failed %d ", __FUNCTION__,
                                             mPtrViEBase->LastError());
     return kMediaConduitCaptureError;
   }
 
+  mVideoCodecStat->SentFrame();
   CSFLogDebug(logTag, "%s Inserted a frame", __FUNCTION__);
   return kMediaConduitNoError;
 }
 
 // Transport Layer Callbacks
 MediaConduitErrorCode
 WebrtcVideoConduit::ReceivedRTPPacket(const void *data, int len)
 {
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.h
@@ -5,16 +5,17 @@
 #ifndef VIDEO_SESSION_H_
 #define VIDEO_SESSION_H_
 
 #include "nsAutoPtr.h"
 #include "mozilla/Attributes.h"
 
 #include "MediaConduitInterface.h"
 #include "MediaEngineWrapper.h"
+#include "CodecStatistics.h"
 
 // conflicts with #include of scoped_ptr.h
 #undef FF
 // Video Engine Includes
 #include "webrtc/common_types.h"
 #ifdef FF
 #undef FF // Avoid name collision between scoped_ptr.h and nsCRTGlue.h.
 #endif
@@ -245,16 +246,26 @@ public:
   virtual ~WebrtcVideoConduit() ;
 
   MediaConduitErrorCode Init(WebrtcVideoConduit *other);
 
   int GetChannel() { return mChannel; }
   webrtc::VideoEngine* GetVideoEngine() { return mVideoEngine; }
   bool GetLocalSSRC(unsigned int* ssrc);
   bool GetRemoteSSRC(unsigned int* ssrc);
+  bool GetVideoEncoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* droppedFrames);
+  bool GetVideoDecoderStats(double* framerateMean,
+                            double* framerateStdDev,
+                            double* bitrateMean,
+                            double* bitrateStdDev,
+                            uint32_t* discardedPackets);
   bool GetAVStats(int32_t* jitterBufferDelayMs,
                   int32_t* playoutBufferDelayMs,
                   int32_t* avSyncOffsetMs);
   bool GetRTPStats(unsigned int* jitterMs, unsigned int* cumulativeLost);
   bool GetRTCPReceiverReport(DOMHighResTimeStamp* timestamp,
                              uint32_t* jitterMs,
                              uint32_t* packetsReceived,
                              uint64_t* bytesReceived,
@@ -339,13 +350,16 @@ private:
   static const unsigned int sAlphaNum = 7;
   static const unsigned int sAlphaDen = 8;
   static const unsigned int sRoundingPadding = 1024;
 
   mozilla::RefPtr<WebrtcAudioConduit> mSyncedTo;
 
   nsAutoPtr<VideoCodecConfig> mExternalSendCodec;
   nsAutoPtr<VideoCodecConfig> mExternalRecvCodec;
+
+  // statistics object for video codec;
+  nsAutoPtr<VideoCodecStatistics> mVideoCodecStat;
 };
 
 } // end namespace
 
 #endif
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.cpp
@@ -0,0 +1,601 @@
+/* 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 "WebrtcGmpVideoCodec.h"
+
+#include <iostream>
+#include <vector>
+
+#include "mozilla/Scoped.h"
+#include "VideoConduit.h"
+#include "AudioConduit.h"
+#include "runnable_utils.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "nsServiceManagerUtils.h"
+
+#include "gmp-video-host.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "webrtc/video_engine/include/vie_external_codec.h"
+
+namespace mozilla {
+
+// Encoder.
+WebrtcGmpVideoEncoder::WebrtcGmpVideoEncoder()
+  : mGMPThread(nullptr)
+  , mGMP(nullptr)
+  , mHost(nullptr)
+  , mCallback(nullptr)
+{}
+
+static int
+WebrtcFrameTypeToGmpFrameType(webrtc::VideoFrameType aIn,
+                              GMPVideoFrameType *aOut)
+{
+  MOZ_ASSERT(aOut);
+  switch(aIn) {
+    case webrtc::kKeyFrame:
+      *aOut = kGMPKeyFrame;
+      break;
+    case webrtc::kDeltaFrame:
+      *aOut = kGMPDeltaFrame;
+      break;
+    case webrtc::kGoldenFrame:
+      *aOut = kGMPGoldenFrame;
+      break;
+    case webrtc::kAltRefFrame:
+      *aOut = kGMPAltRefFrame;
+      break;
+    case webrtc::kSkipFrame:
+      *aOut = kGMPSkipFrame;
+      break;
+    default:
+      MOZ_CRASH();
+      return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+static int
+GmpFrameTypeToWebrtcFrameType(GMPVideoFrameType aIn,
+                              webrtc::VideoFrameType *aOut)
+{
+  MOZ_ASSERT(aOut);
+  switch(aIn) {
+    case kGMPKeyFrame:
+      *aOut = webrtc::kKeyFrame;
+      break;
+    case kGMPDeltaFrame:
+      *aOut = webrtc::kDeltaFrame;
+      break;
+    case kGMPGoldenFrame:
+      *aOut = webrtc::kGoldenFrame;
+      break;
+    case kGMPAltRefFrame:
+      *aOut = webrtc::kAltRefFrame;
+      break;
+    case kGMPSkipFrame:
+      *aOut = webrtc::kSkipFrame;
+      break;
+    default:
+      MOZ_CRASH();
+      return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+
+int32_t
+WebrtcGmpVideoEncoder::InitEncode(const webrtc::VideoCodec* aCodecSettings,
+                                  int32_t aNumberOfCores,
+                                  uint32_t aMaxPayloadSize)
+{
+  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mMPS);
+
+  nsresult rv = mMPS->GetThread(&mGMPThread);
+  if (NS_FAILED(rv)) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  int32_t ret;
+  RUN_ON_THREAD(mGMPThread,
+                WrapRunnableRet(this,
+                                &WebrtcGmpVideoEncoder::InitEncode_g,
+                                aCodecSettings,
+                                aNumberOfCores,
+                                aMaxPayloadSize,
+                                &ret));
+
+  return ret;
+}
+
+int32_t
+WebrtcGmpVideoEncoder::InitEncode_g(const webrtc::VideoCodec* aCodecSettings,
+                                    int32_t aNumberOfCores,
+                                    uint32_t aMaxPayloadSize)
+{
+  GMPVideoHost* host = nullptr;
+  GMPVideoEncoder* gmp = nullptr;
+
+  nsresult rv = mMPS->GetGMPVideoEncoderVP8(&host, &gmp);
+  if (NS_FAILED(rv)) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  mGMP = gmp;
+  mHost = host;
+
+  if (!gmp || !host) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  // Bug XXXXXX: transfer settings from codecSettings to codec.
+  GMPVideoCodec codec;
+  memset(&codec, 0, sizeof(codec));
+
+  codec.mWidth = aCodecSettings->width;
+  codec.mHeight = aCodecSettings->height;
+  codec.mStartBitrate = aCodecSettings->startBitrate;
+  codec.mMinBitrate = aCodecSettings->minBitrate;
+  codec.mMaxBitrate = aCodecSettings->maxBitrate;
+  codec.mMaxFramerate = aCodecSettings->maxFramerate;
+
+  GMPVideoErr err = mGMP->InitEncode(codec, this, 1, aMaxPayloadSize);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+
+int32_t
+WebrtcGmpVideoEncoder::Encode(const webrtc::I420VideoFrame& aInputImage,
+                              const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                              const std::vector<webrtc::VideoFrameType>* aFrameTypes)
+{
+  int32_t ret;
+  MOZ_ASSERT(mGMPThread);
+  RUN_ON_THREAD(mGMPThread,
+                WrapRunnableRet(this,
+                                &WebrtcGmpVideoEncoder::Encode_g,
+                                &aInputImage,
+                                aCodecSpecificInfo,
+                                aFrameTypes,
+                                &ret));
+
+  return ret;
+}
+
+
+int32_t
+WebrtcGmpVideoEncoder::Encode_g(const webrtc::I420VideoFrame* aInputImage,
+                                const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                                const std::vector<webrtc::VideoFrameType>* aFrameTypes)
+{
+  MOZ_ASSERT(mHost);
+  MOZ_ASSERT(mGMP);
+
+  GMPVideoFrame* ftmp = nullptr;
+  GMPVideoErr err = mHost->CreateFrame(kGMPI420VideoFrame, &ftmp);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+  GMPVideoi420Frame* frame = static_cast<GMPVideoi420Frame*>(ftmp);
+
+  err = frame->CreateFrame(aInputImage->allocated_size(webrtc::kYPlane),
+                           aInputImage->buffer(webrtc::kYPlane),
+                           aInputImage->allocated_size(webrtc::kUPlane),
+                           aInputImage->buffer(webrtc::kUPlane),
+                           aInputImage->allocated_size(webrtc::kVPlane),
+                           aInputImage->buffer(webrtc::kVPlane),
+                           aInputImage->width(),
+                           aInputImage->height(),
+                           aInputImage->stride(webrtc::kYPlane),
+                           aInputImage->stride(webrtc::kUPlane),
+                           aInputImage->stride(webrtc::kVPlane));
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+  frame->SetTimestamp(aInputImage->timestamp());
+  frame->SetRenderTime_ms(aInputImage->render_time_ms());
+
+  // Bug XXXXXX: Set codecSpecific info
+  GMPCodecSpecificInfo info;
+  memset(&info, 0, sizeof(info));
+
+  std::vector<GMPVideoFrameType> gmp_frame_types;
+  for (auto it = aFrameTypes->begin(); it != aFrameTypes->end(); ++it) {
+    GMPVideoFrameType ft;
+
+    int32_t ret = WebrtcFrameTypeToGmpFrameType(*it, &ft);
+    if (ret != WEBRTC_VIDEO_CODEC_OK) {
+      return ret;
+    }
+
+    gmp_frame_types.push_back(ft);
+  }
+
+  err = mGMP->Encode(frame, info, gmp_frame_types);
+  if (err != GMPVideoNoErr) {
+    return err;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+
+
+int32_t
+WebrtcGmpVideoEncoder::RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* aCallback)
+{
+  mCallback = aCallback;
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoEncoder::Release()
+{
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoEncoder::SetChannelParameters(uint32_t aPacketLoss, int aRTT)
+{
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoEncoder::SetRates(uint32_t aNewBitRate, uint32_t aFrameRate)
+{
+  int32_t ret;
+  MOZ_ASSERT(mGMPThread);
+  RUN_ON_THREAD(mGMPThread,
+                WrapRunnableRet(this,
+                                &WebrtcGmpVideoEncoder::SetRates_g,
+                                aNewBitRate, aFrameRate,
+                                &ret));
+
+  return ret;
+}
+
+int32_t
+WebrtcGmpVideoEncoder::SetRates_g(uint32_t aNewBitRate, uint32_t aFrameRate)
+{
+  MOZ_ASSERT(mGMP);
+  GMPVideoErr err = mGMP->SetRates(aNewBitRate, aFrameRate);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+#define GMP_ENCODE_HAS_START_CODES 1
+#ifdef GMP_ENCODE_HAS_START_CODES
+// Temporary until inside-sandbox-code switches from start codes to the API here
+static int GetNextNALUnit(const uint8_t **aData,
+                          const uint8_t *aEnd, // at first byte past end
+                          size_t *aNalSize)
+{
+  const uint8_t *data = *aData;
+  uint8_t zeros = 0;
+
+  MOZ_ASSERT(data);
+  // Don't assume we start with a start code (paranoia)
+  while (data < aEnd) {
+    if (*data == 0) {
+      zeros++;
+      if (zeros > 3) {
+        // internal format error; keep going anyways
+        zeros = 3;
+      }
+    } else {
+      if (*data == 0x01) {
+        if (zeros >= 2) {
+          // Found start code 0x000001 or 0x00000001
+          MOZ_ASSERT(zeros == 3); // current temp code only handles 4-byte codes
+          // now find the length of the NAL
+          *aData = ++data; // start of actual data
+
+          while (data < aEnd) {
+            if (*data == 0) {
+              zeros++;
+              if (zeros > 3) {
+                // internal format error; keep going anyways
+                zeros = 3;
+              }
+            } else {
+              if (*data == 0x01) {
+                if (zeros >= 2) {
+                  // Found start code 0x000001 or 0x00000001
+                  *aNalSize = (data - *aData) - zeros;
+                  return 0;
+                }
+              }
+              zeros = 0;
+            }
+            data++;
+          }
+          // NAL ends at the end of the buffer
+          *aNalSize = (data - *aData);
+          return 0;
+        }
+      }
+      zeros = 0;
+    }
+    data++;
+  }
+  return -1; // no nals
+}
+
+#endif
+
+// GMPEncoderCallback virtual functions.
+void
+WebrtcGmpVideoEncoder::Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+                               const GMPCodecSpecificInfo& aCodecSpecificInfo)
+{
+  if (mCallback) { // paranoia
+    webrtc::VideoFrameType ft;
+    GmpFrameTypeToWebrtcFrameType(aEncodedFrame->FrameType(), &ft);
+    GMPBufferType type = aCodecSpecificInfo.mBufferType;
+
+#ifdef GMP_ENCODE_HAS_START_CODES
+    {
+      // This code will be removed when the code inside the plugin is updated
+      // Break input encoded data into NALUs and convert to length+data format
+      const uint8_t* data = aEncodedFrame->Buffer();
+      const uint8_t* end  = data + aEncodedFrame->Size(); // at first byte past end
+      size_t nalSize = 0;
+      while (GetNextNALUnit(&data, end, &nalSize) == 0) {
+        // Assumes 4-byte start codes (0x00000001)
+        MOZ_ASSERT(data >= aEncodedFrame->Buffer() + 4);
+        uint8_t *start_code = const_cast<uint8_t*>(data-sizeof(uint32_t));
+        if (*start_code == 0x00 && *(start_code+1) == 0x00 &&
+            *(start_code+2) == 0x00 && *(start_code+3) == 0x01) {
+          *(reinterpret_cast<uint32_t*>(start_code)) = nalSize;
+        }
+        data += nalSize;
+      }
+      type = GMP_BufferLength32;
+    }
+#endif
+
+    // Right now makes one Encoded() callback per unit
+    // XXX convert to FragmentationHeader format (array of offsets and sizes plus a buffer) in
+    // combination with H264 packetization changes in webrtc/trunk code
+    uint8_t *buffer = aEncodedFrame->Buffer();
+    uint8_t *end = aEncodedFrame->Buffer() + aEncodedFrame->Size();
+    uint32_t size;
+    while (buffer < end) {
+      switch (type) {
+        case GMP_BufferSingle:
+          size = aEncodedFrame->Size();
+          break;
+        case GMP_BufferLength8:
+          size = *buffer++;
+          break;
+        case GMP_BufferLength16:
+          // presumes we can do unaligned loads
+          size = *(reinterpret_cast<uint16_t*>(buffer));
+          buffer += 2;
+          break;
+        case GMP_BufferLength24:
+          // 24-bits is a pain, since byte-order issues make things painful
+          // I'm going to define 24-bit as little-endian always; big-endian must convert
+          size = ((uint32_t) *buffer) |
+                 (((uint32_t) *(buffer+1)) << 8) |
+                 (((uint32_t) *(buffer+2)) << 16);
+          buffer += 3;
+          break;
+        case GMP_BufferLength32:
+          // presumes we can do unaligned loads
+          size = *(reinterpret_cast<uint32_t*>(buffer));
+          buffer += 4;
+          break;
+        default:
+          // really that it's not in the enum; gives more readable error
+          MOZ_ASSERT(aCodecSpecificInfo.mBufferType != GMP_BufferSingle);
+          aEncodedFrame->Destroy();
+          return;
+      }
+      webrtc::EncodedImage unit(buffer, size, size);
+      unit._frameType = ft;
+      unit._timeStamp = aEncodedFrame->TimeStamp();
+      unit._completeFrame = true;
+
+      mCallback->Encoded(unit, nullptr, nullptr);
+
+      buffer += size;
+    }
+  }
+  aEncodedFrame->Destroy();
+}
+
+
+// Decoder.
+WebrtcGmpVideoDecoder::WebrtcGmpVideoDecoder() :
+  mGMPThread(nullptr),
+  mGMP(nullptr),
+  mHost(nullptr),
+  mCallback(nullptr) {}
+
+int32_t
+WebrtcGmpVideoDecoder::InitDecode(const webrtc::VideoCodec* aCodecSettings,
+                                  int32_t aNumberOfCores)
+{
+  mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mMPS);
+
+  if (NS_WARN_IF(NS_FAILED(mMPS->GetThread(&mGMPThread))))
+  {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  int32_t ret;
+  RUN_ON_THREAD(mGMPThread,
+                WrapRunnableRet(this,
+                                &WebrtcGmpVideoDecoder::InitDecode_g,
+                                aCodecSettings,
+                                aNumberOfCores,
+                                &ret));
+
+  return ret;
+}
+
+int32_t
+WebrtcGmpVideoDecoder::InitDecode_g(const webrtc::VideoCodec* aCodecSettings,
+                                    int32_t aNumberOfCores)
+{
+  GMPVideoHost* host = nullptr;
+  GMPVideoDecoder* gmp = nullptr;
+
+  if (NS_WARN_IF(NS_FAILED(mMPS->GetGMPVideoDecoderVP8(&host, &gmp))))
+  {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  mGMP = gmp;
+  mHost = host;
+  if (!gmp || !host) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  // Bug XXXXXX: transfer settings from codecSettings to codec.
+  GMPVideoCodec codec;
+  memset(&codec, 0, sizeof(codec));
+
+  GMPVideoErr err = mGMP->InitDecode(codec, this, 1);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoDecoder::Decode(const webrtc::EncodedImage& aInputImage,
+                              bool aMissingFrames,
+                              const webrtc::RTPFragmentationHeader* aFragmentation,
+                              const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                              int64_t aRenderTimeMs)
+{
+  int32_t ret;
+  MOZ_ASSERT(mGMPThread);
+  RUN_ON_THREAD(mGMPThread,
+                WrapRunnableRet(this,
+                                &WebrtcGmpVideoDecoder::Decode_g,
+                                aInputImage,
+                                aMissingFrames,
+                                aFragmentation,
+                                aCodecSpecificInfo,
+                                aRenderTimeMs,
+                                &ret));
+
+  return ret;
+}
+
+int32_t
+WebrtcGmpVideoDecoder::Decode_g(const webrtc::EncodedImage& aInputImage,
+                                bool aMissingFrames,
+                                const webrtc::RTPFragmentationHeader* aFragmentation,
+                                const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                                int64_t aRenderTimeMs)
+{
+  MOZ_ASSERT(mHost);
+  MOZ_ASSERT(mGMP);
+
+  GMPVideoFrame* ftmp = nullptr;
+  GMPVideoErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  GMPVideoEncodedFrame* frame = static_cast<GMPVideoEncodedFrame*>(ftmp);
+  err = frame->CreateEmptyFrame(aInputImage._length);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+  // XXX It'd be wonderful not to have to memcpy the encoded data!
+  memcpy(frame->Buffer(), aInputImage._buffer, frame->Size());
+
+  frame->SetEncodedWidth(aInputImage._encodedWidth);
+  frame->SetEncodedHeight(aInputImage._encodedHeight);
+  frame->SetTimeStamp(aInputImage._timeStamp);
+  frame->SetCompleteFrame(aInputImage._completeFrame);
+
+  GMPVideoFrameType ft;
+  int32_t ret = WebrtcFrameTypeToGmpFrameType(aInputImage._frameType, &ft);
+  if (ret != WEBRTC_VIDEO_CODEC_OK) {
+    return ret;
+  }
+
+  // Bug XXXXXX: Set codecSpecific info
+  GMPCodecSpecificInfo info;
+  memset(&info, 0, sizeof(info));
+
+  err = mGMP->Decode(frame, aMissingFrames, info, aRenderTimeMs);
+  if (err != GMPVideoNoErr) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoDecoder::RegisterDecodeCompleteCallback( webrtc::DecodedImageCallback* aCallback)
+{
+  mCallback = aCallback;
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+
+int32_t
+WebrtcGmpVideoDecoder::Release()
+{
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcGmpVideoDecoder::Reset()
+{
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+void
+WebrtcGmpVideoDecoder::Decoded(GMPVideoi420Frame* aDecodedFrame)
+{
+  if (mCallback) { // paranioa
+    webrtc::I420VideoFrame image;
+    int ret = image.CreateFrame(aDecodedFrame->AllocatedSize(kGMPYPlane),
+                                aDecodedFrame->Buffer(kGMPYPlane),
+                                aDecodedFrame->AllocatedSize(kGMPUPlane),
+                                aDecodedFrame->Buffer(kGMPUPlane),
+                                aDecodedFrame->AllocatedSize(kGMPVPlane),
+                                aDecodedFrame->Buffer(kGMPVPlane),
+                                aDecodedFrame->Width(),
+                                aDecodedFrame->Height(),
+                                aDecodedFrame->Stride(kGMPYPlane),
+                                aDecodedFrame->Stride(kGMPUPlane),
+                                aDecodedFrame->Stride(kGMPVPlane));
+    if (ret != 0) {
+      return;
+    }
+    image.set_timestamp(aDecodedFrame->Timestamp());
+    image.set_render_time_ms(0);
+
+    mCallback->Decoded(image);
+  }
+  aDecodedFrame->Destroy();
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcGmpVideoCodec.h
@@ -0,0 +1,148 @@
+/* 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/. */
+
+// Class templates copied from WebRTC:
+/*
+ *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+
+#ifndef WEBRTCGMPVIDEOCODEC_H_
+#define WEBRTCGMPVIDEOCODEC_H_
+
+#include <iostream>
+#include <queue>
+
+#include "nsThreadUtils.h"
+#include "mozilla/Mutex.h"
+
+#include "mozIGeckoMediaPluginService.h"
+#include "MediaConduitInterface.h"
+#include "AudioConduit.h"
+#include "VideoConduit.h"
+#include "webrtc/modules/video_coding/codecs/interface/video_codec_interface.h"
+
+#include "gmp-video-host.h"
+#include "gmp-video-encode.h"
+#include "gmp-video-decode.h"
+#include "gmp-video-frame-i420.h"
+#include "gmp-video-frame-encoded.h"
+
+#include "WebrtcGmpVideoCodec.h"
+
+namespace mozilla {
+
+class WebrtcGmpVideoEncoder : public WebrtcVideoEncoder,
+                              public GMPEncoderCallback
+{
+public:
+  WebrtcGmpVideoEncoder();
+  virtual ~WebrtcGmpVideoEncoder() {}
+
+  // Implement VideoEncoder interface.
+  virtual int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings,
+                             int32_t aNumberOfCores,
+                             uint32_t aMaxPayloadSize);
+
+  virtual int32_t Encode(const webrtc::I420VideoFrame& aInputImage,
+                         const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                         const std::vector<webrtc::VideoFrameType>* aFrameTypes);
+
+  virtual int32_t RegisterEncodeCompleteCallback(
+    webrtc::EncodedImageCallback* aCallback) MOZ_OVERRIDE;
+
+  virtual int32_t Release();
+
+  virtual int32_t SetChannelParameters(uint32_t aPacketLoss,
+                                       int aRTT) MOZ_OVERRIDE;
+
+  virtual int32_t SetRates(uint32_t aNewBitRate,
+                           uint32_t aFrameRate) MOZ_OVERRIDE;
+
+  // GMPEncoderCallback virtual functions.
+  virtual void Encoded(GMPVideoEncodedFrame* aEncodedFrame,
+                       const GMPCodecSpecificInfo& aCodecSpecificInfo) MOZ_OVERRIDE;
+
+
+private:
+  virtual int32_t InitEncode_g(const webrtc::VideoCodec* aCodecSettings,
+                               int32_t aNumberOfCores,
+                               uint32_t aMaxPayloadSize);
+
+  virtual int32_t Encode_g(const webrtc::I420VideoFrame* aInputImage,
+                           const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                           const std::vector<webrtc::VideoFrameType>* aFrameTypes);
+
+  virtual int32_t SetRates_g(uint32_t aNewBitRate,
+                             uint32_t aFrameRate);
+
+  nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+  nsIThread* mGMPThread;
+  GMPVideoEncoder* mGMP;
+  GMPVideoHost* mHost;
+  webrtc::EncodedImageCallback* mCallback;
+};
+
+
+class WebrtcGmpVideoDecoder : public WebrtcVideoDecoder,
+                              public GMPDecoderCallback
+{
+public:
+  WebrtcGmpVideoDecoder();
+  virtual ~WebrtcGmpVideoDecoder() {}
+
+  // Implement VideoDecoder interface.
+  virtual int32_t InitDecode(const webrtc::VideoCodec* aCodecSettings,
+                             int32_t aNumberOfCores);
+  virtual int32_t Decode(const webrtc::EncodedImage& aInputImage,
+                         bool aMissingFrames,
+                         const webrtc::RTPFragmentationHeader* aFragmentation,
+                         const webrtc::CodecSpecificInfo* aCodecSpecificInfo = nullptr,
+                         int64_t aRenderTimeMs = -1) MOZ_OVERRIDE;
+  virtual int32_t RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* aCallback) MOZ_OVERRIDE;
+
+  virtual int32_t Release();
+
+  virtual int32_t Reset() MOZ_OVERRIDE;
+
+  virtual void Decoded(GMPVideoi420Frame* aDecodedFrame) MOZ_OVERRIDE;
+
+  virtual void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) MOZ_OVERRIDE {
+    MOZ_CRASH();
+  }
+
+  virtual void ReceivedDecodedFrame(const uint64_t aPictureId) MOZ_OVERRIDE {
+    MOZ_CRASH();
+  }
+
+  virtual void InputDataExhausted() MOZ_OVERRIDE {
+    MOZ_CRASH();
+  }
+
+private:
+  virtual int32_t InitDecode_g(const webrtc::VideoCodec* aCodecSettings,
+                               int32_t aNumberOfCores);
+
+  virtual int32_t Decode_g(const webrtc::EncodedImage& aInputImage,
+                           bool aMissingFrames,
+                           const webrtc::RTPFragmentationHeader* aFragmentation,
+                           const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+                           int64_t aRenderTimeMs);
+
+  nsCOMPtr<mozIGeckoMediaPluginService> mMPS;
+  nsIThread* mGMPThread;
+  GMPVideoDecoder* mGMP;
+  GMPVideoHost* mHost;
+  webrtc::DecodedImageCallback* mCallback;
+};
+
+}
+
+#endif
--- a/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
+++ b/media/webrtc/signaling/src/media/VcmSIPCCBinding.cpp
@@ -6,16 +6,17 @@
 
 #include "CC_Common.h"
 
 #include "CSFMediaProvider.h"
 #include "CSFAudioTermination.h"
 #include "CSFVideoTermination.h"
 #include "MediaConduitErrors.h"
 #include "MediaConduitInterface.h"
+#include "GmpVideoCodec.h"
 #include "MediaPipeline.h"
 #include "MediaPipelineFilter.h"
 #include "VcmSIPCCBinding.h"
 #include "csf_common.h"
 #include "PeerConnectionImpl.h"
 #include "PeerConnectionMedia.h"
 #include "nsThreadUtils.h"
 #include "transportflow.h"
@@ -2167,44 +2168,50 @@ short vcmTxOpen(cc_mcapid_t mcap_id,
 static int vcmEnsureExternalCodec(
     const mozilla::RefPtr<mozilla::VideoSessionConduit>& conduit,
     mozilla::VideoCodecConfig* config,
     bool send)
 {
   if (config->mName == "VP8") {
     // whitelist internal codecs; I420 will be here once we resolve bug 995884
     return 0;
-#ifdef MOZ_WEBRTC_OMX
   } else if (config->mName == "H264_P0" || config->mName == "H264_P1") {
     // Here we use "I420" to register H.264 because WebRTC.org code has a
     // whitelist of supported video codec in |webrtc::ViECodecImpl::CodecValid()|
     // and will reject registration of those not in it.
     // TODO: bug 995884 to support H.264 in WebRTC.org code.
 
     // Register H.264 codec.
     if (send) {
-      VideoEncoder* encoder = OMXVideoCodec::CreateEncoder(OMXVideoCodec::CodecType::CODEC_H264);
+	VideoEncoder* encoder = nullptr;
+#ifdef MOZ_WEBRTC_OMX
+	encoder = OMXVideoCodec::CreateEncoder(
+	    OMXVideoCodec::CodecType::CODEC_H264);
+#else
+	encoder = mozilla::GmpVideoCodec::CreateEncoder();
+#endif
       if (encoder) {
         return conduit->SetExternalSendCodec(config, encoder);
       } else {
         return kMediaConduitInvalidSendCodec;
       }
     } else {
-      VideoDecoder* decoder = OMXVideoCodec::CreateDecoder(OMXVideoCodec::CodecType::CODEC_H264);
+      VideoDecoder* decoder;
+#ifdef MOZ_WEBRTC_OMX
+      decoder = OMXVideoCodec::CreateDecoder(OMXVideoCodec::CodecType::CODEC_H264);
+#else
+      decoder = mozilla::GmpVideoCodec::CreateDecoder();
+#endif
       if (decoder) {
         return conduit->SetExternalRecvCodec(config, decoder);
       } else {
         return kMediaConduitInvalidReceiveCodec;
       }
     }
     NS_NOTREACHED("Shouldn't get here!");
-#else
-  } else if (config->mName == "I420") {
-    return 0;
-#endif
   } else {
     CSFLogError( logTag, "%s: Invalid video codec configured: %s", __FUNCTION__, config->mName.c_str());
     return send ? kMediaConduitInvalidSendCodec : kMediaConduitInvalidReceiveCodec;
   }
 
   return 0;
 }
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2242,16 +2242,17 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
 
   for (size_t p = 0; p < query->pipelines.Length(); ++p) {
     const MediaPipeline& mp = *query->pipelines[p];
     bool isAudio = (mp.Conduit()->type() == MediaSessionConduit::AUDIO);
     nsString idstr = isAudio ?
         NS_LITERAL_STRING("audio_") : NS_LITERAL_STRING("video_");
     idstr.AppendInt(mp.trackid());
 
+    // Gather pipeline stats.
     switch (mp.direction()) {
       case MediaPipeline::TRANSMIT: {
         nsString localId = NS_LITERAL_STRING("outbound_rtp_") + idstr;
         nsString remoteId;
         nsString ssrc;
         unsigned int ssrcval;
         if (mp.Conduit()->GetLocalSSRC(&ssrcval)) {
           ssrc.AppendInt(ssrcval);
@@ -2297,16 +2298,36 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
           s.mType.Construct(RTCStatsType::Outboundrtp);
           if (ssrc.Length()) {
             s.mSsrc.Construct(ssrc);
           }
           s.mRemoteId.Construct(remoteId);
           s.mIsRemote = false;
           s.mPacketsSent.Construct(mp.rtp_packets_sent());
           s.mBytesSent.Construct(mp.rtp_bytes_sent());
+
+          // Lastly, fill in video encoder stats if this is video
+          if (!isAudio) {
+            double framerateMean;
+            double framerateStdDev;
+            double bitrateMean;
+            double bitrateStdDev;
+            uint32_t droppedFrames;
+            if (mp.Conduit()->GetVideoEncoderStats(&framerateMean,
+                                                   &framerateStdDev,
+                                                   &bitrateMean,
+                                                   &bitrateStdDev,
+                                                   &droppedFrames)) {
+              s.mFramerateMean.Construct(framerateMean);
+              s.mFramerateStdDev.Construct(framerateStdDev);
+              s.mBitrateMean.Construct(bitrateMean);
+              s.mBitrateStdDev.Construct(bitrateStdDev);
+              s.mDroppedFrames.Construct(droppedFrames);
+            }
+          }
           query->report->mOutboundRTPStreamStats.Value().AppendElement(s);
         }
         break;
       }
       case MediaPipeline::RECEIVE: {
         nsString localId = NS_LITERAL_STRING("inbound_rtp_") + idstr;
         nsString remoteId;
         nsString ssrc;
@@ -2362,16 +2383,35 @@ PeerConnectionImpl::ExecuteStatsQuery_s(
           int32_t avSyncDelta;
           if (mp.Conduit()->GetAVStats(&jitterBufferDelay,
                                        &playoutBufferDelay,
                                        &avSyncDelta)) {
             s.mMozJitterBufferDelay.Construct(jitterBufferDelay);
             s.mMozAvSyncDelay.Construct(avSyncDelta);
           }
         }
+        // Lastly, fill in video decoder stats if this is video
+        if (!isAudio) {
+          double framerateMean;
+          double framerateStdDev;
+          double bitrateMean;
+          double bitrateStdDev;
+          uint32_t discardedPackets;
+          if (mp.Conduit()->GetVideoDecoderStats(&framerateMean,
+                                                 &framerateStdDev,
+                                                 &bitrateMean,
+                                                 &bitrateStdDev,
+                                                 &discardedPackets)) {
+            s.mFramerateMean.Construct(framerateMean);
+            s.mFramerateStdDev.Construct(framerateStdDev);
+            s.mBitrateMean.Construct(bitrateMean);
+            s.mBitrateStdDev.Construct(bitrateStdDev);
+            s.mDiscardedPackets.Construct(discardedPackets);
+          }
+        }
         query->report->mInboundRTPStreamStats.Value().AppendElement(s);
         break;
       }
     }
   }
 
   // Gather stats from ICE
   for (size_t s = 0; s != query->streams.Length(); ++s) {
--- a/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
+++ b/media/webrtc/signaling/src/peerconnection/WebrtcGlobalInformation.cpp
@@ -237,16 +237,18 @@ struct StreamResult {
   uint8_t candidateTypeBitpattern;
   bool streamSucceeded;
 };
 
 static void StoreLongTermICEStatisticsImpl_m(
     nsresult result,
     nsAutoPtr<RTCStatsQuery> query) {
 
+  using namespace Telemetry;
+
   if (NS_FAILED(result) ||
       !query->error.empty() ||
       !query->report->mIceCandidateStats.WasPassed()) {
     return;
   }
 
   query->report->mClosed.Construct(true);
 
@@ -341,16 +343,88 @@ static void StoreLongTermICEStatisticsIm
       Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_SUCCESS,
                             i->second.candidateTypeBitpattern);
     } else {
       Telemetry::Accumulate(Telemetry::WEBRTC_CANDIDATE_TYPES_GIVEN_FAILURE,
                             i->second.candidateTypeBitpattern);
     }
   }
 
+  // Beyond ICE, accumulate telemetry for various PER_CALL settings here.
+
+  if (query->report->mOutboundRTPStreamStats.WasPassed()) {
+    auto& array = query->report->mOutboundRTPStreamStats.Value();
+    for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
+      auto& s = array[i];
+      bool isVideo = (s.mId.Value().Find("video") != -1);
+      if (!isVideo || s.mIsRemote) {
+        continue;
+      }
+      if (s.mBitrateMean.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_AVG_PER_CALL_KBPS,
+                   uint32_t(s.mBitrateMean.Value() / 1000));
+      }
+      if (s.mBitrateStdDev.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_ENCODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+                   uint32_t(s.mBitrateStdDev.Value() / 1000));
+      }
+      if (s.mFramerateMean.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_AVG_PER_CALL,
+                   uint32_t(s.mFramerateMean.Value()));
+      }
+      if (s.mFramerateStdDev.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_ENCODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+                   uint32_t(s.mFramerateStdDev.Value() * 10));
+      }
+      if (s.mDroppedFrames.WasPassed()) {
+        double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60;
+        if (mins > 0) {
+          Accumulate(WEBRTC_VIDEO_ENCODER_DROPPED_FRAMES_PER_CALL_FPM,
+                     uint32_t(double(s.mDroppedFrames.Value()) / mins));
+        }
+      }
+    }
+  }
+
+  if (query->report->mInboundRTPStreamStats.WasPassed()) {
+    auto& array = query->report->mInboundRTPStreamStats.Value();
+    for (decltype(array.Length()) i = 0; i < array.Length(); i++) {
+      auto& s = array[i];
+      bool isVideo = (s.mId.Value().Find("video") != -1);
+      if (!isVideo || s.mIsRemote) {
+        continue;
+      }
+      if (s.mBitrateMean.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_AVG_PER_CALL_KBPS,
+                   uint32_t(s.mBitrateMean.Value() / 1000));
+      }
+      if (s.mBitrateStdDev.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_DECODER_BITRATE_STD_DEV_PER_CALL_KBPS,
+                   uint32_t(s.mBitrateStdDev.Value() / 1000));
+      }
+      if (s.mFramerateMean.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_AVG_PER_CALL,
+                   uint32_t(s.mFramerateMean.Value()));
+      }
+      if (s.mFramerateStdDev.WasPassed()) {
+        Accumulate(WEBRTC_VIDEO_DECODER_FRAMERATE_10X_STD_DEV_PER_CALL,
+                   uint32_t(s.mFramerateStdDev.Value() * 10));
+      }
+      if (s.mDiscardedPackets.WasPassed()) {
+        double mins = (TimeStamp::Now() - query->iceStartTime).ToSeconds() / 60;
+        if (mins > 0) {
+          Accumulate(WEBRTC_VIDEO_DECODER_DISCARDED_PACKETS_PER_CALL_PPM,
+                     uint32_t(double(s.mDiscardedPackets.Value()) / mins));
+        }
+      }
+    }
+  }
+
+  // Finally, store the stats
+
   PeerConnectionCtx *ctx = GetPeerConnectionCtx();
   if (ctx) {
     ctx->mStatsForClosedPeerConnections.AppendElement(*query->report);
   }
 }
 
 static void GetStatsForLongTermStorage_s(
     nsAutoPtr<RTCStatsQuery> query) {
--- a/media/webrtc/signaling/test/mediaconduit_unittests.cpp
+++ b/media/webrtc/signaling/test/mediaconduit_unittests.cpp
@@ -7,24 +7,32 @@
 #include <fstream>
 #include <unistd.h>
 #include <vector>
 #include <math.h>
 
 using namespace std;
 
 #include "mozilla/Scoped.h"
+#include "mozilla/SyncRunnable.h"
 #include <MediaConduitInterface.h>
 #include "nsIEventTarget.h"
 #include "FakeMediaStreamsImpl.h"
+#include "GmpVideoCodec.h"
+#include "nsThreadUtils.h"
+#include "runnable_utils.h"
 
 #define GTEST_HAS_RTTI 0
 #include "gtest/gtest.h"
 #include "gtest_utils.h"
 
+nsCOMPtr<nsIThread> gMainThread;
+nsCOMPtr<nsIThread> gGtestThread;
+bool gTestsComplete = false;
+
 #include "mtransport_test_utils.h"
 MtransportTestUtils *test_utils;
 
 //Video Frame Color
 const int COLOR = 0x80; //Gray
 
 //MWC RNG of George Marsaglia
 //taken from xiph.org
@@ -43,65 +51,75 @@ struct VideoTestStats
 {
  int numRawFramesInserted;
  int numFramesRenderedSuccessfully;
  int numFramesRenderedWrongly;
 };
 
 VideoTestStats vidStatsGlobal={0,0,0};
 
-
 /**
  * A Dummy Video Conduit Tester.
  * The test-case inserts a 640*480 grey imagerevery 33 milliseconds
  * to the video-conduit for encoding and transporting.
  */
 
 class VideoSendAndReceive
 {
 public:
   VideoSendAndReceive():width(640),
-                        height(480)
+                        height(480),
+			rate(30)
   {
   }
 
   ~VideoSendAndReceive()
   {
   }
 
+  void SetDimensions(int w, int h)
+  {
+    width = w;
+    height = h;
+  }
+  void SetRate(int r) {
+    rate = r;
+  }
   void Init(mozilla::RefPtr<mozilla::VideoSessionConduit> aSession)
   {
         mSession = aSession;
+        mLen = ((width * height) * 3 / 2);
+        mFrame = (uint8_t*) PR_MALLOC(mLen);
+        memset(mFrame, COLOR, mLen);
+        numFrames = 121;
   }
+
   void GenerateAndReadSamples()
   {
-
-    int len = ((width * height) * 3 / 2);
-    uint8_t* frame = (uint8_t*) PR_MALLOC(len);
-    int numFrames = 121;
-    memset(frame, COLOR, len);
-
     do
     {
-      mSession->SendVideoFrame((unsigned char*)frame,
-                                len,
+      mSession->SendVideoFrame((unsigned char*)mFrame,
+                                mLen,
                                 width,
                                 height,
                                 mozilla::kVideoI420,
                                 0);
-      PR_Sleep(PR_MillisecondsToInterval(33));
+      PR_Sleep(PR_MillisecondsToInterval(1000/rate));
       vidStatsGlobal.numRawFramesInserted++;
       numFrames--;
     } while(numFrames >= 0);
-    PR_Free(frame);
   }
 
 private:
 mozilla::RefPtr<mozilla::VideoSessionConduit> mSession;
+mozilla::ScopedDeletePtr<uint8_t> mFrame;
+int mLen;
 int width, height;
+int rate;
+int numFrames;
 };
 
 
 
 /**
  * A Dummy AudioConduit Tester
  * The test reads PCM samples of a standard test file and
  * passws to audio-conduit for encoding, RTPfication and
@@ -262,27 +280,27 @@ void AudioSendAndReceive::GenerateMusic(
     if(i%6==0)j++;
   }
   cerr << "Generating Input Music Done " << endl;
 }
 
 //Hardcoded for 16 bit samples for now
 void AudioSendAndReceive::GenerateAndReadSamples()
 {
-   int16_t audioInput[PLAYOUT_SAMPLE_LENGTH];
-   int16_t audioOutput[PLAYOUT_SAMPLE_LENGTH];
+   mozilla::ScopedDeletePtr<int16_t> audioInput(new int16_t [PLAYOUT_SAMPLE_LENGTH]);
+   mozilla::ScopedDeletePtr<int16_t> audioOutput(new int16_t [PLAYOUT_SAMPLE_LENGTH]);
    short* inbuf;
    int sampleLengthDecoded = 0;
    unsigned int SAMPLES = (PLAYOUT_SAMPLE_FREQUENCY * 10); //10 seconds
    int CHANNELS = 1; //mono audio
-   int sampleLengthInBytes = sizeof(audioInput);
+   int sampleLengthInBytes = sizeof(int16_t) * PLAYOUT_SAMPLE_LENGTH;
    //generated audio buffer
    inbuf = (short *)moz_xmalloc(sizeof(short)*SAMPLES*CHANNELS);
-   memset(audioInput,0,sampleLengthInBytes);
-   memset(audioOutput,0,sampleLengthInBytes);
+   memset(audioInput.get(),0,sampleLengthInBytes);
+   memset(audioOutput.get(),0,sampleLengthInBytes);
    MOZ_ASSERT(SAMPLES <= PLAYOUT_SAMPLE_LENGTH);
 
    FILE* inFile = fopen( iFile.c_str(), "wb+");
    if(!inFile) {
      cerr << "Input File Creation Failed " << endl;
      return;
    }
 
@@ -298,37 +316,37 @@ void AudioSendAndReceive::GenerateAndRea
    fwrite(inbuf,1,SAMPLES*sizeof(inbuf[0])*CHANNELS,inFile);
    FinishWaveHeader(inFile);
    fclose(inFile);
 
    WriteWaveHeader(PLAYOUT_SAMPLE_FREQUENCY, 1, outFile);
    unsigned int numSamplesReadFromInput = 0;
    do
    {
-    if(!memcpy(audioInput, inbuf, sampleLengthInBytes))
+    if(!memcpy(audioInput.get(), inbuf, sampleLengthInBytes))
     {
       return;
     }
 
     numSamplesReadFromInput += PLAYOUT_SAMPLE_LENGTH;
     inbuf += PLAYOUT_SAMPLE_LENGTH;
 
-    mSession->SendAudioFrame(audioInput,
+    mSession->SendAudioFrame(audioInput.get(),
                              PLAYOUT_SAMPLE_LENGTH,
                              PLAYOUT_SAMPLE_FREQUENCY,10);
 
     PR_Sleep(PR_MillisecondsToInterval(10));
-    mOtherSession->GetAudioFrame(audioOutput, PLAYOUT_SAMPLE_FREQUENCY,
+    mOtherSession->GetAudioFrame(audioOutput.get(), PLAYOUT_SAMPLE_FREQUENCY,
                                  10, sampleLengthDecoded);
     if(sampleLengthDecoded == 0)
     {
       cerr << " Zero length Sample " << endl;
     }
 
-    int wrote_  = fwrite (audioOutput, 1 , sampleLengthInBytes, outFile);
+    int wrote_  = fwrite (audioOutput.get(), 1 , sampleLengthInBytes, outFile);
     if(wrote_ != sampleLengthInBytes)
     {
       cerr << "Couldn't Write " << sampleLengthInBytes << "bytes" << endl;
       break;
     }
    }while(numSamplesReadFromInput < SAMPLES);
 
    FinishWaveHeader(outFile);
@@ -391,32 +409,32 @@ public:
       }
     }
    return 0;
  }
 
 };
 
 /**
- *  Fake Audio and Video External Transport Class
+ *  Webrtc Audio and Video External Transport Class
  *  The functions in this class will be invoked by the conduit
  *  when it has RTP/RTCP frame to transmit.
  *  For everty RTP/RTCP frame we receive, we pass it back
  *  to the conduit for eventual decoding and rendering.
  */
-class FakeMediaTransport : public mozilla::TransportInterface
+class WebrtcMediaTransport : public mozilla::TransportInterface
 {
 public:
-  FakeMediaTransport():numPkts(0),
+  WebrtcMediaTransport():numPkts(0),
                        mAudio(false),
                        mVideo(false)
   {
   }
 
-  ~FakeMediaTransport()
+  ~WebrtcMediaTransport()
   {
   }
 
   virtual nsresult SendRtpPacket(const void* data, int len)
   {
     ++numPkts;
     if(mAudio)
     {
@@ -470,41 +488,65 @@ private:
 };
 
 
 namespace {
 
 class TransportConduitTest : public ::testing::Test
 {
  public:
+
   TransportConduitTest()
   {
     //input and output file names
     iAudiofilename = "input.wav";
     oAudiofilename = "recorded.wav";
   }
 
   ~TransportConduitTest()
   {
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            mozilla::WrapRunnable(
+                                                this,
+                                                &TransportConduitTest::SelfDestruct));
+  }
+
+  void SelfDestruct() {
+    mAudioSession = nullptr;
+    mAudioSession2 = nullptr;
+    mAudioTransport = nullptr;
+
+    mVideoSession = nullptr;
+    mVideoSession2 = nullptr;
+    mVideoRenderer = nullptr;
+    mVideoTransport = nullptr;
   }
 
   //1. Dump audio samples to dummy external transport
   void TestDummyAudioAndTransport()
   {
     //get pointer to AudioSessionConduit
     int err=0;
-    mAudioSession = mozilla::AudioSessionConduit::Create(nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::AudioSessionConduit::Create,
+                                                nullptr,
+                                                &mAudioSession));
     if( !mAudioSession )
       ASSERT_NE(mAudioSession, (void*)nullptr);
 
-    mAudioSession2 = mozilla::AudioSessionConduit::Create(nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::AudioSessionConduit::Create,
+                                                nullptr,
+                                                &mAudioSession2));
     if( !mAudioSession2 )
       ASSERT_NE(mAudioSession2, (void*)nullptr);
 
-    FakeMediaTransport* xport = new FakeMediaTransport();
+    WebrtcMediaTransport* xport = new WebrtcMediaTransport();
     ASSERT_NE(xport, (void*)nullptr);
     xport->SetAudioSession(mAudioSession, mAudioSession2);
     mAudioTransport = xport;
 
     // attach the transport to audio-conduit
     err = mAudioSession->AttachTransport(mAudioTransport);
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
     err = mAudioSession2->AttachTransport(mAudioTransport);
@@ -540,33 +582,45 @@ class TransportConduitTest : public ::te
     PR_Sleep(PR_SecondsToInterval(2));
     cerr << "   ******************************************************** " << endl;
     cerr << "    Input Audio  File                " << iAudiofilename << endl;
     cerr << "    Output Audio File                " << oAudiofilename << endl;
     cerr << "   ******************************************************** " << endl;
   }
 
   //2. Dump audio samples to dummy external transport
-  void TestDummyVideoAndTransport()
+  void TestDummyVideoAndTransport(bool send_vp8 = true, const char *source_file = nullptr)
   {
     int err = 0;
     //get pointer to VideoSessionConduit
-    mVideoSession = mozilla::VideoSessionConduit::Create(nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::VideoSessionConduit::Create,
+                                                nullptr,
+                                                &mVideoSession));
     if( !mVideoSession )
       ASSERT_NE(mVideoSession, (void*)nullptr);
 
    // This session is for other one
-    mVideoSession2 = mozilla::VideoSessionConduit::Create(nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::VideoSessionConduit::Create,
+                                                nullptr,
+                                                &mVideoSession2));
     if( !mVideoSession2 )
       ASSERT_NE(mVideoSession2,(void*)nullptr);
 
+    if (!send_vp8) {
+      SetGmpCodecs();
+    }
+
     mVideoRenderer = new DummyVideoTarget();
     ASSERT_NE(mVideoRenderer, (void*)nullptr);
 
-    FakeMediaTransport* xport = new FakeMediaTransport();
+    WebrtcMediaTransport* xport = new WebrtcMediaTransport();
     ASSERT_NE(xport, (void*)nullptr);
     xport->SetVideoSession(mVideoSession,mVideoSession2);
     mVideoTransport = xport;
 
     // attach the transport and renderer to video-conduit
     err = mVideoSession2->AttachRenderer(mVideoRenderer);
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
     err = mVideoSession->AttachTransport(mVideoTransport);
@@ -578,56 +632,77 @@ class TransportConduitTest : public ::te
     mozilla::VideoCodecConfig cinst1(120, "VP8", 0);
     mozilla::VideoCodecConfig cinst2(124, "I420", 0);
 
 
     std::vector<mozilla::VideoCodecConfig* > rcvCodecList;
     rcvCodecList.push_back(&cinst1);
     rcvCodecList.push_back(&cinst2);
 
-    err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
+    err = mVideoSession->ConfigureSendMediaCodec(
+        send_vp8 ? &cinst1 : &cinst2);
+
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
 
+    err = mVideoSession2->ConfigureSendMediaCodec(
+        send_vp8 ? &cinst1 : &cinst2);
+
+    ASSERT_EQ(mozilla::kMediaConduitNoError, err);
     err = mVideoSession2->ConfigureRecvMediaCodecs(rcvCodecList);
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
 
     //start generating samples
     cerr << "   *************************************************" << endl;
     cerr << "    Starting the Video Sample Generation " << endl;
     cerr << "   *************************************************" << endl;
     PR_Sleep(PR_SecondsToInterval(2));
     videoTester.Init(mVideoSession);
     videoTester.GenerateAndReadSamples();
     PR_Sleep(PR_SecondsToInterval(2));
+
     cerr << "   **************************************************" << endl;
     cerr << "    Done With The Testing  " << endl;
     cerr << "    VIDEO TEST STATS  "  << endl;
     cerr << "    Num Raw Frames Inserted: "<<
                                         vidStatsGlobal.numRawFramesInserted << endl;
     cerr << "    Num Frames Successfully Rendered: "<<
                                         vidStatsGlobal.numFramesRenderedSuccessfully << endl;
     cerr << "    Num Frames Wrongly Rendered: "<<
                                         vidStatsGlobal.numFramesRenderedWrongly << endl;
 
     cerr << "    Done With The Testing  " << endl;
 
     cerr << "   **************************************************" << endl;
     ASSERT_EQ(0, vidStatsGlobal.numFramesRenderedWrongly);
-    ASSERT_EQ(vidStatsGlobal.numRawFramesInserted,
-        vidStatsGlobal.numFramesRenderedSuccessfully);
+    if (send_vp8) {
+	ASSERT_EQ(vidStatsGlobal.numRawFramesInserted,
+		  vidStatsGlobal.numFramesRenderedSuccessfully);
+    }
+    else {
+	// Allow some fudge because there seems to be some buffering.
+	// TODO(ekr@rtfm.com): Fix this.
+	ASSERT_GE(vidStatsGlobal.numRawFramesInserted,
+		  vidStatsGlobal.numFramesRenderedSuccessfully);
+	ASSERT_LE(vidStatsGlobal.numRawFramesInserted,
+		  vidStatsGlobal.numFramesRenderedSuccessfully + 2);
+    }
   }
 
  void TestVideoConduitCodecAPI()
   {
     int err = 0;
-    mozilla::RefPtr<mozilla::VideoSessionConduit> mVideoSession;
+    mozilla::RefPtr<mozilla::VideoSessionConduit> videoSession;
     //get pointer to VideoSessionConduit
-    mVideoSession = mozilla::VideoSessionConduit::Create(nullptr);
-    if( !mVideoSession )
-      ASSERT_NE(mVideoSession, (void*)nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::VideoSessionConduit::Create,
+                                                nullptr,
+                                                &videoSession));
+    if( !videoSession )
+      ASSERT_NE(videoSession, (void*)nullptr);
 
     //Test Configure Recv Codec APIS
     cerr << "   *************************************************" << endl;
     cerr << "    Test Receive Codec Configuration API Now " << endl;
     cerr << "   *************************************************" << endl;
 
     std::vector<mozilla::VideoCodecConfig* > rcvCodecList;
 
@@ -635,17 +710,17 @@ class TransportConduitTest : public ::te
     cerr << "   *************************************************" << endl;
     cerr << "    1. Same Codec (VP8) Repeated Twice " << endl;
     cerr << "   *************************************************" << endl;
 
     mozilla::VideoCodecConfig cinst1(120, "VP8", 0);
     mozilla::VideoCodecConfig cinst2(120, "VP8", 0);
     rcvCodecList.push_back(&cinst1);
     rcvCodecList.push_back(&cinst2);
-    err = mVideoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+    err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
     EXPECT_NE(err,mozilla::kMediaConduitNoError);
     rcvCodecList.pop_back();
     rcvCodecList.pop_back();
 
 
     PR_Sleep(PR_SecondsToInterval(2));
     cerr << "   *************************************************" << endl;
     cerr << "    2. Codec With Invalid Payload Names " << endl;
@@ -654,63 +729,67 @@ class TransportConduitTest : public ::te
     cerr << "   Setting payload 2 with name of zero length" << endl;
 
     mozilla::VideoCodecConfig cinst3(124, "I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676", 0);
     mozilla::VideoCodecConfig cinst4(124, "", 0);
 
     rcvCodecList.push_back(&cinst3);
     rcvCodecList.push_back(&cinst4);
 
-    err = mVideoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+    err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
     EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
     rcvCodecList.pop_back();
     rcvCodecList.pop_back();
 
 
     PR_Sleep(PR_SecondsToInterval(2));
     cerr << "   *************************************************" << endl;
     cerr << "    3. Null Codec Parameter  " << endl;
     cerr << "   *************************************************" << endl;
 
     rcvCodecList.push_back(0);
 
-    err = mVideoSession->ConfigureRecvMediaCodecs(rcvCodecList);
+    err = videoSession->ConfigureRecvMediaCodecs(rcvCodecList);
     EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
     rcvCodecList.pop_back();
 
     cerr << "   *************************************************" << endl;
     cerr << "    Test Send Codec Configuration API Now " << endl;
     cerr << "   *************************************************" << endl;
 
     cerr << "   *************************************************" << endl;
     cerr << "    1. Same Codec (VP8) Repeated Twice " << endl;
     cerr << "   *************************************************" << endl;
 
 
-    err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
+    err = videoSession->ConfigureSendMediaCodec(&cinst1);
     EXPECT_EQ(mozilla::kMediaConduitNoError, err);
-    err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
+    err = videoSession->ConfigureSendMediaCodec(&cinst1);
     EXPECT_EQ(mozilla::kMediaConduitCodecInUse, err);
 
 
     cerr << "   *************************************************" << endl;
     cerr << "    2. Codec With Invalid Payload Names " << endl;
     cerr << "   *************************************************" << endl;
     cerr << "   Setting payload with name: I4201234tttttthhhyyyy89087987y76t567r7756765rr6u6676" << endl;
 
-    err = mVideoSession->ConfigureSendMediaCodec(&cinst3);
+    err = videoSession->ConfigureSendMediaCodec(&cinst3);
     EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
 
     cerr << "   *************************************************" << endl;
     cerr << "    3. Null Codec Parameter  " << endl;
     cerr << "   *************************************************" << endl;
 
-    err = mVideoSession->ConfigureSendMediaCodec(nullptr);
+    err = videoSession->ConfigureSendMediaCodec(nullptr);
     EXPECT_TRUE(err != mozilla::kMediaConduitNoError);
 
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnable(
+                                                videoSession.forget().drop(),
+                                                &mozilla::VideoSessionConduit::Release));
   }
 
   void DumpMaxFs(int orig_width, int orig_height, int max_fs,
                  int new_width, int new_height)
   {
     cerr << "Applying max_fs=" << max_fs << " to input resolution " <<
                  orig_width << "x" << orig_height << endl;
     cerr << "New resolution: " << new_width << "x" << new_height << endl;
@@ -719,17 +798,21 @@ class TransportConduitTest : public ::te
 
   // Calculate new resolution for sending video by applying max-fs constraint.
   void GetVideoResolutionWithMaxFs(int orig_width, int orig_height, int max_fs,
                                    int *new_width, int *new_height)
   {
     int err = 0;
 
     // Get pointer to VideoSessionConduit.
-    mVideoSession = mozilla::VideoSessionConduit::Create(nullptr);
+    mozilla::SyncRunnable::DispatchToThread(gMainThread,
+                                            WrapRunnableNMRet(
+                                                &mozilla::VideoSessionConduit::Create,
+                                                nullptr,
+                                                &mVideoSession));
     if( !mVideoSession )
       ASSERT_NE(mVideoSession, (void*)nullptr);
 
     // Configure send codecs on the conduit.
     mozilla::VideoCodecConfig cinst1(120, "VP8", 0, max_fs, 0);
 
     err = mVideoSession->ConfigureSendMediaCodec(&cinst1);
     ASSERT_EQ(mozilla::kMediaConduitNoError, err);
@@ -856,30 +939,41 @@ class TransportConduitTest : public ::te
       if ((width & 1) || (height & 1)) {
         DumpMaxFs(orig_width, orig_height, max_fs, width, height);
         ADD_FAILURE();
       }
     }
     cerr << endl;
  }
 
-private:
+  void SetGmpCodecs() {
+    mExternalEncoder = mozilla::GmpVideoCodec::CreateEncoder();
+    mExternalDecoder = mozilla::GmpVideoCodec::CreateDecoder();
+    mozilla::VideoCodecConfig config(124, "H264", 0);
+    mVideoSession->SetExternalSendCodec(&config, mExternalEncoder);
+    mVideoSession2->SetExternalRecvCodec(&config, mExternalDecoder);
+  }
+
+ private:
   //Audio Conduit Test Objects
   mozilla::RefPtr<mozilla::AudioSessionConduit> mAudioSession;
   mozilla::RefPtr<mozilla::AudioSessionConduit> mAudioSession2;
   mozilla::RefPtr<mozilla::TransportInterface> mAudioTransport;
   AudioSendAndReceive audioTester;
 
   //Video Conduit Test Objects
   mozilla::RefPtr<mozilla::VideoSessionConduit> mVideoSession;
   mozilla::RefPtr<mozilla::VideoSessionConduit> mVideoSession2;
   mozilla::RefPtr<mozilla::VideoRenderer> mVideoRenderer;
   mozilla::RefPtr<mozilla::TransportInterface> mVideoTransport;
   VideoSendAndReceive videoTester;
 
+  mozilla::VideoEncoder* mExternalEncoder;
+  mozilla::VideoDecoder* mExternalDecoder;
+
   std::string fileToPlay;
   std::string fileToRecord;
   std::string iAudiofilename;
   std::string oAudiofilename;
 };
 
 
 // Test 1: Test Dummy External Xport
@@ -887,31 +981,83 @@ TEST_F(TransportConduitTest, TestDummyAu
   TestDummyAudioAndTransport();
 }
 
 // Test 2: Test Dummy External Xport
 TEST_F(TransportConduitTest, TestDummyVideoWithTransport) {
   TestDummyVideoAndTransport();
  }
 
+TEST_F(TransportConduitTest, TestVideoConduitExternalCodec) {
+  TestDummyVideoAndTransport(false);
+}
+
 TEST_F(TransportConduitTest, TestVideoConduitCodecAPI) {
   TestVideoConduitCodecAPI();
  }
 
 TEST_F(TransportConduitTest, TestVideoConduitMaxFs) {
   TestVideoConduitMaxFs();
  }
 
 }  // end namespace
 
+static int test_result;
+bool test_finished = false;
+
+
+
+// This exists to send as an event to trigger shutdown.
+static void tests_complete() {
+  gTestsComplete = true;
+}
+
+// The GTest thread runs this instead of the main thread so it can
+// do things like ASSERT_TRUE_WAIT which you could not do on the main thread.
+static int gtest_main(int argc, char **argv) {
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  ::testing::InitGoogleTest(&argc, argv);
+
+  int result = RUN_ALL_TESTS();
+
+  // Set the global shutdown flag and tickle the main thread
+  // The main thread did not go through Init() so calling Shutdown()
+  // on it will not work.
+  gMainThread->Dispatch(mozilla::WrapRunnableNM(tests_complete), NS_DISPATCH_SYNC);
+
+  return result;
+}
+
 int main(int argc, char **argv)
 {
   // This test can cause intermittent oranges on the builders
   CHECK_ENVIRONMENT_FLAG("MOZ_WEBRTC_MEDIACONDUIT_TESTS")
 
   test_utils = new MtransportTestUtils();
-  ::testing::InitGoogleTest(&argc, argv);
-  int rv = RUN_ALL_TESTS();
+
+  // Set the main thread global which is this thread.
+  nsIThread *thread;
+  NS_GetMainThread(&thread);
+  gMainThread = thread;
+
+  // Now create the GTest thread and run all of the tests on it
+  // When it is complete it will set gTestsComplete
+  NS_NewNamedThread("gtest_thread", &thread);
+  gGtestThread = thread;
+
+  int result;
+  gGtestThread->Dispatch(
+    mozilla::WrapRunnableNMRet(gtest_main, argc, argv, &result), NS_DISPATCH_NORMAL);
+
+  // Here we handle the event queue for dispatches to the main thread
+  // When the GTest thread is complete it will send one more dispatch
+  // with gTestsComplete == true.
+  while (!gTestsComplete && NS_ProcessNextEvent());
+
+  gGtestThread->Shutdown();
+
   delete test_utils;
-  return rv;
+  return test_result;
 }
 
 
+
--- a/media/webrtc/trunk/webrtc/common_types.h
+++ b/media/webrtc/trunk/webrtc/common_types.h
@@ -548,16 +548,26 @@ enum RawVideoType
     kVideoARGB1555 = 9,
     kVideoMJPEG    = 10,
     kVideoNV12     = 11,
     kVideoNV21     = 12,
     kVideoBGRA     = 13,
     kVideoUnknown  = 99
 };
 
+enum VideoReceiveState
+{
+  kReceiveStateInitial,            // No video decoded yet
+  kReceiveStateNormal,
+  kReceiveStatePreemptiveNACK,     // NACK sent for missing packet, no decode stall/fail yet
+  kReceiveStateWaitingKey,         // Decoding stalled, waiting for keyframe or NACK
+  kReceiveStateDecodingWithErrors, // Decoding with errors, waiting for keyframe or NACK
+  kReceiveStateNoIncoming,         // No errors, but no incoming video since last decode
+};
+
 // Video codec
 enum { kConfigParameterSize = 128};
 enum { kPayloadNameSize = 32};
 enum { kMaxSimulcastStreams = 4};
 enum { kMaxTemporalStreams = 4};
 
 enum VideoCodecComplexity
 {
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/interface/video_coding.h
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/interface/video_coding.h
@@ -408,16 +408,29 @@ public:
     // Input:
     //              - callback      : The callback to be registered in the VCM.
     //
     // Return value     : VCM_OK,     on success.
     //                    <0,              on error.
     virtual int32_t RegisterPacketRequestCallback(
                                         VCMPacketRequestCallback* callback) = 0;
 
+    // Register a receive state change callback. This callback will be called when the
+    // module state has changed
+    //
+    // Input:
+    //      - callback      : The callback object to be used by the module when
+    //                        the receiver decode state changes.
+    //                        De-register with a NULL pointer.
+    //
+    // Return value      : VCM_OK, on success.
+    //                     < 0,         on error.
+    virtual int32_t RegisterReceiveStateCallback(
+                                  VCMReceiveStateCallback* callback) = 0;
+
     // Waits for the next frame in the jitter buffer to become complete
     // (waits no longer than maxWaitTimeMs), then passes it to the decoder for decoding.
     // Should be called as often as possible to get the most out of the decoder.
     //
     // Return value      : VCM_OK, on success.
     //                     < 0,         on error.
     virtual int32_t Decode(uint16_t maxWaitTimeMs = 200) = 0;
 
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/interface/video_coding_defines.h
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/interface/video_coding_defines.h
@@ -170,16 +170,27 @@ class VCMPacketRequestCallback {
   virtual int32_t ResendPackets(const uint16_t* sequenceNumbers,
                                       uint16_t length) = 0;
 
  protected:
   virtual ~VCMPacketRequestCallback() {
   }
 };
 
+// Callback class used for telling the user about the state of the decoder & jitter buffer.
+//
+class VCMReceiveStateCallback {
+ public:
+  virtual void ReceiveStateChange(VideoReceiveState state) = 0;
+
+ protected:
+  virtual ~VCMReceiveStateCallback() {
+  }
+};
+
 // Callback used to inform the user of the the desired resolution
 // as subscribed by Media Optimization (Quality Modes)
 class VCMQMSettingsCallback {
  public:
   virtual int32_t SetVideoQMSettings(const uint32_t frameRate,
                                            const uint32_t width,
                                            const uint32_t height) = 0;
 
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/receiver.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/receiver.cc
@@ -33,16 +33,17 @@ VCMReceiver::VCMReceiver(VCMTiming* timi
       vcm_id_(vcm_id),
       clock_(clock),
       receiver_id_(receiver_id),
       master_(master),
       jitter_buffer_(clock_, event_factory, vcm_id, receiver_id, master),
       timing_(timing),
       render_wait_event_(event_factory->CreateEvent()),
       state_(kPassive),
+      receiveState_(kReceiveStateInitial),
       max_video_delay_ms_(kMaxVideoDelayMs) {}
 
 VCMReceiver::~VCMReceiver() {
   render_wait_event_->Set();
   delete crit_sect_;
 }
 
 void VCMReceiver::Reset() {
@@ -50,18 +51,20 @@ void VCMReceiver::Reset() {
   if (!jitter_buffer_.Running()) {
     jitter_buffer_.Start();
   } else {
     jitter_buffer_.Flush();
   }
   render_wait_event_->Reset();
   if (master_) {
     state_ = kReceiving;
+    receiveState_ = kReceiveStateInitial;
   } else {
     state_ = kPassive;
+    receiveState_ = kReceiveStateInitial;
   }
 }
 
 int32_t VCMReceiver::Initialize() {
   Reset();
   CriticalSectionScoped cs(crit_sect_);
   if (!master_) {
     SetNackMode(kNoNack, -1, -1);
@@ -203,18 +206,19 @@ VCMEncodedFrame* VCMReceiver::FrameForDe
   VCMEncodedFrame* frame = jitter_buffer_.ExtractAndSetDecode(frame_timestamp);
   if (frame == NULL) {
     return NULL;
   }
   frame->SetRenderTime(next_render_time_ms);
   TRACE_EVENT_ASYNC_STEP1("webrtc", "Video", frame->TimeStamp(),
                           "SetRenderTS", "render_time", next_render_time_ms);
   if (dual_receiver != NULL) {
-    dual_receiver->UpdateState(*frame);
+    dual_receiver->UpdateDualState(*frame);
   }
+  UpdateReceiveState(*frame);
   if (!frame->Complete()) {
     // Update stats for incomplete frames.
     bool retransmitted = false;
     const int64_t last_packet_time_ms =
         jitter_buffer_.LastPacketTime(frame, &retransmitted);
     if (last_packet_time_ms >= 0 && !retransmitted) {
       // We don't want to include timestamps which have suffered from
       // retransmission here, since we compensate with extra retransmission
@@ -251,16 +255,17 @@ void VCMReceiver::SetNackMode(VCMNackMod
                               int low_rtt_nack_threshold_ms,
                               int high_rtt_nack_threshold_ms) {
   CriticalSectionScoped cs(crit_sect_);
   // Default to always having NACK enabled in hybrid mode.
   jitter_buffer_.SetNackMode(nackMode, low_rtt_nack_threshold_ms,
                              high_rtt_nack_threshold_ms);
   if (!master_) {
     state_ = kPassive;  // The dual decoder defaults to passive.
+    receiveState_ = kReceiveStateWaitingKey; // XXX Initial
   }
 }
 
 void VCMReceiver::SetNackSettings(size_t max_nack_list_size,
                                   int max_packet_age_to_nack,
                                   int max_incomplete_time_ms) {
   jitter_buffer_.SetNackSettings(max_nack_list_size,
                                  max_packet_age_to_nack,
@@ -310,16 +315,21 @@ void VCMReceiver::CopyJitterBufferStateF
   jitter_buffer_.CopyFrom(receiver.jitter_buffer_);
 }
 
 VCMReceiverState VCMReceiver::State() const {
   CriticalSectionScoped cs(crit_sect_);
   return state_;
 }
 
+VideoReceiveState VCMReceiver::ReceiveState() const {
+  CriticalSectionScoped cs(crit_sect_);
+  return receiveState_;
+}
+
 void VCMReceiver::SetDecodeErrorMode(VCMDecodeErrorMode decode_error_mode) {
   jitter_buffer_.SetDecodeErrorMode(decode_error_mode);
 }
 
 VCMDecodeErrorMode VCMReceiver::DecodeErrorMode() const {
   return jitter_buffer_.decode_error_mode();
 }
 
@@ -347,23 +357,39 @@ int VCMReceiver::RenderBufferSizeMs() {
   const int64_t now_ms = clock_->TimeInMilliseconds();
   timing_->SetJitterDelay(jitter_buffer_.EstimatedJitterMs());
   // Get render timestamps.
   uint32_t render_start = timing_->RenderTimeMs(timestamp_start, now_ms);
   uint32_t render_end = timing_->RenderTimeMs(timestamp_end, now_ms);
   return render_end - render_start;
 }
 
+void VCMReceiver::UpdateReceiveState(const VCMEncodedFrame& frame) {
+  if (frame.Complete() && frame.FrameType() == kVideoFrameKey) {
+    receiveState_ = kReceiveStateNormal;
+    return;
+  }
+  if (frame.MissingFrame() || !frame.Complete()) {
+    // State is corrupted
+    receiveState_ = kReceiveStateWaitingKey;
+  }
+  // state continues
+}
+
 void VCMReceiver::UpdateState(VCMReceiverState new_state) {
   CriticalSectionScoped cs(crit_sect_);
   assert(!(state_ == kPassive && new_state == kWaitForPrimaryDecode));
+  WEBRTC_TRACE(webrtc::kTraceInfo, webrtc::kTraceVideoCoding,
+               VCMId(vcm_id_, receiver_id_),
+               "Receiver changing state: %d to %d",
+               state_, new_state);
   state_ = new_state;
 }
 
-void VCMReceiver::UpdateState(const VCMEncodedFrame& frame) {
+void VCMReceiver::UpdateDualState(const VCMEncodedFrame& frame) {
   if (jitter_buffer_.nack_mode() == kNoNack) {
     // Dual decoder mode has not been enabled.
     return;
   }
   // Update the dual receiver state.
   if (frame.Complete() && frame.FrameType() == kVideoFrameKey) {
     UpdateState(kPassive);
   }
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/receiver.h
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/receiver.h
@@ -70,44 +70,47 @@ class VCMReceiver {
   VCMNackMode NackMode() const;
   VCMNackStatus NackList(uint16_t* nackList, uint16_t size,
                          uint16_t* nack_list_length);
 
   // Dual decoder.
   bool DualDecoderCaughtUp(VCMEncodedFrame* dual_frame,
                            VCMReceiver& dual_receiver) const;
   VCMReceiverState State() const;
+  VideoReceiveState ReceiveState() const;
 
   // Receiver video delay.
   int SetMinReceiverDelay(int desired_delay_ms);
 
   // Decoding with errors.
   void SetDecodeErrorMode(VCMDecodeErrorMode decode_error_mode);
   VCMDecodeErrorMode DecodeErrorMode() const;
 
   // Returns size in time (milliseconds) of complete continuous frames in the
   // jitter buffer. The render time is estimated based on the render delay at
   // the time this function is called.
   int RenderBufferSizeMs();
 
  private:
   void CopyJitterBufferStateFromReceiver(const VCMReceiver& receiver);
   void UpdateState(VCMReceiverState new_state);
-  void UpdateState(const VCMEncodedFrame& frame);
+  void UpdateReceiveState(const VCMEncodedFrame& frame);
+  void UpdateDualState(const VCMEncodedFrame& frame);
   static int32_t GenerateReceiverId();
 
   CriticalSectionWrapper* crit_sect_;
   int32_t vcm_id_;
   Clock* clock_;
   int32_t receiver_id_;
   bool master_;
   VCMJitterBuffer jitter_buffer_;
   VCMTiming* timing_;
   scoped_ptr<EventWrapper> render_wait_event_;
   VCMReceiverState state_;
+  VideoReceiveState receiveState_;
   int max_video_delay_ms_;
 
   static int32_t receiver_id_counter_;
 };
 
 }  // namespace webrtc
 
 #endif  // WEBRTC_MODULES_VIDEO_CODING_MAIN_SOURCE_RECEIVER_H_
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_coding_impl.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_coding_impl.cc
@@ -274,16 +274,21 @@ class VideoCodingModuleImpl : public Vid
     return receiver_->RegisterFrameTypeCallback(frameTypeCallback);
   }
 
   virtual int32_t RegisterPacketRequestCallback(
       VCMPacketRequestCallback* callback) OVERRIDE {
     return receiver_->RegisterPacketRequestCallback(callback);
   }
 
+  virtual int32_t RegisterReceiveStateCallback(
+      VCMReceiveStateCallback* callback) OVERRIDE {
+    return receiver_->RegisterReceiveStateCallback(callback);
+  }
+
   virtual int RegisterRenderBufferSizeCallback(
       VCMRenderBufferSizeCallback* callback) OVERRIDE {
     return receiver_->RegisterRenderBufferSizeCallback(callback);
   }
 
   virtual int32_t Decode(uint16_t maxWaitTimeMs) OVERRIDE {
     return receiver_->Decode(maxWaitTimeMs);
   }
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_coding_impl.h
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_coding_impl.h
@@ -135,30 +135,32 @@ class VideoSender {
 class VideoReceiver {
  public:
   typedef VideoCodingModule::ReceiverRobustness ReceiverRobustness;
 
   VideoReceiver(const int32_t id, Clock* clock, EventFactory* event_factory);
   ~VideoReceiver();
 
   int32_t InitializeReceiver();
+  void SetReceiveState(VideoReceiveState state);
   int32_t RegisterReceiveCodec(const VideoCodec* receiveCodec,
                                int32_t numberOfCores,
                                bool requireKeyFrame);
 
   int32_t RegisterExternalDecoder(VideoDecoder* externalDecoder,
                                   uint8_t payloadType,
                                   bool internalRenderTiming);
   int32_t RegisterReceiveCallback(VCMReceiveCallback* receiveCallback);
   int32_t RegisterReceiveStatisticsCallback(
       VCMReceiveStatisticsCallback* receiveStats);
   int32_t RegisterDecoderTimingCallback(
       VCMDecoderTimingCallback* decoderTiming);
   int32_t RegisterFrameTypeCallback(VCMFrameTypeCallback* frameTypeCallback);
   int32_t RegisterPacketRequestCallback(VCMPacketRequestCallback* callback);
+  int32_t RegisterReceiveStateCallback(VCMReceiveStateCallback* callback);
   int RegisterRenderBufferSizeCallback(VCMRenderBufferSizeCallback* callback);
 
   int32_t Decode(uint16_t maxWaitTimeMs);
   int32_t DecodeDualFrame(uint16_t maxWaitTimeMs);
   int32_t ResetDecoder();
 
   int32_t ReceiveCodec(VideoCodec* currentReceiveCodec) const;
   VideoCodecType ReceiveCodec() const;
@@ -204,26 +206,28 @@ class VideoReceiver {
                     // in any frame
   };
 
   int32_t _id;
   Clock* clock_;
   scoped_ptr<CriticalSectionWrapper> process_crit_sect_;
   CriticalSectionWrapper* _receiveCritSect;
   bool _receiverInited;
+  VideoReceiveState _receiveState;
   VCMTiming _timing;
   VCMTiming _dualTiming;
   VCMReceiver _receiver;
   VCMReceiver _dualReceiver;
   VCMDecodedFrameCallback _decodedFrameCallback;
   VCMDecodedFrameCallback _dualDecodedFrameCallback;
   VCMFrameTypeCallback* _frameTypeCallback;
   VCMReceiveStatisticsCallback* _receiveStatsCallback;
   VCMDecoderTimingCallback* _decoderTimingCallback;
   VCMPacketRequestCallback* _packetRequestCallback;
+  VCMReceiveStateCallback* _receiveStateCallback;
   VCMRenderBufferSizeCallback* render_buffer_callback_;
   VCMGenericDecoder* _decoder;
   VCMGenericDecoder* _dualDecoder;
 #ifdef DEBUG_DECODER_BIT_STREAM
   FILE* _bitStreamBeforeDecoder;
 #endif
   VCMFrameBuffer _frameFromFile;
   VCMKeyRequestMode _keyRequestMode;
--- a/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_receiver.cc
+++ b/media/webrtc/trunk/webrtc/modules/video_coding/main/source/video_receiver.cc
@@ -27,26 +27,28 @@ namespace vcm {
 VideoReceiver::VideoReceiver(const int32_t id,
                              Clock* clock,
                              EventFactory* event_factory)
     : _id(id),
       clock_(clock),
       process_crit_sect_(CriticalSectionWrapper::CreateCriticalSection()),
       _receiveCritSect(CriticalSectionWrapper::CreateCriticalSection()),
       _receiverInited(false),
+      _receiveState(kReceiveStateInitial),
       _timing(clock_, id, 1),
       _dualTiming(clock_, id, 2, &_timing),
       _receiver(&_timing, clock_, event_factory, id, 1, true),
       _dualReceiver(&_dualTiming, clock_, event_factory, id, 2, false),
       _decodedFrameCallback(_timing, clock_),
       _dualDecodedFrameCallback(_dualTiming, clock_),
       _frameTypeCallback(NULL),
       _receiveStatsCallback(NULL),
       _decoderTimingCallback(NULL),
       _packetRequestCallback(NULL),
+      _receiveStateCallback(NULL),
       render_buffer_callback_(NULL),
       _decoder(NULL),
       _dualDecoder(NULL),
 #ifdef DEBUG_DECODER_BIT_STREAM
       _bitStreamBeforeDecoder(NULL),
 #endif
       _frameFromFile(),
       _keyRequestMode(kKeyOnError),
@@ -159,16 +161,35 @@ int32_t VideoReceiver::Process() {
         }
       }
     }
   }
 
   return returnValue;
 }
 
+void VideoReceiver::SetReceiveState(VideoReceiveState state) {
+  if (state == _receiveState) {
+    return;
+  }
+  if (state == kReceiveStatePreemptiveNACK &&
+      (_receiveState == kReceiveStateWaitingKey ||
+       _receiveState == kReceiveStateDecodingWithErrors)) {
+    // invalid state transition - this lets us try to set it on NACK
+    // without worrying about the current state
+    return;
+  }
+  _receiveState = state;
+
+  CriticalSectionScoped cs(process_crit_sect_.get());
+  if (_receiveStateCallback != NULL) {
+    _receiveStateCallback->ReceiveStateChange(_receiveState);
+  }
+}
+
 int32_t VideoReceiver::TimeUntilNextProcess() {
   uint32_t timeUntilNextProcess = _receiveStatsTimer.TimeUntilProcess();
   if ((_receiver.NackMode() != kNoNack) ||
       (_dualReceiver.State() != kPassive)) {
     // We need a Process call more often if we are relying on
     // retransmissions
     timeUntilNextProcess =
         VCM_MIN(timeUntilNextProcess, _retransmissionTimer.TimeUntilProcess());
@@ -290,16 +311,17 @@ int32_t VideoReceiver::InitializeReceive
 
   _decoder = NULL;
   _decodedFrameCallback.SetUserReceiveCallback(NULL);
   _receiverInited = true;
   _frameTypeCallback = NULL;
   _receiveStatsCallback = NULL;
   _decoderTimingCallback = NULL;
   _packetRequestCallback = NULL;
+  _receiveStateCallback = NULL;
   _keyRequestMode = kKeyOnError;
   _scheduleKeyRequest = false;
 
   return VCM_OK;
 }
 
 // Register a receive callback. Will be called whenever there is a new frame
 // ready for rendering.
@@ -351,16 +373,23 @@ int32_t VideoReceiver::RegisterFrameType
 
 int32_t VideoReceiver::RegisterPacketRequestCallback(
     VCMPacketRequestCallback* callback) {
   CriticalSectionScoped cs(process_crit_sect_.get());
   _packetRequestCallback = callback;
   return VCM_OK;
 }
 
+int32_t VideoReceiver::RegisterReceiveStateCallback(
+    VCMReceiveStateCallback* callback) {
+  CriticalSectionScoped cs(process_crit_sect_.get());
+  _receiveStateCallback = callback;
+  return VCM_OK;
+}
+
 int VideoReceiver::RegisterRenderBufferSizeCallback(
     VCMRenderBufferSizeCallback* callback) {
   CriticalSectionScoped cs(process_crit_sect_.get());
   render_buffer_callback_ = callback;
   return VCM_OK;
 }
 
 // Decode next frame, blocking.
@@ -394,19 +423,24 @@ int32_t VideoReceiver::Decode(uint16_t m
     // start decoding retransmitted frames and recover.
     CriticalSectionScoped cs(_receiveCritSect);
     if (_dualDecoder != NULL) {
       _codecDataBase.ReleaseDecoder(_dualDecoder);
     }
     _dualDecoder = _codecDataBase.CreateDecoderCopy();
     if (_dualDecoder != NULL) {
       _dualDecoder->RegisterDecodeCompleteCallback(&_dualDecodedFrameCallback);
+      SetReceiveState(kReceiveStateDecodingWithErrors);
     } else {
       _dualReceiver.Reset();
+      // presume we have an error, though we might not yet
+      SetReceiveState(kReceiveStateWaitingKey);
     }
+  } else {
+    SetReceiveState(_receiver.ReceiveState());
   }
 
   if (frame == NULL) {
     return VCM_FRAME_NOT_READY;
   } else {
     CriticalSectionScoped cs(_receiveCritSect);
 
     // If this frame was too late, we should adjust the delay accordingly
@@ -676,27 +710,29 @@ int32_t VideoReceiver::IncomingPacket(co
   const VCMPacket packet(incomingPayload, payloadLength, rtpInfo);
   int32_t ret;
   if (_dualReceiver.State() != kPassive) {
     ret = _dualReceiver.InsertPacket(
         packet, rtpInfo.type.Video.width, rtpInfo.type.Video.height);
     if (ret == VCM_FLUSH_INDICATOR) {
       RequestKeyFrame();
       ResetDecoder();
+      SetReceiveState(kReceiveStateWaitingKey);
     } else if (ret < 0) {
       return ret;
     }
   }
   ret = _receiver.InsertPacket(
       packet, rtpInfo.type.Video.width, rtpInfo.type.Video.height);
   // TODO(holmer): Investigate if this somehow should use the key frame
   // request scheduling to throttle the requests.
   if (ret == VCM_FLUSH_INDICATOR) {
     RequestKeyFrame();
     ResetDecoder();
+    SetReceiveState(kReceiveStateWaitingKey);
   } else if (ret < 0) {
     return ret;
   }
   return VCM_OK;
 }
 
 // Minimum playout delay (used for lip-sync). This is the minimum delay required
 // to sync with audio. Not included in  VideoCodingModule::Delay()
@@ -739,21 +775,27 @@ int32_t VideoReceiver::NackList(uint16_t
                    "Out of memory");
       return VCM_MEMORY;
     }
     case kNackKeyFrameRequest: {
       WEBRTC_TRACE(webrtc::kTraceWarning,
                    webrtc::kTraceVideoCoding,
                    VCMId(_id),
                    "Failed to get NACK list, requesting key frame");
+      SetReceiveState(kReceiveStateWaitingKey);
       return RequestKeyFrame();
     }
     default:
       break;
   }
+  if (*size != 0) {
+    // Note: not a valid transition from WaitingKey or DecodingWithErrors;
+    // will be ignored in that case
+    SetReceiveState(kReceiveStatePreemptiveNACK);
+  }
   return VCM_OK;
 }
 
 int32_t VideoReceiver::ReceivedFrameCount(VCMFrameCount* frameCount) const {
   _receiver.ReceivedFrameCount(frameCount);
   return VCM_OK;
 }
 
--- a/media/webrtc/trunk/webrtc/video/receive_statistics_proxy.cc
+++ b/media/webrtc/trunk/webrtc/video/receive_statistics_proxy.cc
@@ -22,33 +22,34 @@ ReceiveStatisticsProxy::ReceiveStatistic
                                                ViECodec* codec,
                                                int channel)
     : channel_(channel),
       lock_(CriticalSectionWrapper::CreateCriticalSection()),
       clock_(clock),
       // 1000ms window, scale 1000 for ms to s.
       decode_fps_estimator_(1000, 1000),
       renders_fps_estimator_(1000, 1000),
+      receive_state_(kReceiveStateInitial),
       codec_(codec),
       rtp_rtcp_(rtp_rtcp) {
   stats_.ssrc = ssrc;
 }
 
 ReceiveStatisticsProxy::~ReceiveStatisticsProxy() {}
 
 VideoReceiveStream::Stats ReceiveStatisticsProxy::GetStats() const {
   VideoReceiveStream::Stats stats;
   {
     CriticalSectionScoped cs(lock_.get());
     stats = stats_;
   }
   stats.c_name = GetCName();
   codec_->GetReceiveSideDelay(channel_, &stats.avg_delay_ms);
   stats.discarded_packets = codec_->GetDiscardedPackets(channel_);
-  codec_->GetReceiveCodecStastistics(
+  codec_->GetReceiveCodecStatistics(
       channel_, stats.key_frames, stats.delta_frames);
 
   return stats;
 }
 
 std::string ReceiveStatisticsProxy::GetCName() const {
   char rtcp_cname[ViERTP_RTCP::KMaxRTCPCNameLength];
   if (rtp_rtcp_->GetRemoteRTCPCName(channel_, rtcp_cname) != 0)
@@ -59,16 +60,22 @@ std::string ReceiveStatisticsProxy::GetC
 void ReceiveStatisticsProxy::IncomingRate(const int video_channel,
                                           const unsigned int framerate,
                                           const unsigned int bitrate) {
   CriticalSectionScoped cs(lock_.get());
   stats_.network_frame_rate = framerate;
   stats_.bitrate_bps = bitrate;
 }
 
+void ReceiveStatisticsProxy::ReceiveStateChange(const int video_channel,
+                                                VideoReceiveState state) {
+  CriticalSectionScoped cs(lock_.get());
+  receive_state_ = state;
+}
+
 void ReceiveStatisticsProxy::StatisticsUpdated(
     const webrtc::RtcpStatistics& statistics,
     uint32_t ssrc) {
   CriticalSectionScoped cs(lock_.get());
 
   stats_.rtcp_stats = statistics;
 }
 
--- a/media/webrtc/trunk/webrtc/video/receive_statistics_proxy.h
+++ b/media/webrtc/trunk/webrtc/video/receive_statistics_proxy.h
@@ -55,16 +55,17 @@ class ReceiveStatisticsProxy : public Vi
   virtual void DecoderTiming(int decode_ms,
                              int max_decode_ms,
                              int current_delay_ms,
                              int target_delay_ms,
                              int jitter_buffer_ms,
                              int min_playout_delay_ms,
                              int render_delay_ms) OVERRIDE {}
   virtual void RequestNewKeyFrame(const int video_channel) OVERRIDE {}
+  virtual void ReceiveStateChange(const int video_channel, VideoReceiveState state) OVERRIDE;
 
   // Overrides RtcpStatisticsBallback.
   virtual void StatisticsUpdated(const webrtc::RtcpStatistics& statistics,
                                  uint32_t ssrc) OVERRIDE;
 
   // Overrides StreamDataCountersCallback.
   virtual void DataCountersUpdated(const webrtc::StreamDataCounters& counters,
                                    uint32_t ssrc) OVERRIDE;
@@ -73,15 +74,16 @@ class ReceiveStatisticsProxy : public Vi
   std::string GetCName() const;
 
   const int channel_;
   scoped_ptr<CriticalSectionWrapper> lock_;
   Clock* clock_;
   VideoReceiveStream::Stats stats_;
   RateStatistics decode_fps_estimator_;
   RateStatistics renders_fps_estimator_;
+  VideoReceiveState receive_state_;
   ViECodec* codec_;
   ViERTP_RTCP* rtp_rtcp_;
 };
 
 }  // namespace internal
 }  // namespace webrtc
 #endif  // WEBRTC_VIDEO_RECEIVE_STATISTICS_PROXY_H_
--- a/media/webrtc/trunk/webrtc/video_engine/include/vie_codec.h
+++ b/media/webrtc/trunk/webrtc/video_engine/include/vie_codec.h
@@ -70,16 +70,19 @@ class WEBRTC_DLLEXPORT ViEDecoderObserve
                              int jitter_buffer_ms,
                              int min_playout_delay_ms,
                              int render_delay_ms) = 0;
 
   // This method is called when the decoder needs a new key frame from encoder
   // on the sender.
   virtual void RequestNewKeyFrame(const int video_channel) = 0;
 
+  // This method is called when the decoder changes state
+  virtual void ReceiveStateChange(const int video_channel, VideoReceiveState state) = 0;
+
  protected:
   virtual ~ViEDecoderObserver() {}
 };
 
 class WEBRTC_DLLEXPORT ViECodec {
  public:
   // Factory for the ViECodec sub‐API and increases an internal reference
   // counter if successful. Returns NULL if the API is not supported or if
@@ -125,22 +128,22 @@ class WEBRTC_DLLEXPORT ViECodec {
       unsigned char& config_parameters_size) const = 0;
 
   // Enables advanced scaling of the captured video stream if the stream
   // differs from the send codec settings.
   virtual int SetImageScaleStatus(const int video_channel,
                                   const bool enable) = 0;
 
   // Gets the number of sent key frames and number of sent delta frames.
-  virtual int GetSendCodecStastistics(const int video_channel,
+  virtual int GetSendCodecStatistics(const int video_channel,
                                       unsigned int& key_frames,
                                       unsigned int& delta_frames) const = 0;
 
   // Gets the number of decoded key frames and number of decoded delta frames.
-  virtual int GetReceiveCodecStastistics(const int video_channel,
+  virtual int GetReceiveCodecStatistics(const int video_channel,
                                          unsigned int& key_frames,
                                          unsigned int& delta_frames) const = 0;
 
   // Estimate of the min required buffer time from the expected arrival time
   // until rendering to get smooth playback.
   virtual int GetReceiveSideDelay(const int video_channel,
                                   int* delay_ms) const = 0;
 
--- a/media/webrtc/trunk/webrtc/video_engine/test/auto_test/source/vie_autotest_custom_call.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/test/auto_test/source/vie_autotest_custom_call.cc
@@ -1620,28 +1620,28 @@ void PrintCodecStatistics(webrtc::ViECod
   int number_of_errors = 0;
   unsigned int key_frames = 0;
   unsigned int delta_frames = 0;
   switch (stat_type) {
     case kReceivedStatistic:
       std::cout << "Codec Receive statistics"
                 << std::endl;
       // Get and print the Receive Codec Statistics
-      error = vie_codec->GetReceiveCodecStastistics(video_channel, key_frames,
-                                                    delta_frames);
+      error = vie_codec->GetReceiveCodecStatistics(video_channel, key_frames,
+                                                   delta_frames);
       number_of_errors += ViETest::TestError(error == 0,
                                              "ERROR: %s at line %d",
                                              __FUNCTION__, __LINE__);
       break;
     case kSendStatistic:
       std::cout << "Codec Send statistics"
                 << std::endl;
       // Get and print the Send Codec Statistics
-      error = vie_codec->GetSendCodecStastistics(video_channel, key_frames,
-                                                 delta_frames);
+      error = vie_codec->GetSendCodecStatistics(video_channel, key_frames,
+                                                delta_frames);
       number_of_errors += ViETest::TestError(error == 0,
                                              "ERROR: %s at line %d",
                                              __FUNCTION__, __LINE__);
       break;
   }
   std::cout << "\tNumber of encoded key frames: "
             << key_frames << std::endl;
   std::cout << "\tNumber of encoded delta frames: "
--- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.cc
@@ -181,16 +181,20 @@ int32_t ViEChannel::Init() {
     WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, channel_id_),
                  "%s: VCM::RegisterReceiveCallback failure", __FUNCTION__);
     return -1;
   }
   if (vcm_.RegisterFrameTypeCallback(this) != 0) {
     WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_),
                  "%s: VCM::RegisterFrameTypeCallback failure", __FUNCTION__);
   }
+  if (vcm_.RegisterReceiveStateCallback(this) != 0) {
+    WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_),
+                 "%s: VCM::RegisterReceiveStateCallback failure", __FUNCTION__);
+  }
   if (vcm_.RegisterReceiveStatisticsCallback(this) != 0) {
     WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_),
                  "%s: VCM::RegisterReceiveStatisticsCallback failure",
                  __FUNCTION__);
   }
   if (vcm_.RegisterDecoderTimingCallback(this) != 0) {
     WEBRTC_TRACE(kTraceWarning, kTraceVideo, ViEId(engine_id_, channel_id_),
                  "%s: VCM::RegisterDecoderTimingCallback failure",
@@ -1913,16 +1917,27 @@ int32_t ViEChannel::SliceLossIndicationR
 
 int32_t ViEChannel::ResendPackets(const uint16_t* sequence_numbers,
                                         uint16_t length) {
   WEBRTC_TRACE(kTraceStream, kTraceVideo, ViEId(engine_id_, channel_id_),
                "%s(length: %d)", __FUNCTION__, length);
   return rtp_rtcp_->SendNACK(sequence_numbers, length);
 }
 
+void ViEChannel::ReceiveStateChange(VideoReceiveState state) {
+  WEBRTC_TRACE(kTraceStream, kTraceVideo, ViEId(engine_id_, channel_id_),
+               "%s", __FUNCTION__);
+  {
+    CriticalSectionScoped cs(callback_cs_.get());
+    if (codec_observer_) {
+      codec_observer_->ReceiveStateChange(channel_id_, state);
+    }
+  }
+}
+
 bool ViEChannel::ChannelDecodeThreadFunction(void* obj) {
   return static_cast<ViEChannel*>(obj)->ChannelDecodeProcess();
 }
 
 bool ViEChannel::ChannelDecodeProcess() {
   vcm_.Decode(kMaxDecodeWaitTimeMs);
   return true;
 }
--- a/media/webrtc/trunk/webrtc/video_engine/vie_channel.h
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_channel.h
@@ -52,16 +52,17 @@ class VoEVideoSync;
 struct SenderInfo;
 
 class ViEChannel
     : public VCMFrameTypeCallback,
       public VCMReceiveCallback,
       public VCMReceiveStatisticsCallback,
       public VCMDecoderTimingCallback,
       public VCMPacketRequestCallback,
+      public VCMReceiveStateCallback,
       public RtcpFeedback,
       public RtpFeedback,
       public ViEFrameProviderBase {
  public:
   friend class ChannelStatsObserver;
 
   ViEChannel(int32_t channel_id,
              int32_t engine_id,
@@ -343,16 +344,19 @@ class ViEChannel
   // Implements VideoFrameTypeCallback.
   virtual int32_t SliceLossIndicationRequest(
       const uint64_t picture_id);
 
   // Implements VideoPacketRequestCallback.
   virtual int32_t ResendPackets(const uint16_t* sequence_numbers,
                                 uint16_t length);
 
+  // Implements ReceiveStateCallback.
+  virtual void ReceiveStateChange(VideoReceiveState state);
+
   int32_t SetVoiceChannel(int32_t ve_channel_id,
                           VoEVideoSync* ve_sync_interface);
   int32_t VoiceChannel();
 
   // Implements ViEFrameProviderBase.
   virtual int FrameCallbackChanged() {return -1;}
 
   int32_t RegisterEffectFilter(ViEEffectFilter* effect_filter);
--- a/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.cc
@@ -407,19 +407,19 @@ int ViECodecImpl::SetImageScaleStatus(co
 
   if (vie_encoder->ScaleInputImage(enable) != 0) {
     shared_data_->SetLastError(kViECodecUnknownError);
     return -1;
   }
   return 0;
 }
 
-int ViECodecImpl::GetSendCodecStastistics(const int video_channel,
-                                          unsigned int& key_frames,
-                                          unsigned int& delta_frames) const {
+int ViECodecImpl::GetSendCodecStatistics(const int video_channel,
+                                         unsigned int& key_frames,
+                                         unsigned int& delta_frames) const {
   WEBRTC_TRACE(kTraceApiCall, kTraceVideo,
                ViEId(shared_data_->instance_id(), video_channel),
                "%s(video_channel %d)", __FUNCTION__, video_channel);
 
   ViEChannelManagerScoped cs(*(shared_data_->channel_manager()));
   ViEEncoder* vie_encoder = cs.Encoder(video_channel);
   if (!vie_encoder) {
     WEBRTC_TRACE(kTraceError, kTraceVideo,
@@ -432,19 +432,19 @@ int ViECodecImpl::GetSendCodecStastistic
 
   if (vie_encoder->SendCodecStatistics(&key_frames, &delta_frames) != 0) {
     shared_data_->SetLastError(kViECodecUnknownError);
     return -1;
   }
   return 0;
 }
 
-int ViECodecImpl::GetReceiveCodecStastistics(const int video_channel,
-                                             unsigned int& key_frames,
-                                             unsigned int& delta_frames) const {
+int ViECodecImpl::GetReceiveCodecStatistics(const int video_channel,
+                                            unsigned int& key_frames,
+                                            unsigned int& delta_frames) const {
   WEBRTC_TRACE(kTraceApiCall, kTraceVideo,
                ViEId(shared_data_->instance_id(), video_channel),
                "%s(video_channel: %d)", __FUNCTION__,
                video_channel);
 
   ViEChannelManagerScoped cs(*(shared_data_->channel_manager()));
   ViEChannel* vie_channel = cs.Channel(video_channel);
   if (!vie_channel) {
--- a/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.h
+++ b/media/webrtc/trunk/webrtc/video_engine/vie_codec_impl.h
@@ -38,22 +38,22 @@ class ViECodecImpl
                               const VideoCodec& video_codec);
   virtual int GetReceiveCodec(const int video_channel,
                               VideoCodec& video_codec) const;
   virtual int GetCodecConfigParameters(
     const int video_channel,
     unsigned char config_parameters[kConfigParameterSize],
     unsigned char& config_parameters_size) const;
   virtual int SetImageScaleStatus(const int video_channel, const bool enable);
-  virtual int GetSendCodecStastistics(const int video_channel,
-                                      unsigned int& key_frames,
-                                      unsigned int& delta_frames) const;
-  virtual int GetReceiveCodecStastistics(const int video_channel,
-                                         unsigned int& key_frames,
-                                         unsigned int& delta_frames) const;
+  virtual int GetSendCodecStatistics(const int video_channel,
+                                     unsigned int& key_frames,
+                                     unsigned int& delta_frames) const;
+  virtual int GetReceiveCodecStatistics(const int video_channel,
+                                        unsigned int& key_frames,
+                                        unsigned int& delta_frames) const;
   virtual int GetReceiveSideDelay(const int video_channel,
                                   int* delay_ms) const;
   virtual int GetCodecTargetBitrate(const int video_channel,
                                     unsigned int* bitrate) const;
   virtual unsigned int GetDiscardedPackets(const int video_channel) const;
   virtual int SetKeyFrameRequestCallbackStatus(const int video_channel,
                                                const bool enable);
   virtual int SetSignalKeyPacketLossStatus(const int video_channel,
--- a/mobile/android/base/widget/ButtonToast.java
+++ b/mobile/android/base/widget/ButtonToast.java
@@ -144,16 +144,17 @@ public class ButtonToast {
             // Using Android's animation frameworks will not correctly turn off clicking.
             // See bug 885717.
             PropertyAnimator animator = new PropertyAnimator(duration);
             animator.attach(mView, PropertyAnimator.Property.ALPHA, 0.0f);
             animator.addPropertyAnimationListener(new PropertyAnimator.PropertyAnimationListener () {
                 // If we are showing a toast and go in the background
                 // onAnimationEnd will be called when the app is restored
                 public void onPropertyAnimationEnd() {
+                    mView.clearAnimation();
                     mView.setVisibility(View.GONE);
                 }
                 public void onPropertyAnimationStart() { }
             });
             animator.start();
         }
     }
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -245,17 +245,17 @@ pref("media.webm.enabled", true);
 pref("media.gstreamer.enabled", true);
 #endif
 #ifdef MOZ_APPLEMEDIA
 pref("media.apple.mp3.enabled", true);
 #endif
 #ifdef MOZ_WEBRTC
 pref("media.navigator.enabled", true);
 pref("media.navigator.video.enabled", true);
-pref("media.navigator.load_adapt", false);
+pref("media.navigator.load_adapt", true);
 pref("media.navigator.load_adapt.measure_interval",1000);
 pref("media.navigator.load_adapt.avg_seconds",3);
 pref("media.navigator.load_adapt.high_load","0.90");
 pref("media.navigator.load_adapt.low_load","0.40");
 pref("media.navigator.video.default_fps",30);
 pref("media.navigator.video.default_minfps",10);
 #ifdef MOZ_WIDGET_GONK
 pref("media.navigator.video.default_width",320);
--- a/netwerk/cache2/CacheFileMetadata.cpp
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -180,21 +180,27 @@ CacheFileMetadata::ReadMetadata(CacheFil
     LOG(("CacheFileMetadata::ReadMetadata() - File is corrupted, creating "
          "empty metadata. [this=%p, filesize=%lld]", this, size));
 
     InitEmptyMetadata();
     aListener->OnMetadataRead(NS_OK);
     return NS_OK;
   }
 
-  // round offset to 4k blocks
-  int64_t offset = (size / kAlignSize) * kAlignSize;
+  // Set offset so that we read at least kMinMetadataRead if the file is big
+  // enough.
+  int64_t offset;
+  if (size < kMinMetadataRead) {
+    offset = 0;
+  } else {
+    offset = size - kMinMetadataRead;
+  }
 
-  if (size - offset < kMinMetadataRead && offset > kAlignSize)
-    offset -= kAlignSize;
+  // round offset to kAlignSize blocks
+  offset = (offset / kAlignSize) * kAlignSize;
 
   mBufSize = size - offset;
   mBuf = static_cast<char *>(moz_xmalloc(mBufSize));
 
   DoMemoryReport(MemoryUsage());
 
   LOG(("CacheFileMetadata::ReadMetadata() - Reading metadata from disk, trying "
        "offset=%lld, filesize=%lld [this=%p]", offset, size, this));
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -1313,25 +1313,26 @@ DataChannelConnection::HandleDataMessage
     // before we're told about the external negotiation.  We need to buffer
     // data until either a) Open comes in, if the ordering get messed up,
     // or b) the app tells us this channel was externally negotiated.  When
     // these occur, we deliver the data.
 
     // Since this is rare and non-performance, keep a single list of queued
     // data messages to deliver once the channel opens.
     LOG(("Queuing data for stream %u, length %u", stream, length));
+    // Copies data
     mQueuedData.AppendElement(new QueuedDataMessage(stream, ppid, data, length));
     return;
   }
 
   // XXX should this be a simple if, no warnings/debugbreaks?
   NS_ENSURE_TRUE_VOID(channel->mState != CLOSED);
 
   {
-    nsAutoCString recvData(buffer, length);
+    nsAutoCString recvData(buffer, length); // copies (<64) or allocates
     bool is_binary = true;
 
     if (ppid == DATA_CHANNEL_PPID_DOMSTRING ||
         ppid == DATA_CHANNEL_PPID_DOMSTRING_LAST) {
       is_binary = false;
     }
     if (is_binary != channel->mIsRecvBinary && !channel->mRecvBuffer.IsEmpty()) {
       NS_WARNING("DataChannel message aborted by fragment type change!");
@@ -1928,16 +1929,21 @@ DataChannelConnection::ReceiveCallback(s
   } else {
     MutexAutoLock lock(mLock);
     if (flags & MSG_NOTIFICATION) {
       HandleNotification(static_cast<union sctp_notification *>(data), datalen);
     } else {
       HandleMessage(data, datalen, ntohl(rcv.rcv_ppid), rcv.rcv_sid);
     }
   }
+  // sctp allocates 'data' with malloc(), and expects the receiver to free
+  // it (presumably with free).
+  // XXX future optimization: try to deliver messages without an internal
+  // alloc/copy, and if so delay the free until later.
+  free(data);
   // usrsctp defines the callback as returning an int, but doesn't use it
   return 1;
 }
 
 already_AddRefed<DataChannel>
 DataChannelConnection::Open(const nsACString& label, const nsACString& protocol,
                             Type type, bool inOrder,
                             uint32_t prValue, DataChannelListener *aListener,
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -1228,16 +1228,22 @@ function fillInTree(aRoot)
  *        The sum of all explicit HEAP reports for this process.
  * @return A boolean indicating if "heap-allocated" is known for the process.
  */
 function addHeapUnclassifiedNode(aT, aHeapAllocatedNode, aHeapTotal)
 {
   if (aHeapAllocatedNode === undefined)
     return false;
 
+  if (aT.findKid("heap-unclassified")) {
+    // heap-unclassified was already calculated, there's nothing left to do.
+    // This can happen when memory reports are exported from areweslimyet.com.
+    return true;
+  }
+
   assert(aHeapAllocatedNode._isDegenerate, "heap-allocated is not degenerate");
   let heapAllocatedBytes = aHeapAllocatedNode._amount;
   let heapUnclassifiedT = new TreeNode("heap-unclassified", UNITS_BYTES);
   heapUnclassifiedT._amount = heapAllocatedBytes - aHeapTotal;
   heapUnclassifiedT._description =
       "Memory not classified by a more specific report. This includes " +
       "slop bytes due to internal fragmentation in the heap allocator " +
       "(caused when the allocator rounds up request sizes).";
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -143,16 +143,20 @@ private:
 
   // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
   // guarantee calling the callback
   nsresult OnStopRequestInternal(nsIRequest *aRequest,
                                  nsISupports *aContext,
                                  nsresult aResult,
                                  bool* aShouldBlock);
 
+  // Strip url parameters, fragments, and user@pass fields from the URI spec
+  // using nsIURL. If aURI is not an nsIURL, returns the original nsIURI.spec.
+  nsresult GetStrippedSpec(nsIURI* aUri, nsACString& spec);
+
   // Escape '/' and '%' in certificate attribute values.
   nsCString EscapeCertificateAttribute(const nsACString& aAttribute);
 
   // Escape ':' in fingerprint values.
   nsCString EscapeFingerprint(const nsACString& aAttribute);
 
   // Generate whitelist strings for the given certificate pair from the same
   // certificate chain.
@@ -586,17 +590,17 @@ PendingLookup::AddRedirects(nsIArray* aR
 
     nsCOMPtr<nsIURI> uri;
     rv = principal->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
 
     // Add the spec to our list of local lookups. The most recent redirect is
     // the last element.
     nsCString spec;
-    rv = uri->GetSpec(spec);
+    rv = GetStrippedSpec(uri, spec);
     NS_ENSURE_SUCCESS(rv, rv);
     mAnylistSpecs.AppendElement(spec);
     LOG(("ApplicationReputation: Appending redirect %s\n", spec.get()));
 
     // Store the redirect information in the remote request.
     ClientDownloadRequest_Resource* resource = mRequest.add_resources();
     resource->set_url(spec.get());
     resource->set_type(ClientDownloadRequest::DOWNLOAD_REDIRECT);
@@ -614,37 +618,67 @@ PendingLookup::StartLookup()
   nsresult rv = DoLookupInternal();
   if (NS_FAILED(rv)) {
     return OnComplete(false, NS_OK);
   };
   return rv;
 }
 
 nsresult
+PendingLookup::GetStrippedSpec(nsIURI* aUri, nsACString& escaped)
+{
+  // If aURI is not an nsIURL, we do not want to check the lists or send a
+  // remote query.
+  nsresult rv;
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aUri, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = url->GetScheme(escaped);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCString temp;
+  rv = url->GetHostPort(temp);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  escaped.Append("://");
+  escaped.Append(temp);
+
+  rv = url->GetFilePath(temp);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // nsIUrl.filePath starts with '/'
+  escaped.Append(temp);
+
+  return NS_OK;
+}
+
+nsresult
 PendingLookup::DoLookupInternal()
 {
   // We want to check the target URI, its referrer, and associated redirects
   // against the local lists.
   nsCOMPtr<nsIURI> uri;
   nsresult rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCString spec;
-  rv = uri->GetSpec(spec);
+  rv = GetStrippedSpec(uri, spec);
   NS_ENSURE_SUCCESS(rv, rv);
+
   mAnylistSpecs.AppendElement(spec);
+
   ClientDownloadRequest_Resource* resource = mRequest.add_resources();
   resource->set_url(spec.get());
   resource->set_type(ClientDownloadRequest::DOWNLOAD_URL);
 
   nsCOMPtr<nsIURI> referrer = nullptr;
   rv = mQuery->GetReferrerURI(getter_AddRefs(referrer));
   if (referrer) {
     nsCString spec;
-    rv = referrer->GetSpec(spec);
+    rv = GetStrippedSpec(referrer, spec);
     NS_ENSURE_SUCCESS(rv, rv);
     mAnylistSpecs.AppendElement(spec);
     resource->set_referrer(spec.get());
   }
   nsCOMPtr<nsIArray> redirects;
   rv = mQuery->GetRedirects(getter_AddRefs(redirects));
   if (redirects) {
     AddRedirects(redirects);
@@ -769,17 +803,17 @@ PendingLookup::SendRemoteQueryInternal()
   LOG(("Sending remote query for application reputation [this = %p]", this));
   // We did not find a local result, so fire off the query to the application
   // reputation service.
   nsCOMPtr<nsIURI> uri;
   nsresult rv;
   rv = mQuery->GetSourceURI(getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, rv);
   nsCString spec;
-  rv = uri->GetSpec(spec);
+  rv = GetStrippedSpec(uri, spec);
   NS_ENSURE_SUCCESS(rv, rv);
   mRequest.set_url(spec.get());
 
   uint32_t fileSize;
   rv = mQuery->GetFileSize(&fileSize);
   NS_ENSURE_SUCCESS(rv, rv);
   mRequest.set_length(fileSize);
   // We have no way of knowing whether or not a user initiated the download.
--- a/toolkit/components/downloads/test/unit/test_app_rep.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep.js
@@ -11,19 +11,19 @@ const gAppRep = Cc["@mozilla.org/downloa
                   getService(Ci.nsIApplicationReputationService);
 let gHttpServ = null;
 let gTables = {};
 
 let ALLOW_LIST = 0;
 let BLOCK_LIST = 1;
 let NO_LIST = 2;
 
-let whitelistedURI = createURI("http://whitelisted.com");
-let exampleURI = createURI("http://example.com");
-let blocklistedURI = createURI("http://blocklisted.com");
+let whitelistedURI = createURI("http://foo:bar@whitelisted.com/index.htm#junk");
+let exampleURI = createURI("http://user:password@example.com/i.html?foo=bar");
+let blocklistedURI = createURI("http://baz:qux@blocklisted.com?xyzzy");
 
 function readFileToString(aFilename) {
   let f = do_get_file(aFilename);
   let stream = Cc["@mozilla.org/network/file-input-stream;1"]
                  .createInstance(Ci.nsIFileInputStream);
   stream.init(f, -1, 0, 0);
   let buf = NetUtil.readInputStreamToString(stream, stream.available());
   return buf;
@@ -243,16 +243,35 @@ add_test(function test_unlisted() {
   }, function onComplete(aShouldBlock, aStatus) {
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_false(aShouldBlock);
     check_telemetry(counts.total + 1, counts.shouldBlock, listCounts);
     run_next_test();
   });
 });
 
+add_test(function test_non_uri() {
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  let counts = get_telemetry_counts();
+  let listCounts = counts.listCounts;
+  // No listcount is incremented, since the sourceURI is not an nsIURL
+  let source = NetUtil.newURI("data:application/octet-stream,ABC");
+  do_check_false(source instanceof Ci.nsIURL);
+  gAppRep.queryReputation({
+    sourceURI: source,
+    fileSize: 12,
+  }, function onComplete(aShouldBlock, aStatus) {
+    do_check_eq(Cr.NS_OK, aStatus);
+    do_check_false(aShouldBlock);
+    check_telemetry(counts.total + 1, counts.shouldBlock, listCounts);
+    run_next_test();
+  });
+});
+
 add_test(function test_local_blacklist() {
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/download");
   let counts = get_telemetry_counts();
   let listCounts = counts.listCounts;
   listCounts[BLOCK_LIST]++;
   gAppRep.queryReputation({
     sourceURI: blocklistedURI,
--- a/toolkit/components/satchel/test/mochitest.ini
+++ b/toolkit/components/satchel/test/mochitest.ini
@@ -1,21 +1,15 @@
 [DEFAULT]
-skip-if = buildapp == 'b2g' || e10s
+skip-if = toolkit == 'android' || buildapp == 'b2g' || os == 'linux' || e10s # linux - bug 947531
 support-files =
   satchel_common.js
   subtst_form_submission_1.html
   subtst_privbrowsing.html
 
 [test_bug_511615.html]
-skip-if = toolkit == 'android'
 [test_bug_787624.html]
-skip-if = toolkit == 'android'
-# [test_form_autocomplete.html]
-# Test disabled for too many intermittent failures (bug 874429)
+[test_form_autocomplete.html]
+skip-if = true # Test disabled for too many intermittent failures (bug 874429)
 [test_form_autocomplete_with_list.html]
-skip-if = toolkit == 'android'
 [test_form_submission.html]
-skip-if = toolkit == 'android'
 [test_form_submission_cap.html]
-skip-if = toolkit == 'android'
 [test_form_submission_cap2.html]
-skip-if = toolkit == 'android'
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5319,30 +5319,128 @@
   },
   "WEBRTC_AUDIO_QUALITY_OUTBOUND_JITTER": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 1000,
     "description": "RTCP-reported jitter by remote recipient of outbound audio (ms). Sampled every second of a call (for easy comparison)."
   },
+  "WEBRTC_VIDEO_ERROR_RECOVERY_MS": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": 10000,
+    "n_buckets": 500,
+    "description": "Time to recover from a video error in ms"
+  },
+  "WEBRTC_VIDEO_RECOVERY_BEFORE_ERROR_PER_MIN": {
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": 1000,
+    "n_buckets": 200,
+    "description": "Number of losses recovered before error per min"
+  },
+  "WEBRTC_VIDEO_RECOVERY_AFTER_ERROR_PER_MIN": {
+    "expires_in_version": "never",
+    "kind"