Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 17 Jul 2017 17:00:52 -0700
changeset 369289 efc0b1525edbd357818dc7195537364e76f709e7
parent 369249 e0b0865639cebc1b5afa0268a4b073fcdde0e69c (current diff)
parent 369186 bccbac6fcf204af8ee5a529a8b95ed8ddc94bb2f (diff)
child 369290 5e73b9798464c3f7106f0161dc9a49b234f42f9c
push id46631
push userkwierso@gmail.com
push dateTue, 18 Jul 2017 00:38:28 +0000
treeherderautoland@216a5bf264b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.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 autoland to central, a=merge MozReview-Commit-ID: 5dZhRSLgWzI
media/webrtc/signaling/src/media-conduit/OMXVideoCodec.cpp
media/webrtc/signaling/src/media-conduit/OMXVideoCodec.h
media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h
toolkit/components/places/tests/expiration/test_analyze_runs.js
toolkit/components/places/tests/expiration/test_outdated_analyze.js
toolkit/components/places/tests/unit/test_analyze.js
toolkit/themes/windows/global/icons/Search-glass.png
tools/lint/eslint/update
--- a/.hgignore
+++ b/.hgignore
@@ -115,16 +115,17 @@ GPATH
 ^testing/mozharness/.coverage
 ^testing/mozharness/nosetests.xml
 
 # Ignore tox generated dir
 .tox/
 
 # Ignore ESLint node_modules
 ^node_modules/
+^tools/lint/eslint/eslint-plugin-mozilla/node_modules/
 
 # Ignore talos virtualenv and tp5n files.
 # The tp5n set is supposed to be decompressed at
 # testing/talos/talos/tests/tp5n in order to run tests like tps
 # locally. Similarly, running talos requires a Python package virtual
 # environment. Both the virtual environment and tp5n files end up littering
 # the status command, so we ignore them.
 ^testing/talos/.Python
--- a/browser/base/content/browser-media.js
+++ b/browser/base/content/browser-media.js
@@ -163,17 +163,23 @@ var gEMEHandler = {
       Services.prefs.setBoolPref(firstPlayPref, true);
     } else {
       document.getElementById(anchorId).removeAttribute("firstplay");
     }
 
     let mainAction = {
       label: gNavigatorBundle.getString(btnLabelId),
       accessKey: gNavigatorBundle.getString(btnAccessKeyId),
-      callback() { openPreferences("panePrivacy", {origin: "browserMedia"}); },
+      callback() {
+        if (Services.prefs.getBoolPref("browser.preferences.useOldOrganization")) {
+          openPreferences("paneContent", {origin: "browserMedia"});
+        } else {
+          openPreferences("panePrivacy", {origin: "browserMedia"});
+        }
+      },
       dismiss: true
     };
     let options = {
       dismissed: true,
       eventCallback: aTopic => aTopic == "swapping",
       learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content",
     };
     PopupNotifications.show(browser, "drmContentPlaying", message, anchorId, mainAction, null, options);
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -517,16 +517,20 @@ class Tab extends TabBase {
     } else if (nativeTab.muteReason) {
       mutedInfo.reason = "extension";
       mutedInfo.extensionId = nativeTab.muteReason;
     }
 
     return mutedInfo;
   }
 
+  get lastAccessed() {
+    return this.nativeTab.lastAccessed;
+  }
+
   get pinned() {
     return this.nativeTab.pinned;
   }
 
   get active() {
     return this.nativeTab.selected;
   }
 
@@ -573,16 +577,17 @@ class Tab extends TabBase {
     let result = {
       sessionId: String(tabData.closedId),
       index: tabData.pos ? tabData.pos : 0,
       windowId: window && windowTracker.getId(window),
       highlighted: false,
       active: false,
       pinned: false,
       incognito: Boolean(tabData.state && tabData.state.isPrivate),
+      lastAccessed: tabData.state ? tabData.state.lastAccessed : tabData.lastAccessed,
     };
 
     if (extension.tabManager.hasTabPermission(tabData)) {
       let entries = tabData.state ? tabData.state.entries : tabData.entries;
       let entry = entries[entries.length - 1];
       result.url = entry.url;
       result.title = entry.title;
       if (tabData.image) {
--- a/browser/components/extensions/schemas/tabs.json
+++ b/browser/components/extensions/schemas/tabs.json
@@ -59,16 +59,17 @@
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
           "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active"},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+          "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."},
           "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
           "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -118,16 +118,17 @@ skip-if = debug || asan # Bug 1354681
 [browser_ext_tabs_executeScript.js]
 [browser_ext_tabs_executeScript_good.js]
 [browser_ext_tabs_executeScript_bad.js]
 [browser_ext_tabs_executeScript_multiple.js]
 [browser_ext_tabs_executeScript_no_create.js]
 [browser_ext_tabs_executeScript_runAt.js]
 [browser_ext_tabs_getCurrent.js]
 [browser_ext_tabs_insertCSS.js]
+[browser_ext_tabs_lastAccessed.js]
 [browser_ext_tabs_removeCSS.js]
 [browser_ext_tabs_move_array.js]
 [browser_ext_tabs_move_window.js]
 [browser_ext_tabs_move_window_multiple.js]
 [browser_ext_tabs_move_window_pinned.js]
 [browser_ext_tabs_onHighlighted.js]
 [browser_ext_tabs_onUpdated.js]
 [browser_ext_tabs_printPreview.js]
--- a/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
+++ b/browser/components/extensions/test/browser/browser_ext_sessions_getRecentlyClosed_tabs.js
@@ -38,42 +38,49 @@ add_task(async function test_sessions_ge
   });
 
   let win = await BrowserTestUtils.openNewBrowserWindow();
   await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "about:mozilla");
   await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
   let expectedTabs = [];
   let tab = win.gBrowser.selectedTab;
   expectedTabs.push(expectedTabInfo(tab, win));
+  let lastAccessedTimes = new Map();
+  lastAccessedTimes.set("about:mozilla", tab.lastAccessed);
 
   for (let url of ["about:robots", "about:buildconfig"]) {
     tab = await BrowserTestUtils.openNewForegroundTab(win.gBrowser, url);
     expectedTabs.push(expectedTabInfo(tab, win));
+    lastAccessedTimes.set(url, tab.lastAccessed);
   }
 
   await extension.startup();
 
   // Test with a closed tab.
   await BrowserTestUtils.removeTab(tab);
 
   extension.sendMessage("check-sessions");
   let recentlyClosed = await extension.awaitMessage("recentlyClosed");
   let tabInfo = recentlyClosed[0].tab;
   let expectedTab = expectedTabs.pop();
   checkTabInfo(expectedTab, tabInfo);
+  ok(tabInfo.lastAccessed > lastAccessedTimes.get(tabInfo.url),
+     "lastAccessed has been updated");
 
   // Test with a closed window containing tabs.
   await BrowserTestUtils.closeWindow(win);
 
   extension.sendMessage("check-sessions");
   recentlyClosed = await extension.awaitMessage("recentlyClosed");
   let tabInfos = recentlyClosed[0].window.tabs;
   is(tabInfos.length, 2, "Expected number of tabs in closed window.");
   for (let x = 0; x < tabInfos.length; x++) {
     checkTabInfo(expectedTabs[x], tabInfos[x]);
+    ok(tabInfos[x].lastAccessed > lastAccessedTimes.get(tabInfos[x].url),
+       "lastAccessed has been updated");
   }
 
   await extension.unload();
 
   // Test without tabs permission.
   extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["sessions"],
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_lastAccessed.js
@@ -0,0 +1,42 @@
+"use strict";
+
+add_task(async function testLastAccessed() {
+  let past = Date.now();
+
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/?1");
+  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/?2");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["tabs"],
+    },
+    async background() {
+      browser.test.onMessage.addListener(async function(msg, past) {
+        if (msg !== "past") {
+          return;
+        }
+
+        let [tab1] = await browser.tabs.query({url: "https://example.com/?1"});
+        let [tab2] = await browser.tabs.query({url: "https://example.com/?2"});
+
+        browser.test.assertTrue(tab1 && tab2, "Expected tabs were found");
+
+        let now = Date.now();
+
+        browser.test.assertTrue(past < tab1.lastAccessed &&
+                                tab1.lastAccessed < tab2.lastAccessed &&
+                                tab2.lastAccessed <= now,
+                                "lastAccessed timestamps are recent and in the right order");
+
+        await browser.tabs.remove([tab1.id, tab2.id]);
+
+        browser.test.notifyPass("tabs.lastAccessed");
+      });
+    },
+  });
+
+  await extension.startup();
+  await extension.sendMessage("past", past);
+  await extension.awaitFinish("tabs.lastAccessed");
+  await extension.unload();
+});
--- a/browser/components/newtab/NewTabSearchProvider.jsm
+++ b/browser/components/newtab/NewTabSearchProvider.jsm
@@ -63,17 +63,21 @@ SearchProvider.prototype = {
   },
 
   removeFormHistory({browser}, suggestion) {
     ContentSearch.removeFormHistoryEntry({target: browser}, suggestion);
   },
 
   manageEngines(browser) {
     const browserWin = browser.ownerGlobal;
-    browserWin.openPreferences("paneGeneral", { origin: "contentSearch" });
+    if (Services.prefs.getBoolPref("browser.preferences.useOldOrganization")) {
+      browserWin.openPreferences("paneSearch", { origin: "contentSearch" });
+    } else {
+      browserWin.openPreferences("paneGeneral", { origin: "contentSearch" });
+    }
   },
 
   async asyncGetState() {
     let state = await ContentSearch.currentStateObj(true);
     return state;
   },
 
   async asyncPerformSearch({browser}, searchData) {
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -313,17 +313,21 @@ BrowserGlue.prototype = {
     Cu.import("resource://services-sync/main.js");
     Weave.Service.scheduler.delayedAutoConnect(delay);
   },
 
   // nsIObserver implementation
   observe: function BG_observe(subject, topic, data) {
     switch (topic) {
       case "notifications-open-settings":
-        this._openPreferences("privacy", { origin: "notifOpenSettings" });
+        if (Services.prefs.getBoolPref("browser.preferences.useOldOrganization")) {
+          this._openPreferences("content", { origin: "notifOpenSettings" });
+        } else {
+          this._openPreferences("privacy", { origin: "notifOpenSettings" });
+        }
         break;
       case "prefservice:after-app-defaults":
         this._onAppDefaults();
         break;
       case "final-ui-startup":
         this._beforeUIStartup();
         break;
       case "browser-delayed-startup-finished":
--- a/browser/extensions/formautofill/content/editProfile.xhtml
+++ b/browser/extensions/formautofill/content/editProfile.xhtml
@@ -25,17 +25,17 @@
       <input id="family-name" type="text"/>
     </label>
     <label id="organization-container">
       <span>Company</span>
       <input id="organization" type="text"/>
     </label>
     <label id="street-address-container">
       <span>Street Address</span>
-      <textarea id="street-address"/>
+      <textarea id="street-address" rows="3"/>
     </label>
     <label id="address-level2-container">
       <span>City/Town</span>
       <input id="address-level2" type="text"/>
     </label>
     <label id="address-level1-container">
       <span>State/Province</span>
       <input id="address-level1" type="text"/>
--- a/browser/extensions/formautofill/content/manageProfiles.css
+++ b/browser/extensions/formautofill/content/manageProfiles.css
@@ -20,16 +20,17 @@ fieldset > legend {
   box-sizing: border-box;
   width: 100%;
   padding: 0.4em 0.7em;
   font-size: 0.9em;
   color: #808080;
   background-color: var(--in-content-box-background-hover);
   border: 1px solid var(--in-content-box-border-color);
   border-radius: 2px 2px 0 0;
+  -moz-user-select: none;
 }
 
 option:nth-child(even) {
   background-color: -moz-oddtreerow;
 }
 
 #profiles {
   font-size: 0.85em;
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -234,17 +234,18 @@ ManageProfileDialog.prototype = {
    *
    * @param  {DOMEvent} event
    */
   handleClick(event) {
     if (event.target == this._elements.remove) {
       this.removeAddresses(this._selectedOptions.map(option => option.value));
     } else if (event.target == this._elements.add) {
       this.openEditDialog();
-    } else if (event.target == this._elements.edit) {
+    } else if (event.target == this._elements.edit ||
+               event.target.parentNode == this._elements.addresses && event.detail > 1) {
       this.openEditDialog(this._selectedOptions[0].address);
     }
   },
 
   /**
    * Handle key press events
    *
    * @param  {DOMEvent} event
@@ -269,24 +270,26 @@ ManageProfileDialog.prototype = {
 
   /**
    * Attach event listener
    */
   attachEventListeners() {
     window.addEventListener("unload", this, {once: true});
     window.addEventListener("keypress", this);
     this._elements.addresses.addEventListener("change", this);
+    this._elements.addresses.addEventListener("click", this);
     this._elements.controlsContainer.addEventListener("click", this);
     Services.obs.addObserver(this, "formautofill-storage-changed");
   },
 
   /**
    * Remove event listener
    */
   detachEventListeners() {
     window.removeEventListener("keypress", this);
     this._elements.addresses.removeEventListener("change", this);
+    this._elements.addresses.removeEventListener("click", this);
     this._elements.controlsContainer.removeEventListener("click", this);
     Services.obs.removeObserver(this, "formautofill-storage-changed");
   },
 };
 
 new ManageProfileDialog();
--- a/browser/extensions/formautofill/locale/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locale/en-US/formautofill.properties
@@ -24,10 +24,10 @@ organization = company
 tel = phone
 email = email
 # LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
 fieldNameSeparator = ,\u0020
 # LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
 # text that is displayed for informing users what categories are about to be filled.
 # "%S" will be replaced with a list generated from the pre-defined categories.
 # The text would be e.g. Also fill company, phone, email
-phishingWarningMessage = Also fill %S
-phishingWarningMessage2 = Fill %S
+phishingWarningMessage = Also autofills %S
+phishingWarningMessage2 = Autofills %S
--- a/browser/extensions/formautofill/skin/shared/editProfile.css
+++ b/browser/extensions/formautofill/skin/shared/editProfile.css
@@ -21,16 +21,17 @@ label {
 }
 
 label > span {
   box-sizing: border-box;
   flex: 0 0 8em;
   padding-inline-end: 0.5em;
   align-self: center;
   text-align: end;
+  -moz-user-select: none;
 }
 
 input,
 select {
   box-sizing: border-box;
   flex: 1 0 auto;
   width: calc(50% - 8em);
 }
--- a/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
+++ b/browser/extensions/formautofill/test/browser/browser_autocomplete_footer.js
@@ -64,24 +64,24 @@ add_task(async function test_press_enter
 
 add_task(async function test_phishing_warning() {
   await BrowserTestUtils.withNewTab({gBrowser, url: URL}, async function(browser) {
     const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
 
     await openPopupOn(browser, "#street-address");
     const warningBox = itemsBox.querySelector(".autocomplete-richlistitem:last-child")._warningTextBox;
     ok(warningBox, "Got phishing warning box");
-    await expectWarningText(browser, "Also fill company, phone, email");
+    await expectWarningText(browser, "Also autofills company, phone, email");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectWarningText(browser, "Also fill company, phone, email");
+    await expectWarningText(browser, "Also autofills company, phone, email");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectWarningText(browser, "Fill address");
+    await expectWarningText(browser, "Autofills address");
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
     await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-    await expectWarningText(browser, "Also fill company, phone, email");
+    await expectWarningText(browser, "Also autofills company, phone, email");
 
     // Ensure the popup is closed before entering the next test.
     await ContentTask.spawn(browser, {}, async function() {
       content.document.getElementById("street-address").blur();
     });
     await BrowserTestUtils.waitForCondition(() => !autoCompletePopup.popupOpen);
   });
 });
--- a/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_first_time_use_doorhanger.js
@@ -27,19 +27,18 @@ add_task(async function test_first_time_
         form.querySelector("#tel").value = "1-345-345-3456";
 
         // Wait 500ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 500));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
-      let notificationElement = PopupNotifications.panel.firstChild;
       // Open the panel via main button
-      notificationElement.button.click();
+      await clickDoorhangerButton(MAIN_BUTTON_INDEX);
       let tab = await tabPromise;
       ok(tab, "Privacy panel opened");
       await BrowserTestUtils.removeTab(tab);
     }
   );
 
   addresses = await getAddresses();
   is(addresses.length, 1, "Address saved");
--- a/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js
+++ b/browser/extensions/formautofill/test/browser/browser_update_doorhanger.js
@@ -6,115 +6,97 @@ add_task(async function test_update_addr
   await saveAddress(TEST_ADDRESS_1);
   let addresses = await getAddresses();
   is(addresses.length, 1, "1 address in storage");
 
   await BrowserTestUtils.withNewTab({gBrowser, url: FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      await ContentTask.spawn(browser, null, async function() {
-        content.document.querySelector("form #organization").focus();
-      });
-
-      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-      await expectPopupOpen(browser);
+      await openPopupOn(browser, "form #organization");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
 
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let org = form.querySelector("#organization");
         await new Promise(resolve => setTimeout(resolve, 1000));
         org.value = "Mozilla";
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
-      let notificationElement = PopupNotifications.panel.firstChild;
-      notificationElement.button.click();
+      await clickDoorhangerButton(MAIN_BUTTON_INDEX);
     }
   );
 
   addresses = await getAddresses();
   is(addresses.length, 1, "Still 1 address in storage");
   is(addresses[0].organization, "Mozilla", "Verify the organization field");
 });
 
 add_task(async function test_create_new_address() {
   let addresses = await getAddresses();
   is(addresses.length, 1, "1 address in storage");
 
   await BrowserTestUtils.withNewTab({gBrowser, url: FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      await ContentTask.spawn(browser, null, async function() {
-        content.document.querySelector("form #tel").focus();
-      });
-
-      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-      await expectPopupOpen(browser);
+      await openPopupOn(browser, "form #tel");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
 
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let tel = form.querySelector("#tel");
         await new Promise(resolve => setTimeout(resolve, 1000));
         tel.value = "+1-234-567-890";
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
-      let notificationElement = PopupNotifications.panel.firstChild;
-      notificationElement.secondaryButton.click();
+      await clickDoorhangerButton(SECONDARY_BUTTON_INDEX);
     }
   );
 
   addresses = await getAddresses();
   is(addresses.length, 2, "2 addresses in storage");
   is(addresses[1].tel, "+1-234-567-890", "Verify the tel field");
 });
 
 add_task(async function test_create_new_address_merge() {
   let addresses = await getAddresses();
-  is(addresses.length, 2, "2 address in storage");
+  is(addresses.length, 2, "2 addresses in storage");
 
   await BrowserTestUtils.withNewTab({gBrowser, url: FORM_URL},
     async function(browser) {
       let promiseShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel,
                                                        "popupshown");
-      await ContentTask.spawn(browser, null, async function() {
-        content.document.querySelector("form #tel").focus();
-      });
-
-      await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
-      await expectPopupOpen(browser);
+      await openPopupOn(browser, "form #tel");
       await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
       await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, browser);
 
       // Choose the latest address and revert to the original phone number
       await ContentTask.spawn(browser, null, async function() {
         let form = content.document.getElementById("form");
         let tel = form.querySelector("#tel");
         tel.value = "+1 617 253 5702";
 
         // Wait 1000ms before submission to make sure the input value applied
         await new Promise(resolve => setTimeout(resolve, 1000));
         form.querySelector("input[type=submit]").click();
       });
 
       await promiseShown;
-      let notificationElement = PopupNotifications.panel.firstChild;
-      notificationElement.secondaryButton.click();
+      await clickDoorhangerButton(SECONDARY_BUTTON_INDEX);
     }
   );
 
   addresses = await getAddresses();
   is(addresses.length, 2, "Still 2 addresses in storage");
 });
--- a/browser/extensions/formautofill/test/browser/head.js
+++ b/browser/extensions/formautofill/test/browser/head.js
@@ -1,11 +1,11 @@
 /* exported MANAGE_PROFILES_DIALOG_URL, EDIT_PROFILE_DIALOG_URL, BASE_URL,
             TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3,
-            sleep, expectPopupOpen, openPopupOn,
+            sleep, expectPopupOpen, openPopupOn, clickDoorhangerButton,
             getAddresses, saveAddress, removeAddresses */
 
 "use strict";
 
 const MANAGE_PROFILES_DIALOG_URL = "chrome://formautofill/content/manageProfiles.xhtml";
 const EDIT_PROFILE_DIALOG_URL = "chrome://formautofill/content/editProfile.xhtml";
 const BASE_URL = "http://mochi.test:8888/browser/browser/extensions/formautofill/test/browser/";
 
@@ -28,16 +28,19 @@ const TEST_ADDRESS_2 = {
   country: "US",
 };
 
 const TEST_ADDRESS_3 = {
   "street-address": "Other Address",
   "postal-code": "12345",
 };
 
+const MAIN_BUTTON_INDEX = 0;
+const SECONDARY_BUTTON_INDEX = 1;
+
 async function sleep(ms = 500) {
   await new Promise(resolve => setTimeout(resolve, ms));
 }
 
 async function expectPopupOpen(browser) {
   const {autoCompletePopup, autoCompletePopup: {richlistbox: itemsBox}} = browser;
   const listItemElems = itemsBox.querySelectorAll(".autocomplete-richlistitem");
 
@@ -76,14 +79,40 @@ function saveAddress(address) {
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
 function removeAddresses(guids) {
   Services.cpmm.sendAsyncMessage("FormAutofill:RemoveAddresses", {guids});
   return TestUtils.topicObserved("formautofill-storage-changed");
 }
 
+/**
+ * Clicks the popup notification button and wait for popup hidden.
+ *
+ * @param {number} buttonIndex Number indicating which button to click.
+ *                             See the constants in this file.
+ */
+async function clickDoorhangerButton(buttonIndex) {
+  let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
+  let notifications = PopupNotifications.panel.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  ok(true, notifications.length + " notification(s)");
+  let notification = notifications[0];
+
+  if (buttonIndex == MAIN_BUTTON_INDEX) {
+    ok(true, "Triggering main action");
+    EventUtils.synthesizeMouseAtCenter(notification.button, {});
+  } else if (buttonIndex == SECONDARY_BUTTON_INDEX) {
+    ok(true, "Triggering secondary action");
+    EventUtils.synthesizeMouseAtCenter(notification.secondaryButton, {});
+  } else if (notification.childNodes[buttonIndex - 1]) {
+    ok(true, "Triggering secondary action with index " + buttonIndex);
+    EventUtils.synthesizeMouseAtCenter(notification.childNodes[buttonIndex - 1], {});
+  }
+  await popuphidden;
+}
+
 registerCleanupFunction(async function() {
   let addresses = await getAddresses();
   if (addresses.length) {
     await removeAddresses(addresses.map(address => address.guid));
   }
 });
--- a/browser/extensions/onboarding/content/onboarding-tour-agent.js
+++ b/browser/extensions/onboarding/content/onboarding-tour-agent.js
@@ -10,18 +10,29 @@ document.getElementById("onboarding-over
                         .addEventListener("click", evt => {
   switch (evt.target.id) {
     case "onboarding-tour-addons-button":
       Mozilla.UITour.showHighlight("addons");
       break;
     case "onboarding-tour-customize-button":
       Mozilla.UITour.showHighlight("customize");
       break;
-   case "onboarding-tour-default-browser-button":
-      Mozilla.UITour.setConfiguration("defaultBrowser");
+    case "onboarding-tour-default-browser-button":
+      Mozilla.UITour.getConfiguration("appinfo", (config) => {
+        let isDefaultBrowser = config.defaultBrowser;
+        let btn = document.getElementById("onboarding-tour-default-browser-button");
+        let msg = document.getElementById("onboarding-tour-is-default-browser-msg");
+        if (isDefaultBrowser) {
+          btn.classList.add("onboarding-hidden");
+          msg.classList.remove("onboarding-hidden");
+        } else {
+          btn.disabled = true;
+          Mozilla.UITour.setConfiguration("defaultBrowser");
+        }
+      });
       break;
     case "onboarding-tour-private-browsing-button":
       Mozilla.UITour.showHighlight("privateWindow");
       break;
     case "onboarding-tour-search-button":
       Mozilla.UITour.openSearchPanel(() => {});
       break;
     case "onboarding-tour-sync-button":
@@ -29,14 +40,15 @@ document.getElementById("onboarding-over
       if (emailInput.checkValidity()) {
         Mozilla.UITour.showFirefoxAccounts(null, emailInput.value);
       }
       break;
     case "onboarding-overlay":
     case "onboarding-overlay-close-btn":
       // Dismiss any highlights if a user tries to close the dialog.
       Mozilla.UITour.hideHighlight();
+      break;
   }
   // Dismiss any highlights if a user tries to change to other tours.
   if (evt.target.classList.contains("onboarding-tour-item")) {
     Mozilla.UITour.hideHighlight();
   }
 });
--- a/browser/extensions/onboarding/content/onboarding.css
+++ b/browser/extensions/onboarding/content/onboarding.css
@@ -28,17 +28,18 @@
   height: 29px;
   position: absolute;
   cursor: pointer;
   top: 30px;
   offset-inline-start: 30px;
   background: url("img/overlay-icon.svg") no-repeat;
 }
 
-#onboarding-overlay-dialog {
+#onboarding-overlay-dialog,
+.onboarding-hidden {
   display: none;
 }
 
 #onboarding-overlay-close-btn,
 #onboarding-notification-close-btn {
   position: absolute;
   top: 15px;
   offset-inline-end: 15px;
@@ -144,16 +145,26 @@
   padding-inline-start: 29px;
 }
 
 #onboarding-tour-list > li.onboarding-active,
 #onboarding-tour-list > li:hover {
   color: #0A84FF;
 }
 
+/* Default browser tour */
+#onboarding-tour-is-default-browser-msg {
+  font-size: 16px;
+  line-height: 21px;
+  float: inline-end;
+  margin-inline-end: 26px;
+  margin-top: -32px;
+  text-align: center;
+}
+
 /* Sync tour */
 #onboarding-tour-sync-page form {
   text-align: center;
 }
 
 #onboarding-tour-sync-page form > h3 {
   text-align: center;
   margin: 0;
@@ -256,23 +267,23 @@
   border: none;
   border-radius: 0;
   color: #fff;
   float: inline-end;
   margin-inline-end: 26px;
   margin-top: -32px;
 }
 
-.onboarding-tour-action-button:hover:not([disabled="true"]) ,
+.onboarding-tour-action-button:hover:not([disabled]) ,
 #onboarding-notification-action-btn:hover {
   background: #0060df;
   cursor: pointer;
 }
 
-.onboarding-tour-action-button:active:not([disabled="true"]),
+.onboarding-tour-action-button:active:not([disabled]),
 #onboarding-notification-action-btn:active  {
   background: #003EAA;
 }
 
 .onboarding-tour-action-button:disabled {
   opacity: 0.5;
 }
 
--- a/browser/extensions/onboarding/content/onboarding.js
+++ b/browser/extensions/onboarding/content/onboarding.js
@@ -155,31 +155,34 @@ var onboardingTourset = {
     tourNameId: "onboarding.tour-default-browser",
     getNotificationStrings(bundle) {
       return {
         title: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.title", [BRAND_SHORT_NAME], 1),
         message: bundle.formatStringFromName("onboarding.notification.onboarding-tour-default-browser.message", [BRAND_SHORT_NAME], 1),
         button: bundle.GetStringFromName("onboarding.button.learnMore"),
       };
     },
-    getPage(win) {
+    getPage(win, bundle) {
       let div = win.document.createElement("div");
       let defaultBrowserButtonId = win.matchMedia("(-moz-os-version: windows-win7)").matches ?
         "onboarding.tour-default-browser.win7.button" : "onboarding.tour-default-browser.button";
+      let isDefaultMessage = bundle.GetStringFromName("onboarding.tour-default-browser.is-default.message");
+      let isDefault2ndMessage = bundle.formatStringFromName("onboarding.tour-default-browser.is-default.2nd-message", [BRAND_SHORT_NAME], 1);
       // eslint-disable-next-line no-unsanitized/property
       div.innerHTML = `
         <section class="onboarding-tour-description">
           <h1 data-l10n-id="onboarding.tour-default-browser.title2"></h1>
           <p data-l10n-id="onboarding.tour-default-browser.description2"></p>
         </section>
         <section class="onboarding-tour-content">
           <img src="resource://onboarding/img/figure_default.svg" />
         </section>
         <aside class="onboarding-tour-button-container">
           <button id="onboarding-tour-default-browser-button" class="onboarding-tour-action-button" data-l10n-id="${defaultBrowserButtonId}"></button>
+          <div id="onboarding-tour-is-default-browser-msg" class="onboarding-hidden">${isDefaultMessage}<br/>${isDefault2ndMessage}</div>
         </aside>
       `;
       return div;
     },
   },
   "sync": {
     id: "onboarding-tour-sync",
     tourNameId: "onboarding.tour-sync2",
@@ -228,18 +231,16 @@ var onboardingTourset = {
  */
 class Onboarding {
   constructor(contentWindow) {
     this.init(contentWindow);
   }
 
   async init(contentWindow) {
     this._window = contentWindow;
-    this._tourItems = [];
-    this._tourPages = [];
     this._tours = [];
 
     let tourIds = this._getTourIDList(Services.prefs.getStringPref("browser.onboarding.tour-type", "update"));
     tourIds.forEach(tourId => {
       if (onboardingTourset[tourId]) {
         this._tours.push(onboardingTourset[tourId]);
       }
     });
@@ -275,16 +276,18 @@ class Onboarding {
     }
   }
 
   _initUI() {
     if (this.uiInitialized) {
       return;
     }
     this.uiInitialized = true;
+    this._tourItems = [];
+    this._tourPages = [];
 
     this._overlayIcon = this._renderOverlayIcon();
     this._overlayIcon.addEventListener("click", this);
     this._window.document.body.appendChild(this._overlayIcon);
 
     this._overlay = this._renderOverlay();
     this._overlay.addEventListener("click", this);
     this._window.document.body.appendChild(this._overlay);
@@ -406,16 +409,17 @@ class Onboarding {
 
     this._clearPrefObserver();
     this._overlayIcon.remove();
     this._overlay.remove();
     if (this._notificationBar) {
       this._notificationBar.remove();
     }
 
+    this._tourItems = this._tourPages =
     this._overlayIcon = this._overlay = this._notificationBar = null;
   }
 
   toggleOverlay() {
     if (this._tourItems.length == 0) {
       // Lazy loading until first toggle.
       this._loadTours(this._tours);
     }
@@ -459,17 +463,17 @@ class Onboarding {
     });
     if (params.length > 0) {
       this.sendMessageToChrome("set-prefs", params);
     }
   }
 
   markTourCompletionState(tourId) {
     // We are doing lazy load so there might be no items.
-    if (this._tourItems.length > 0 && this.isTourCompleted(tourId)) {
+    if (this._tourItems && this._tourItems.length > 0 && this.isTourCompleted(tourId)) {
       let targetItem = this._tourItems.find(item => item.id == tourId);
       targetItem.classList.add("onboarding-complete");
     }
   }
 
   _muteNotificationOnFirstSession() {
     if (Preferences.isSet("browser.onboarding.notification.tour-ids-queue")) {
       // There is a queue. We had prompted before, this must not be the 1st session.
--- a/browser/extensions/onboarding/locales/en-US/onboarding.properties
+++ b/browser/extensions/onboarding/locales/en-US/onboarding.properties
@@ -51,16 +51,20 @@ onboarding.tour-default-browser=Default 
 # LOCALIZATION NOTE(onboarding.tour-default-browser.title2): This string will be used in the default browser tour title. %S is brandShortName
 onboarding.tour-default-browser.title2=Make %S your go-to browser.
 # LOCALIZATION NOTE(onboarding.tour-default-browser.description2): This string will be used in the default browser tour description. %1$S is brandShortName
 onboarding.tour-default-browser.description2=Love %1$S? Set it as your default browser. Open a link from another application, and %1$S will be there for you.
 # LOCALIZATION NOTE(onboarding.tour-default-browser.button): Label for a button to open the OS default browser settings where it's not possible to set the default browser directly. (OSX, Linux, Windows 8 and higher)
 onboarding.tour-default-browser.button=Open Default Browser Settings
 # LOCALIZATION NOTE(onboarding.tour-default-browser.win7.button): Label for a button to directly set the default browser (Windows 7). %S is brandShortName
 onboarding.tour-default-browser.win7.button=Make %S Your Default Browser
+# LOCALIZATION NOTE(onboarding.tour-default-browser.is-default.message): Label displayed when Firefox is already set as default browser. followed on a new line by "tour-default-browser.is-default.2nd-message".
+onboarding.tour-default-browser.is-default.message=You’ve got this!
+# LOCALIZATION NOTE(onboarding.tour-default-browser.is-default.2nd-message): Label displayed when Firefox is already set as default browser. %S is brandShortName
+onboarding.tour-default-browser.is-default.2nd-message=%S is already your default browser.
 # LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.title): This string will be used in the notification message for the default browser tour. %S is brandShortName.
 onboarding.notification.onboarding-tour-default-browser.title=Make %S your go-to browser.
 # LOCALIZATION NOTE(onboarding.notification.onboarding-tour-default-browser.message): %1$S is brandShortName
 onboarding.notification.onboarding-tour-default-browser.message=It doesn’t take much to get the most from %1$S. Just set %1$S as your default browser and put control, customization, and protection on autopilot.
 
 onboarding.tour-sync2=Sync
 onboarding.tour-sync.title2=Pick up where you left off.
 onboarding.tour-sync.description2=Sync makes it easy to access bookmarks, passwords, and even open tabs on all your devices. Sync also gives you control of the types of information you want, and don’t want, to share.
--- a/browser/extensions/onboarding/test/browser/head.js
+++ b/browser/extensions/onboarding/test/browser/head.js
@@ -51,17 +51,17 @@ function promiseOnboardingOverlayLoaded(
         resolve(false);
       });
     })
   };
   return BrowserTestUtils.waitForCondition(
     condition,
     "Should load onboarding overlay",
     100,
-    30
+    50 // Bug 1381335 increased retries, so debug builds can trigger idle in time
   );
 }
 
 function promiseOnboardingOverlayOpened(browser) {
   let condition = () => {
     return ContentTask.spawn(browser, {}, function() {
       return new Promise(resolve => {
         let overlay = content.document.querySelector("#onboarding-overlay");
--- a/browser/locales/search/list.json
+++ b/browser/locales/search/list.json
@@ -717,17 +717,17 @@
         "visibleDefaultEngines": [
           "google", "yahoo", "bing", "amazondotcom", "ddg", "twitter", "wikipedia-uz"
         ]
       }
     },
     "vi": {
       "default": {
         "visibleDefaultEngines": [
-          "google", "ddg", "wikipedia-vi", "zing-mp3"
+          "google", "coccoc", "ddg", "wikipedia-vi", "zing-mp3"
         ]
       }
     },
     "wo": {
       "default": {
         "visibleDefaultEngines": [
           "google", "yahoo", "bing", "amazondotcom", "ddg", "wikipedia-wo"
         ]
new file mode 100644
--- /dev/null
+++ b/browser/locales/searchplugins/coccoc.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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/. -->
+
+<SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
+<ShortName>Cốc Cốc</ShortName>
+<Description>Use Cốc Cốc to search on coccoc.com</Description>
+<InputEncoding>UTF-8</InputEncoding>
+<Image width="16" height="16">data:image/ico;base64,AAABAAIAEBAAAAAAIABoBAAAJgAAACAgAAAAACAAqBAAAI4EAAAoAAAAEAAAACAAAAABACAAAAAAAEAEAAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8B////Af///wH///8BIHzwGSB88HcgfPA1////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88EsgfPD/IHzw/yB88OMgfPCTIHzwQyB88AX///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwUyB88O8gfPD/IHzw/yB88P8gfPDtIHzwoSB88E0gfPAD////Af///wH///8B////AVi0ZyNYtGcF////Af///wEgfPAjIHzwyyB88P8gfPD/IHzw/yB88P8gfPD/IHzwn////wH///8B////AVi0ZxFYtGf9WLRnuVi0Zw3///8B////ASB88AcgfPCPIHzw/yB88P8gfPD/IHzw/yB88PsgfPAzIHzwE////wH///8BWLRnu1i0Z/9YtGfJWLRnFf///wH///8B////ASB88FEgfPDvIHzw/yB88P8gfPDHIHzw+yB88Kf///8B////AVi0ZzVYtGf/WLRn/1i0Z9lYtGch////Af///wH///8BIHzwHyB88IsgfPCHIHzwFyB88PkgfPD5IHzwGf///wH///8BWLRnqVi0Z/9YtGf/WLRn5Vi0Zy////8B////Af///wH///8B////Af///wEgfPDtIHzw/yB88Hv///8B////AVi0ZydYtGf7WLRn/1i0Z/9YtGfvWLRnPf///wH///8B////Af///wH///8BIHzw4SB88P8gfPDhIHzwA////wH///8BWLRnl1i0Z/9YtGf/WLRn/1i0Z/FYtGcR////Af///wH///8B////ASB88NMgfPD/IHzw/yB88FH///8B////AVi0ZxtYtGf1WLRn/1i0Z/9YtGf/WLRnL////wH///8B////Af///wEgfPDHIHzw/yB88P8gfPC5////Af///wH///8BWLRnc1i0Z/9YtGf/WLRnw1i0ZwP///8B////Af///wH///8BIHzwuyB88P8gfPD/IHzw/SB88Cf///8B////Af///wFYtGctWLRnQ1i0ZwX///8B////Af///wH///8B////ASB88K0gfPD/IHzw/yB88P8gfPBp////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPBfIHzw/yB88P8gfPD3IHzwK////wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88EsgfPB9IHzwMf///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////AQAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8AAP//AAD//wAA//8oAAAAIAAAAEAAAAABACAAAAAAAIAQAAAAAAAAAAAAAAAAAAAAAAAA////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwYSB88PcgfPDhIHzwkSB88EEgfPAD////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPDFIHzw/yB88P8gfPD/IHzw/yB88O0gfPCfIHzwTSB88An///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88GcgfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw9SB88K0gfPBbIHzwD////wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88EcgfPDrIHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD7IHzwuSB88GkgfPAb////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88BsgfPDBIHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPDFIHzwbSB88An///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88AMgfPCFIHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw0SB88BH///8B////Af///wH///8B////Af///wH///8B////AVi0Zy1YtGdhWLRnD////wH///8B////Af///wH///8B////Af///wEgfPBHIHzw6yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzwm////wH///8B////Af///wH///8B////Af///wFYtGcbWLRn+Vi0Z/9YtGfbWLRnIf///wH///8B////Af///wH///8B////Af///wEgfPAZIHzwvSB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPDx////Af///wH///8B////Af///wH///8B////AVi0ZytYtGf/WLRn/1i0Z/9YtGfnWLRnL////wH///8B////Af///wH///8B////Af///wEgfPADIHzwgSB88P0gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P////8B////Af///wH///8B////Af///wH///8B////AVi0Z71YtGf/WLRn/1i0Z/9YtGfxWLRnP////wH///8B////Af///wH///8B////Af///wH///8BIHzwQyB88OkgfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw0SB88DkgfPCVIHzwS////wH///8B////Af///wH///8BWLRnNVi0Z/9YtGf/WLRn/1i0Z/9YtGf5WLRnU////wH///8B////Af///wH///8B////Af///wH///8BIHzwGSB88L0gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPBPIHzw7SB88P8gfPD7IHzwHf///wH///8B////Af///wH///8BWLRnq1i0Z/9YtGf/WLRn/1i0Z/9YtGf9WLRnaf///wH///8B////Af///wH///8B////Af///wH///8BIHzwAyB88HcgfPDzIHzw/yB88P8gfPDnIHzwWf///wEgfPD/IHzw/yB88P8gfPCF////Af///wH///8B////Af///wFYtGcnWLRn/Vi0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRngf///wH///8B////Af///wH///8B////Af///wH///8B////ASB88AkgfPA1IHzwMSB88AP///8B////ASB88PkgfPD/IHzw/yB88OkgfPAH////Af///wH///8B////Af///wFYtGeZWLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnmVi0ZwP///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzw7SB88P8gfPD/IHzw/yB88Fn///8B////Af///wH///8B////AVi0ZxlYtGf3WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnr1i0Zwn///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPDhIHzw/yB88P8gfPD/IHzwwf///wH///8B////Af///wH///8B////AVi0Z4dYtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnw1i0Zw////8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88NMgfPD/IHzw/yB88P8gfPD/IHzwLf///wH///8B////Af///wH///8BWLRnEVi0Z+9YtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnz1i0ZxX///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwxyB88P8gfPD/IHzw/yB88P8gfPCX////Af///wH///8B////Af///wH///8BWLRndVi0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnx1i0ZwP///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPC9IHzw/yB88P8gfPD/IHzw/yB88PMgfPAN////Af///wH///8B////Af///wFYtGcJWLRn41i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRnP////wH///8B////Af///wH///8B////Af///wH///8B////ASB88LEgfPD/IHzw/yB88P8gfPD/IHzw/yB88Gv///8B////Af///wH///8B////Af///wFYtGdjWLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGdt////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwoSB88P8gfPD/IHzw/yB88P8gfPD/IHzw1f///wH///8B////Af///wH///8B////AVi0ZwVYtGfXWLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z1H///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPCVIHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzwP////wH///8B////Af///wH///8B////AVi0Z0tYtGf/WLRn/1i0Z/9YtGf/WLRn/1i0Z/9YtGflWLRnC////wH///8B////Af///wH///8B////Af///wH///8B////ASB88IsgfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPCp////Af///wH///8B////Af///wH///8B////AVi0Z39YtGf/WLRn/1i0Z/9YtGf/WLRn7Vi0Zzn///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwfyB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88PsgfPAZ////Af///wH///8B////Af///wH///8B////AVi0ZzFYtGeDWLRnm1i0Z3FYtGcV////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPBxIHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88H////8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////ASB88GMgfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPD/IHzw0f///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8BIHzwUyB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88P8gfPDT////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPATIHzw9yB88P8gfPD/IHzw/yB88P8gfPD/IHzw/yB88JH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPBvIHzw/yB88P8gfPD/IHzw/yB88P8gfPDjIHzwF////wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wEgfPBbIHzw0yB88P8gfPDzIHzwpSB88Bv///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////Af///wH///8B////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA</Image>
+<Url type="application/x-suggestions+json" method="GET"
+     template="https://coccoc.com/composer/autocomplete">
+  <Param name="of"  value="b" />
+  <Param name="q" value="{searchTerms}"/>
+  <Param name="s" value="ff"/>
+</Url>
+<Url type="text/html" method="GET" template="https://coccoc.com/search" resultdomain="coccoc.com">
+  <Param name="query" value="{searchTerms}"/>
+  <Param name="s" value="ff"/>
+  <Param name="utm_source" value="firefox"/>
+</Url>
+</SearchPlugin>
--- a/browser/themes/linux/syncedtabs/sidebar.css
+++ b/browser/themes/linux/syncedtabs/sidebar.css
@@ -41,17 +41,17 @@ html {
 .textbox-search-clear {
   background-image: url(moz-icon://stock/gtk-clear?size=menu);
   background-repeat: no-repeat;
   width: 16px;
   height: 16px;
 }
 
 .textbox-search-icon {
-  background-image: url(moz-icon://stock/gtk-find?size=menu);
+  background-image: url(chrome://global/skin/icons/search-textbox.svg);
   background-repeat: no-repeat;
   width: 16px;
   height: 16px;
   display: block;
 }
 
 .textbox-search-icon[searchbutton]:not([disabled]) ,
 .textbox-search-clear:not([disabled]) {
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -12,17 +12,18 @@
   font-size: 1.25rem;
 }
 
 * {
   -moz-user-select: text;
 }
 
 button,
-treecol {
+treecol,
+html|option {
   /* override the * rule */
   -moz-user-select: none;
 }
 
 #engineList treechildren::-moz-tree-image(engineShown, checked),
 #blocklistsTree treechildren::-moz-tree-image(selectionCol, checked) {
   list-style-image: url("chrome://global/skin/in-content/check.svg");
   -moz-context-properties: fill, stroke;
index 17d27c99eede5f41ab7114e3e97762c0d6daf5b1..a5fcc1dec4abcb966b3066bb4b50db4a45bedb57
GIT binary patch
literal 984
zc$@*)11J26P)<h;3K|Lk000e1NJLTq001Tc002k`1ONa4Fv?F400002VoOIv0063u
zBQgL0010qNS#tmY3ljhU3ljkVnw%H_000McNliru;Rg;1Apx~KX$JrR18GS_K~z}7
zt(e=6+ei#VPf>E_ve=*6{|AD6N?w9&#$xS5x9%6WXGg-ohAbVEtRlNT`2G9aKd-#-
z1xHU$lK5+2ug)AWzv2HPd)_khP6D9CC;klV-GlEWaRTsDk#Xl^16#hT9oX|1AN=7?
zS05Vx@_As-yZ$Knpr1eqB;AC2DzY0vzk(S7s^I4O^}G=z!3{93uq0QJwyL>-B}pQL
z7C^T|*YgV8p~7-Hhw8d)*R$u<_6O9OEkT+POW*~%=Y`~7k{;ul-U#XzJu%kqGR+s=
zbz`Weona;U@rv4f7Sp+Qz6l`e`|(SB4I7Um*c{ggyYyUNz2$nVCTM}hqQ#mb(_;LT
zdX+Osx`ixy)TJ@K)4>^=>kkE3A6lg9nr3c*r+(WCkgHgj>b6bwM1YYcNC*;5I&SrB
zPE$3<fE+4lo8kiz=E!=m$xM*JK9oe)?KHw};M(K{ltj*6YZ76GeZx)$Destc$8l^d
zTzRHuf%%RE*cf#|q#ZS`*^do1v(qxdPCFeovM&1bRLvVERThy>gGbC!cbq6X!PH}h
zQY{aJy09~!QgnuOwS7We^qCB%J;8079hPkdBt*(#NfAV*k5s)#Q{W&7g@cGdR;iqi
zqtkX!#-h=KP$&pGI)}qiKi7rh4(C}AwYj4-ew0c#S8M^3R6vqcNI{7+X!P28Y6uYy
zge-5z=0kfdhvVGNHNx_m$I%=p8k?j`nvx<E(#864X<ET(fzc9+2xyUEg1JswghD7s
zC6~Z5)*Dx+MO>1qVO8KIR&N#@pQl+p0>Y}|R{UkPDz-mRh$+61Aj=ZE9*>6;ceSFc
zra2jPgh)ET?4peQ#zysV75FylmUcS)wr!Oj_$+pNE{!RcB>mYHeKcBaRFy5miLnm)
zGY)>nX|b40vOJ$=dAQu$0@z%Q!*ZWJ{-AQPo#>)%s<xy#wbVFyOUca6t_LwL+iC8A
zX$#&ud`{AIRq$|WY_8e4o0@CbA5QkgK6jPu1N_z+^L=wmt&{bxSI=v9n(X|r&*D3%
z*<$oORk3}|7Qefm|Nl+D4{J8aQzTtjW0&cZYOY7nSM=r{yW7HCz-+s{F1m&dPW$s?
zXRYSnJ-1)`j<%%dYxWYoR!hUduxhkP&ySz{)ZV<GIsOCIEO#x?2IFV|0000<MNUMn
GLSTZ6_0ZY?
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -258,35 +258,35 @@
   padding-inline-start: var(--tab-curve-half-width);
 }
 
 /* Tab Overflow */
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]),
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
   background-image: url(chrome://browser/skin/tabbrowser/tab-overflow-indicator.png);
   background-size: 100% 100%;
-  width: 14px;
+  width: 19px;
   margin-bottom: calc(var(--navbar-tab-toolbar-highlight-overlap) + var(--tab-toolbar-navbar-overlap));
   pointer-events: none;
   position: relative;
   z-index: 3; /* the selected tab's z-index + 1 */
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:-moz-locale-dir(rtl),
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:-moz-locale-dir(ltr) {
   transform: scaleX(-1);
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator:not([collapsed]) {
   margin-inline-start: -2px;
-  margin-inline-end: -12px;
+  margin-inline-end: -17px;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator:not([collapsed]) {
-  margin-inline-start: -12px;
+  margin-inline-start: -17px;
   margin-inline-end: -2px;
 }
 
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-start-indicator[collapsed],
 .tabbrowser-arrowscrollbox > .arrowscrollbox-overflow-end-indicator[collapsed] {
   opacity: 0;
 }
 
--- a/browser/themes/windows/syncedtabs/sidebar.css
+++ b/browser/themes/windows/syncedtabs/sidebar.css
@@ -29,17 +29,17 @@ html {
   padding: 2px 2px 3px;
   padding-inline-start: 4px;
   color: -moz-FieldText;
 }
 
 .textbox-search-icon {
   width: 16px;
   height: 16px;
-  background-image: url(chrome://global/skin/icons/Search-glass.png);
+  background-image: url(chrome://global/skin/icons/search-textbox.svg);
   background-repeat: no-repeat;
   display: block;
 }
 
 .textbox-search-icon:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -119,16 +119,18 @@ private:
 #ifdef MOZ_GONK_MEDIACODEC
   DECL_MEDIA_PREF("media.gonk.enabled",                       PDMGonkDecoderEnabled, bool, true);
 #endif
 #ifdef MOZ_WIDGET_ANDROID
   DECL_MEDIA_PREF("media.android-media-codec.enabled",        PDMAndroidMediaCodecEnabled, bool, false);
   DECL_MEDIA_PREF("media.android-media-codec.preferred",      PDMAndroidMediaCodecPreferred, bool, false);
   DECL_MEDIA_PREF("media.navigator.hardware.vp8_encode.acceleration_remote_enabled", RemoteMediaCodecVP8EncoderEnabled, bool, false);
 #endif
+  // WebRTC
+  DECL_MEDIA_PREF("media.navigator.mediadatadecoder_enabled", MediaDataDecoderEnabled, bool, false);
 #ifdef MOZ_FFMPEG
   DECL_MEDIA_PREF("media.ffmpeg.enabled",                     PDMFFmpegEnabled, bool, true);
   DECL_MEDIA_PREF("media.libavcodec.allow-obsolete",          LibavcodecAllowObsolete, bool, false);
 #endif
 #if defined(MOZ_FFMPEG) || defined(MOZ_FFVPX)
   DECL_MEDIA_PREF("media.ffmpeg.low-latency.enabled",         PDMFFmpegLowLatencyEnabled, bool, false);
 #endif
 #ifdef MOZ_FFVPX
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -1005,17 +1005,16 @@ skip-if = toolkit == 'android' # bug 129
 [test_resolution_change.html]
 skip-if = toolkit == 'android' # android(bug 1232305)
 tags=capturestream
 [test_resume.html]
 skip-if = true # bug 1021673
 [test_seek_negative.html]
 skip-if = toolkit == 'android' # bug 1295443, bug 1306787, android(bug 1232305)
 [test_seek_nosrc.html]
-skip-if = toolkit == 'android' # android(bug 1232305)
 [test_seek_out_of_range.html]
 skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
 [test_seek_promise_bug1344357.html]
 skip-if = toolkit == 'android' # bug 1299382, android(bug 1232305)
 [test_seek-1.html]
 skip-if = toolkit == 'android' # bug 1322806, android(bug 1232305)
 [test_seek-2.html]
 skip-if = toolkit == 'android' # bug 1309778, android(bug 1232305)
new file mode 100644
--- /dev/null
+++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe-ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<img style="position:absolute; left:0; top:0"
+src="data:image/png;base64,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"
+>
new file mode 100644
--- /dev/null
+++ b/dom/media/test/reftest/bipbop_300_215kbps.mp4.lastframe.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+function doTest() {
+  var video = document.getElementById("v1");
+  video.src = "../bipbop_300_215kbps.mp4";
+  video.play();
+  video.addEventListener("ended", function() {
+    document.documentElement.removeAttribute('class');
+  }, {once: true});
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</head>
+<body>
+<video id="v1" style="position:absolute; left:0; top:0"></video>
+</body>
+</html>
--- a/dom/media/test/reftest/generateREF.html
+++ b/dom/media/test/reftest/generateREF.html
@@ -1,55 +1,55 @@
 <!DOCTYPE HTML>
 <html>
 <head>
 <script type="application/javascript">
 </script>
 </head>
 <body>
+<p id="out"></p>
 <video id="v1" style="position:absolute; left:0; top:0"></video>
 <canvas id="canvas"></canvas>
 <script type="application/javascript">
 /* READ ME first.
 The script is trying to make a reftest sample for reftest.
 HOW TO USE:
 1. Choose the first or last frame you want to generate. And set
-window.onload function to dumpFirstFrameToConsole/dumpLastFrameToConsole.
-2. Set the video.src in dumpFirstFrameToConsole/dumpLastFrameToConsole.
+window.onload function to dumpFirstFrame/dumpLastFrame.
+2. Set the video.src in dumpFirstFrame/dumpLastFrame.
 3. Run the script on browser.
-4. Open console (ctrl+shift+k) to get the first/last frame.
-5. Copy the url to your xxx-ref.html(short.mp4.firstframe-ref.html).
+4. Copy the base64 image url to your xxx-ref.html(short.mp4.firstframe-ref.html).
 You might hit security error if the video.src cross origin.
 Enable "media.seekToNextFrame.enabled" for the seekToNextFrame function
 or using nightly, the seekToNextFrame() ensure the ended event fired.
 */
 
-//window.onload = function() { setTimeout(dumpFirstFrameToConsole, 0); };
-window.onload = function() { setTimeout(dumpLastFrameToConsole, 0); };
+//window.onload = function() { setTimeout(dumpFirstFrame, 0); };
+window.onload = function() { setTimeout(dumpLastFrame, 0); };
 
-function drawVideoToConsole(v) {
+function drawVideoToInnerHTML(v) {
   var canvas = document.getElementById("canvas");
   canvas.width = v.videoWidth;
   canvas.height = v.videoHeight;
   var ctx = canvas.getContext("2d");
   ctx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight);
   var dataURL = canvas.toDataURL();
-  console.log(dataURL);
+  document.getElementById("out").innerHTML=dataURL;
 }
 
-function dumpFirstFrameToConsole() {
+function dumpFirstFrame() {
   var video = document.getElementById("v1");
   video.src = "short.mp4";
   video.preload = "metadata";
   video.addEventListener("loadeddata", function() {
-    drawVideoToConsole(video);
+    drawVideoToInnerHTML(video);
   });
 }
 
-function dumpLastFrameToConsole() {
+function dumpLastFrame() {
   var video = document.getElementById("v1");
   video.src = "short.mp4";
   video.preload = "metadata";
   video.seenEnded = false;
   // Seek to the end
   video.addEventListener("loadeddata", function() {
     video.currentTime = video.duration;
     video.onseeked = () => {
@@ -67,14 +67,14 @@ function dumpLastFrameToConsole() {
       () => {
         // Reach the end, do nothing.
       }
     );
   }
 
   video.addEventListener("ended", function() {
     video.seenEnded = true;
-    drawVideoToConsole(video);
+    drawVideoToInnerHTML(video);
   });
 }
 </script>
 </body>
 </html>
--- a/dom/media/test/reftest/reftest.list
+++ b/dom/media/test/reftest/reftest.list
@@ -1,2 +1,3 @@
 skip-if(Android) fuzzy-if(OSX,22,49977) skip-if(winWidget) fuzzy-if(webrender,70,600) HTTP(..) == short.mp4.firstframe.html short.mp4.firstframe-ref.html
 skip-if(Android) fuzzy-if(OSX,23,51392) fuzzy-if(winWidget,59,76797) fuzzy-if(webrender,60,1800) HTTP(..) == short.mp4.lastframe.html short.mp4.lastframe-ref.html
+skip-if(Android) skip-if(winWidget) fuzzy-if(webrender,55,4281) fuzzy-if(OSX,3,111852) HTTP(..) == bipbop_300_215kbps.mp4.lastframe.html bipbop_300_215kbps.mp4.lastframe-ref.html
\ No newline at end of file
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -47,24 +47,23 @@ to mochitest command.
 * Unsupported values
   * SVG-in-OpenType values not supported servo/servo#15211 bug 1355412
     * test_value_storage.html `context-` [7]
     * test_bug798843_pref.html [3]
 * Incorrect parsing
   * different parsing bug 1364260
     * test_supports_rules.html [6]
     * test_condition_text.html [1]
-  * test_value_storage.html `calc(25% - 10%)`: calc percent in -webkit-gradient bug 1380918 [5]
 * Incorrect serialization
   * place-{content,items,self} shorthands bug 1363971
     * test_align_shorthand_serialization.html [6]
   * system font serialization with subprop specified bug 1364286
     * test_system_font_serialization.html [3]
   * radial gradients are not serialized using modern unprefixed style bug 1380259
-    * test_computed_style.html `gradient` [2]
+    * test_computed_style.html `gradient` [1]
 * Unit should be preserved after parsing servo/servo#15346
   * test_units_time.html [1]
 * getComputedStyle style doesn't contain custom properties bug 1336891
   * test_variables.html `custom property name` [2]
 * test_css_supports.html: issues around @supports syntax servo/servo#15482 [2]
 * test_author_specified_style.html: support serializing color as author specified bug 1348165 [27]
 * browser_newtab_share_rule_processors.js: agent style sheet sharing [1]
 * :visited support (bug 1328509)
--- a/media/libstagefright/binding/MP4Metadata.cpp
+++ b/media/libstagefright/binding/MP4Metadata.cpp
@@ -763,17 +763,17 @@ MP4MetadataRust::Init()
   if (MOZ_LOG_TEST(sLog, LogLevel::Debug)) {
     mp4parse_log(true);
   }
 
   mp4parse_status rv = mp4parse_read(mRustParser.get());
   MOZ_LOG(sLog, LogLevel::Debug, ("rust parser returned %d\n", rv));
   Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_SUCCESS,
                         rv == mp4parse_status_OK);
-  if (rv != mp4parse_status_OK) {
+  if (rv != mp4parse_status_OK && rv != mp4parse_status_TABLE_TOO_LARGE) {
     MOZ_LOG(sLog, LogLevel::Info, ("Rust mp4 parser fails to parse this stream."));
     MOZ_ASSERT(rv > 0);
     Telemetry::Accumulate(Telemetry::MEDIA_RUST_MP4PARSE_ERROR_CODE, rv);
     return false;
   }
 
   UpdateCrypto();
 
--- a/media/libstagefright/binding/include/mp4parse.h
+++ b/media/libstagefright/binding/include/mp4parse.h
@@ -18,16 +18,17 @@ extern "C" {
 
 typedef enum mp4parse_status {
 	mp4parse_status_OK = 0,
 	mp4parse_status_BAD_ARG = 1,
 	mp4parse_status_INVALID = 2,
 	mp4parse_status_UNSUPPORTED = 3,
 	mp4parse_status_EOF = 4,
 	mp4parse_status_IO = 5,
+	mp4parse_status_TABLE_TOO_LARGE = 6,
 } mp4parse_status;
 
 typedef enum mp4parse_track_type {
 	mp4parse_track_type_VIDEO = 0,
 	mp4parse_track_type_AUDIO = 1,
 } mp4parse_track_type;
 
 typedef enum mp4parse_codec {
--- a/media/libstagefright/binding/mp4parse-cargo.patch
+++ b/media/libstagefright/binding/mp4parse-cargo.patch
@@ -1,55 +1,55 @@
 diff --git a/media/libstagefright/binding/mp4parse/Cargo.toml b/media/libstagefright/binding/mp4parse/Cargo.toml
 index ff9422c..814c4c6 100644
 --- a/media/libstagefright/binding/mp4parse/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse/Cargo.toml
-@@ -18,19 +18,13 @@ exclude = [
+@@ -20,19 +20,11 @@ exclude = [
  ]
  
+-[badges]
+-travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
+ 
  [dependencies]
 -byteorder = "1.0.0"
 -afl = { version = "0.1.1", optional = true }
 -afl-plugin = { version = "0.1.1", optional = true }
 -abort_on_panic = { version = "1.0.0", optional = true }
 -bitreader = { version = "0.3.0" }
 -num-traits = "0.1.37"
 +byteorder = "1.0.0"
 +bitreader = { version = "0.3.0" }
 +num-traits = "0.1.37"
-
+ 
  [dev-dependencies]
  test-assembler = "0.1.2"
  
 -[features]
 -fuzz = ["afl", "afl-plugin", "abort_on_panic"]
 -
  # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
  [profile.release]
  debug-assertions = true
 diff --git a/media/libstagefright/binding/mp4parse_capi/Cargo.toml b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 index a30e045..a965f06 100644
 --- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
 +++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
-@@ -18,22 +18,13 @@ exclude = [
+@@ -18,19 +18,10 @@ exclude = [
    "*.mp4",
  ]
- 
+
 -build = "build.rs"
 -
 -[badges]
 -travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 +build = false
- 
+
  [dependencies]
  byteorder = "1.0.0"
  mp4parse = {version = "0.8.0", path = "../mp4parse"}
  num-traits = "0.1.37"
- 
+
 -[build-dependencies]
 -moz-cheddar = "0.4.0"
 -
 -[features]
 -fuzz = ["mp4parse/fuzz"]
 -
- # Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
- [profile.release]
- debug-assertions = true
--- a/media/libstagefright/binding/mp4parse/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse/Cargo.toml
@@ -3,33 +3,28 @@ name = "mp4parse"
 version = "0.8.0"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
-documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
+documentation = "https://docs.rs/mp4parse/"
 license = "MPL-2.0"
 categories = ["multimedia::video"]
 
 repository = "https://github.com/mozilla/mp4parse-rust"
 
 # Avoid complaints about trying to package test files.
 exclude = [
   "*.mp4",
 ]
 
-[badges]
-travis-ci = { repository = "https://github.com/mozilla/mp4parse-rust" }
 
 [dependencies]
 byteorder = "1.0.0"
 bitreader = { version = "0.3.0" }
 num-traits = "0.1.37"
 
 [dev-dependencies]
 test-assembler = "0.1.2"
 
-# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
-[profile.release]
-debug-assertions = true
--- a/media/libstagefright/binding/mp4parse/src/boxes.rs
+++ b/media/libstagefright/binding/mp4parse/src/boxes.rs
@@ -132,14 +132,9 @@ box_database!(
     QTWaveAtom                        0x77617665, // "wave" - quicktime atom
     ProtectionSystemSpecificHeaderBox 0x70737368, // "pssh"
     SchemeInformationBox              0x73636869, // "schi"
     TrackEncryptionBox                0x74656e63, // "tenc"
     ProtectionSchemeInformationBox    0x73696e66, // "sinf"
     OriginalFormatBox                 0x66726d61, // "frma"
     MP3AudioSampleEntry               0x2e6d7033, // ".mp3" - from F4V.
     CompositionOffsetBox              0x63747473, // "ctts"
-    JPEGAtom                          0x6a706567, // "jpeg" - QT JPEG
-    AC3SampleEntry                    0x61632d33, // "ac-3"
-    EC3SampleEntry                    0x65632d33, // "ec-3"
-    AC3SpecificBox                    0x64616333, // "dac3"
-    EC3SpecificBox                    0x64656333, // "dec3"
 );
--- a/media/libstagefright/binding/mp4parse/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse/src/lib.rs
@@ -62,16 +62,18 @@ pub enum Error {
     /// Parse error caused by limited parser support rather than invalid data.
     Unsupported(&'static str),
     /// Reflect `std::io::ErrorKind::UnexpectedEof` for short data.
     UnexpectedEOF,
     /// Propagate underlying errors from `std::io`.
     Io(std::io::Error),
     /// read_mp4 terminated without detecting a moov box.
     NoMoov,
+    /// Parse error caused by table size is over limitation.
+    TableTooLarge,
 }
 
 impl From<bitreader::BitReaderError> for Error {
     fn from(_: bitreader::BitReaderError) -> Error {
         Error::InvalidData("invalid data")
     }
 }
 
@@ -262,18 +264,16 @@ pub struct ES_Descriptor {
 }
 
 #[allow(non_camel_case_types)]
 #[derive(Debug, Clone)]
 pub enum AudioCodecSpecific {
     ES_Descriptor(ES_Descriptor),
     FLACSpecificBox(FLACSpecificBox),
     OpusSpecificBox(OpusSpecificBox),
-    AC3SpecificBox,
-    EC3SpecificBox,
     MP3,
 }
 
 #[derive(Debug, Clone)]
 pub struct AudioSampleEntry {
     data_reference_index: u16,
     pub channelcount: u16,
     pub samplesize: u16,
@@ -282,17 +282,16 @@ pub struct AudioSampleEntry {
     pub protection_info: Vec<ProtectionSchemeInfoBox>,
 }
 
 #[derive(Debug, Clone)]
 pub enum VideoCodecSpecific {
     AVCConfig(Vec<u8>),
     VPxConfig(VPxConfigBox),
     ESDSConfig(Vec<u8>),
-    JPEG,
 }
 
 #[derive(Debug, Clone)]
 pub struct VideoSampleEntry {
     data_reference_index: u16,
     pub width: u16,
     pub height: u16,
     pub codec_specific: VideoCodecSpecific,
@@ -411,19 +410,16 @@ pub enum CodecType {
     Opus,
     H264,   // 14496-10
     MP4V,   // 14496-2
     VP10,
     VP9,
     VP8,
     EncryptedVideo,
     EncryptedAudio,
-    JPEG,   // QT JPEG atom
-    AC3,    // Digital Audio Compression (AC-3, Enhanced AC-3) Standard, ETSI TS 102 366.
-    EC3,    // Digital Audio Compression (AC-3, Enhanced AC-3) Standard, ETSI TS 102 366.
 }
 
 impl Default for CodecType {
     fn default() -> Self { CodecType::Unknown }
 }
 
 /// The media's global (mvhd) timescale in units per second.
 #[derive(Debug, Copy, Clone, PartialEq)]
@@ -1157,17 +1153,17 @@ fn read_stss<T: Read>(src: &mut BMFFBox<
 
 /// Parse a stsc box.
 fn read_stsc<T: Read>(src: &mut BMFFBox<T>) -> Result<SampleToChunkBox> {
     let (_, _) = read_fullbox_extra(src)?;
     let sample_count = be_u32_with_limit(src)?;
     let mut samples = Vec::new();
     for _ in 0..sample_count {
         let first_chunk = be_u32(src)?;
-        let samples_per_chunk = be_u32(src)?;
+        let samples_per_chunk = be_u32_with_limit(src)?;
         let sample_description_index = be_u32(src)?;
         samples.push(SampleToChunk {
             first_chunk: first_chunk,
             samples_per_chunk: samples_per_chunk,
             sample_description_index: sample_description_index,
         });
     }
 
@@ -1190,17 +1186,17 @@ fn read_ctts<T: Read>(src: &mut BMFFBox<
 
     let mut offsets = Vec::new();
     for _ in 0..counts {
         let (sample_count, time_offset) = match version {
             // According to spec, Version0 shoule be used when version == 0;
             // however, some buggy contents have negative value when version == 0.
             // So we always use Version1 here.
             0...1 => {
-                let count = be_u32(src)?;
+                let count = be_u32_with_limit(src)?;
                 let offset = TimeOffsetVersion::Version1(be_i32(src)?);
                 (count, offset)
             },
             _ => {
                 return Err(Error::InvalidData("unsupported version in 'ctts' box"));
             }
         };
         offsets.push(TimeOffset {
@@ -1238,17 +1234,17 @@ fn read_stsz<T: Read>(src: &mut BMFFBox<
 }
 
 /// Parse a stts box.
 fn read_stts<T: Read>(src: &mut BMFFBox<T>) -> Result<TimeToSampleBox> {
     let (_, _) = read_fullbox_extra(src)?;
     let sample_count = be_u32_with_limit(src)?;
     let mut samples = Vec::new();
     for _ in 0..sample_count {
-        let sample_count = be_u32(src)?;
+        let sample_count = be_u32_with_limit(src)?;
         let sample_delta = be_u32(src)?;
         samples.push(Sample {
             sample_count: sample_count,
             sample_delta: sample_delta,
         });
     }
 
     // Padding could be added in some contents.
@@ -1669,17 +1665,16 @@ fn read_hdlr<T: Read>(src: &mut BMFFBox<
 fn read_video_sample_entry<T: Read>(src: &mut BMFFBox<T>) -> Result<(CodecType, SampleEntry)> {
     let name = src.get_header().name;
     let codec_type = match name {
         BoxType::AVCSampleEntry | BoxType::AVC3SampleEntry => CodecType::H264,
         BoxType::MP4VideoSampleEntry => CodecType::MP4V,
         BoxType::VP8SampleEntry => CodecType::VP8,
         BoxType::VP9SampleEntry => CodecType::VP9,
         BoxType::ProtectedVisualSampleEntry => CodecType::EncryptedVideo,
-        BoxType::JPEGAtom => CodecType::JPEG,
         _ => {
             log!("Unsupported video codec, box {:?} found", name);
             CodecType::Unknown
         }
     };
 
     // Skip uninteresting fields.
     skip(src, 6)?;
@@ -1691,21 +1686,17 @@ fn read_video_sample_entry<T: Read>(src:
 
     let width = be_u16(src)?;
     let height = be_u16(src)?;
 
     // Skip uninteresting fields.
     skip(src, 50)?;
 
     // Skip clap/pasp/etc. for now.
-    let mut codec_specific = if name == BoxType::JPEGAtom {
-        Some(VideoCodecSpecific::JPEG)
-    } else {
-        None
-    };
+    let mut codec_specific = None;
     let mut protection_info = Vec::new();
     let mut iter = src.box_iter();
     while let Some(mut b) = iter.next_box()? {
         match b.head.name {
             BoxType::AVCConfigurationBox => {
                 if (name != BoxType::AVCSampleEntry &&
                     name != BoxType::AVC3SampleEntry &&
                     name != BoxType::ProtectedVisualSampleEntry) ||
@@ -1732,21 +1723,16 @@ fn read_video_sample_entry<T: Read>(src:
                 if name != BoxType::MP4VideoSampleEntry || codec_specific.is_some() {
                     return Err(Error::InvalidData("malformed video sample entry"));
                 }
                 let (_, _) = read_fullbox_extra(&mut b.content)?;
                 let esds_size = b.head.size - b.head.offset - 4;
                 let esds = read_buf(&mut b.content, esds_size as usize)?;
                 codec_specific = Some(VideoCodecSpecific::ESDSConfig(esds));
             }
-            BoxType::JPEGAtom => {
-                if name != BoxType::JPEGAtom || codec_specific.is_some() {
-                    return Err(Error::InvalidData("malformed video sample entry"));
-                }
-            }
             BoxType::ProtectionSchemeInformationBox => {
                 if name != BoxType::ProtectedVisualSampleEntry {
                     return Err(Error::InvalidData("malformed video sample entry"));
                 }
                 let sinf = read_sinf(&mut b)?;
                 log!("{:?} (sinf)", sinf);
                 protection_info.push(sinf);
             }
@@ -1870,36 +1856,16 @@ fn read_audio_sample_entry<T: Read>(src:
                 if name != BoxType::ProtectedAudioSampleEntry {
                     return Err(Error::InvalidData("malformed audio sample entry"));
                 }
                 let sinf = read_sinf(&mut b)?;
                 log!("{:?} (sinf)", sinf);
                 codec_type = CodecType::EncryptedAudio;
                 protection_info.push(sinf);
             }
-            BoxType::AC3SpecificBox => {
-                if name != BoxType::AC3SampleEntry {
-                    return Err(Error::InvalidData("malformed AC3 sample entry"));
-                }
-                // TODO: AC3SpecificBox needs to be parsed for detail information.
-                skip_box_remain(&mut b)?;
-                log!("(ac3)");
-                codec_type = CodecType::AC3;
-                codec_specific = Some(AudioCodecSpecific::AC3SpecificBox);
-            }
-            BoxType::EC3SpecificBox => {
-                if name != BoxType::EC3SpecificBox {
-                    return Err(Error::InvalidData("malformed EC3 sample entry"));
-                }
-                // TODO: EC3SpecificBox needs to be parsed for detail information.
-                skip_box_remain(&mut b)?;
-                log!("(ec3)");
-                codec_type = CodecType::EC3;
-                codec_specific = Some(AudioCodecSpecific::EC3SpecificBox);
-            }
             _ => {
                 log!("Unsupported audio codec, box {:?} found", b.head.name);
                 skip_box_content(&mut b)?;
             }
         }
         check_parser_state!(b.content);
     }
 
@@ -2079,17 +2045,17 @@ fn be_u24<T: ReadBytesExt>(src: &mut T) 
 fn be_u32<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
     src.read_u32::<byteorder::BigEndian>().map_err(From::from)
 }
 
 /// Using in reading table size and return error if it exceeds limitation.
 fn be_u32_with_limit<T: ReadBytesExt>(src: &mut T) -> Result<u32> {
     be_u32(src).and_then(|v| {
         if v > TABLE_SIZE_LIMIT {
-            return Err(Error::Unsupported("Over limited value"));
+            return Err(Error::TableTooLarge);
         }
         Ok(v)
     })
 }
 
 fn be_u64<T: ReadBytesExt>(src: &mut T) -> Result<u64> {
     src.read_u64::<byteorder::BigEndian>().map_err(From::from)
 }
--- a/media/libstagefright/binding/mp4parse/src/tests.rs
+++ b/media/libstagefright/binding/mp4parse/src/tests.rs
@@ -896,41 +896,16 @@ fn read_esds() {
     assert_eq!(es.audio_object_type, Some(2));
     assert_eq!(es.audio_sample_rate, Some(24000));
     assert_eq!(es.audio_channel_count, Some(6));
     assert_eq!(es.codec_esds, aac_esds);
     assert_eq!(es.decoder_specific_data, aac_dc_descriptor);
 }
 
 #[test]
-fn read_ac3_sample_entry() {
-    let ac3 =
-        vec![
-            0x00, 0x00, 0x00, 0x0b, 0x64, 0x61, 0x63, 0x33, 0x10, 0x11, 0x60
-        ];
-
-    let mut stream = make_box(BoxSize::Auto, b"ac-3", |s| {
-        s.append_repeated(0, 6)
-         .B16(1)    // data_reference_count
-         .B16(0)
-         .append_repeated(0, 6)
-         .B16(2)
-         .B16(16)
-         .append_repeated(0, 4)
-         .B32(48000 << 16)
-         .append_bytes(ac3.as_slice())
-    });
-
-    let mut iter = super::BoxIter::new(&mut stream);
-    let mut stream = iter.next_box().unwrap().unwrap();
-    let (codec_type, _) = super::read_audio_sample_entry(&mut stream)
-          .expect("fail to read ac3 atom");
-    assert_eq!(codec_type, super::CodecType::AC3);
-}
-#[test]
 fn read_stsd_mp4v() {
     let mp4v =
         vec![
                                                 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
             0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xd0, 0x01, 0xe0, 0x00, 0x48,
             0x00, 0x00, 0x00, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
             0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
@@ -1026,58 +1001,23 @@ fn max_table_limit() {
     }).into_inner();
     let mut stream = make_box(BoxSize::Auto, b"edts", |s| {
         s.append_bytes(elst.as_slice())
     });
     let mut iter = super::BoxIter::new(&mut stream);
     let mut stream = iter.next_box().unwrap().unwrap();
     let mut track = super::Track::new(0);
     match super::read_edts(&mut stream, &mut track) {
-        Err(Error::Unsupported(s)) => assert_eq!(s, "Over limited value"),
+        Err(Error::TableTooLarge) => (),
         Ok(_) => panic!("expected an error result"),
         _ => panic!("expected a different error result"),
     }
 }
 
 #[test]
-fn jpeg_video_sample_entry() {
-    let jpeg = make_box(BoxSize::Auto, b"jpeg", |s| {
-        s.append_repeated(0, 6)
-         .B16(1)
-         .append_repeated(0, 16)
-         .B16(1024)
-         .B16(1024)
-         .append_repeated(0, 14)
-         .append_repeated(0, 32)
-         .append_repeated(0, 4)
-    }).into_inner();
-    let mut stream = make_fullbox(BoxSize::Auto, b"stsd", 0, |s| {
-        s.B32(1)
-         .append_bytes(jpeg.as_slice())
-    });
-
-    let mut iter = super::BoxIter::new(&mut stream);
-    let mut stream = iter.next_box().unwrap().unwrap();
-    let mut track = super::Track::new(0);
-    match super::read_stsd(&mut stream, &mut track) {
-        Ok(sample_description) => {
-            match sample_description.descriptions[0] {
-                super::SampleEntry::Video(ref jpeg) => {
-                    assert_eq!(track.codec_type, super::CodecType::JPEG);
-                    assert_eq!(jpeg.height, 1024);
-                    assert_eq!(jpeg.width, 1024);
-                } ,
-                _ => {},
-            }
-        },
-        _ => panic!("failed to parse a jpeg atom"),
-    }
-}
-
-#[test]
 fn unknown_video_sample_entry() {
     let unknown_codec = make_box(BoxSize::Auto, b"yyyy", |s| {
         s.append_repeated(0, 16)
     }).into_inner();
     let mut stream = make_box(BoxSize::Auto, b"xxxx", |s| {
         s.append_repeated(0, 6)
          .B16(1)
          .append_repeated(0, 16)
--- a/media/libstagefright/binding/mp4parse/tests/public.rs
+++ b/media/libstagefright/binding/mp4parse/tests/public.rs
@@ -55,19 +55,16 @@ fn public_api() {
                         assert!(vpx.chroma_subsampling > 0);
                         assert!(!vpx.codec_init.is_empty());
                         "VPx"
                     }
                     mp4::VideoCodecSpecific::ESDSConfig(mp4v) => {
                         assert!(!mp4v.is_empty());
                         "MP4V"
                     }
-                    mp4::VideoCodecSpecific::JPEG => {
-                        "JPEG"
-                    }
                 }, "AVC");
             }
             Some(mp4::SampleEntry::Audio(a)) => {
                 // track part
                 assert_eq!(track.duration, Some(mp4::TrackScaledTime(2944, 1)));
                 assert_eq!(track.empty_duration, Some(mp4::MediaScaledTime(0)));
                 assert_eq!(track.media_time, Some(mp4::TrackScaledTime(1024, 1)));
                 assert_eq!(track.timescale, Some(mp4::TrackTimeScale(48000, 1)));
@@ -97,22 +94,16 @@ fn public_api() {
                     mp4::AudioCodecSpecific::OpusSpecificBox(opus) => {
                         // We don't enter in here, we just check if fields are public.
                         assert!(opus.version > 0);
                         "Opus"
                     }
                     mp4::AudioCodecSpecific::MP3 => {
                         "MP3"
                     }
-                    mp4::AudioCodecSpecific::AC3SpecificBox => {
-                        "AC3"
-                    }
-                    mp4::AudioCodecSpecific::EC3SpecificBox => {
-                        "EC3"
-                    }
                 }, "ES");
                 assert!(a.samplesize > 0);
                 assert!(a.samplerate > 0);
             }
             Some(mp4::SampleEntry::Unknown) | None => {}
         }
     }
 }
--- a/media/libstagefright/binding/mp4parse_capi/Cargo.toml
+++ b/media/libstagefright/binding/mp4parse_capi/Cargo.toml
@@ -3,28 +3,25 @@ name = "mp4parse_capi"
 version = "0.8.0"
 authors = [
   "Ralph Giles <giles@mozilla.com>",
   "Matthew Gregan <kinetik@flim.org>",
   "Alfredo Yang <ayang@mozilla.com>",
 ]
 
 description = "Parser for ISO base media file format (mp4)"
-documentation = "https://mp4parse-docs.surge.sh/mp4parse/"
+documentation = "https://docs.rs/mp4parse_capi/"
 license = "MPL-2.0"
 
 repository = "https://github.com/mozilla/mp4parse-rust"
 
 # Avoid complaints about trying to package test files.
 exclude = [
   "*.mp4",
 ]
 
 build = false
 
 [dependencies]
 byteorder = "1.0.0"
 mp4parse = {version = "0.8.0", path = "../mp4parse"}
 num-traits = "0.1.37"
 
-# Somewhat heavy-handed, but we want at least -Z force-overflow-checks=on.
-[profile.release]
-debug-assertions = true
--- a/media/libstagefright/binding/mp4parse_capi/src/lib.rs
+++ b/media/libstagefright/binding/mp4parse_capi/src/lib.rs
@@ -64,16 +64,17 @@ use mp4parse::Track;
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_status {
     OK = 0,
     BAD_ARG = 1,
     INVALID = 2,
     UNSUPPORTED = 3,
     EOF = 4,
     IO = 5,
+    TABLE_TOO_LARGE = 6,
 }
 
 #[allow(non_camel_case_types)]
 #[repr(C)]
 #[derive(PartialEq, Debug)]
 pub enum mp4parse_track_type {
     VIDEO = 0,
     AUDIO = 1,
@@ -330,17 +331,18 @@ pub unsafe extern fn mp4parse_read(parse
         Err(Error::UnexpectedEOF) => mp4parse_status::EOF,
         Err(Error::Io(_)) => {
             // Block further calls after a read failure.
             // Getting std::io::ErrorKind::UnexpectedEof is normal
             // but our From trait implementation should have converted
             // those to our Error::UnexpectedEOF variant.
             (*parser).set_poisoned(true);
             mp4parse_status::IO
-        }
+        },
+        Err(Error::TableTooLarge) => mp4parse_status::TABLE_TOO_LARGE,
     }
 }
 
 /// Return the number of tracks parsed by previous `mp4parse_read()` call.
 #[no_mangle]
 pub unsafe extern fn mp4parse_get_track_count(parser: *const mp4parse_parser, count: *mut u32) -> mp4parse_status {
     // Validate arguments from C.
     if parser.is_null() || count.is_null() || (*parser).poisoned() {
@@ -412,44 +414,39 @@ pub unsafe extern fn mp4parse_get_track_
     }
 
     info.track_type = match context.tracks[track_index].track_type {
         TrackType::Video => mp4parse_track_type::VIDEO,
         TrackType::Audio => mp4parse_track_type::AUDIO,
         TrackType::Unknown => return mp4parse_status::UNSUPPORTED,
     };
 
+    // Return UNKNOWN for unsupported format.
     info.codec = match context.tracks[track_index].data {
         Some(SampleEntry::Audio(ref audio)) => match audio.codec_specific {
             AudioCodecSpecific::OpusSpecificBox(_) =>
                 mp4parse_codec::OPUS,
             AudioCodecSpecific::FLACSpecificBox(_) =>
                 mp4parse_codec::FLAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::AAC =>
                 mp4parse_codec::AAC,
             AudioCodecSpecific::ES_Descriptor(ref esds) if esds.audio_codec == CodecType::MP3 =>
                 mp4parse_codec::MP3,
             AudioCodecSpecific::ES_Descriptor(_) =>
                 mp4parse_codec::UNKNOWN,
             AudioCodecSpecific::MP3 =>
                 mp4parse_codec::MP3,
-            AudioCodecSpecific::AC3SpecificBox =>
-                mp4parse_codec::AC3,
-            AudioCodecSpecific::EC3SpecificBox =>
-                mp4parse_codec::EC3,
         },
         Some(SampleEntry::Video(ref video)) => match video.codec_specific {
             VideoCodecSpecific::VPxConfig(_) =>
                 mp4parse_codec::VP9,
             VideoCodecSpecific::AVCConfig(_) =>
                 mp4parse_codec::AVC,
-            VideoCodecSpecific::ESDSConfig(_) =>
-                mp4parse_codec::MP4V,
-            VideoCodecSpecific::JPEG =>
-                mp4parse_codec::JPEG,
+            VideoCodecSpecific::ESDSConfig(_) => // MP4V (14496-2) video is unsupported.
+                mp4parse_codec::UNKNOWN,
         },
         _ => mp4parse_codec::UNKNOWN,
     };
 
     let track = &context.tracks[track_index];
 
     if let (Some(track_timescale),
             Some(context_timescale)) = (track.timescale,
@@ -568,18 +565,16 @@ pub unsafe extern fn mp4parse_get_track_
                             return mp4parse_status::INVALID;
                         }
                         (*info).extra_data.length = v.len() as u32;
                         (*info).extra_data.data = v.as_ptr();
                     }
                 }
             }
         }
-        AudioCodecSpecific::AC3SpecificBox => (),
-        AudioCodecSpecific::EC3SpecificBox => (),
         AudioCodecSpecific::MP3 => (),
     }
 
     if let Some(p) = audio.protection_info.iter().find(|sinf| sinf.tenc.is_some()) {
         if let Some(ref tenc) = p.tenc {
             (*info).protected_data.is_encrypted = tenc.is_encrypted;
             (*info).protected_data.iv_size = tenc.iv_size;
             (*info).protected_data.kid.set_data(&(tenc.kid));
--- a/media/libstagefright/binding/update-rust.sh
+++ b/media/libstagefright/binding/update-rust.sh
@@ -1,13 +1,13 @@
 #!/bin/sh -e
 # Script to update mp4parse-rust sources to latest upstream
 
 # Default version.
-VER=ce7e2e66613009d56aea2588c4d11a23d4dd8056
+VER=33e7ea77aaf06939b4aaf284f487cdd13d3db671
 
 # Accept version or commit from the command line.
 if test -n "$1"; then
   VER=$1
 fi
 
 echo "Fetching sources..."
 rm -rf _upstream
--- a/media/libstagefright/gtest/TestParser.cpp
+++ b/media/libstagefright/gtest/TestParser.cpp
@@ -216,17 +216,17 @@ static const TestFileData testFiles[] = 
 };
 
 static const TestFileData rustTestFiles[] = {
   // filename                      #V dur   w    h  #A dur  crypt        off   moof  headr  audio_profile
   { "test_case_1156505.mp4",        0, -1,   0,   0, 0, -1, false, 152, false, false, 0 },
   { "test_case_1181213.mp4",        1, 416666,
                                            320, 240, 1, 477460,
                                                              true,   0, false, false, 2 },
-  { "test_case_1181215.mp4",        1, 4966666,   190,   240, 0, -1, false,   0, false, false, 0 },
+  { "test_case_1181215.mp4",        0, -1,   0,   0, 0, -1, false,   0, false, false, 0 },
   { "test_case_1181220.mp4",        0, -1,   0,   0, 0, -1, false,   0, false, false, 0 },
   { "test_case_1181223.mp4",        0, 416666,
                                            320, 240, 0, -1, false,   0, false, false, 0 },
   { "test_case_1181719.mp4",        0, -1,   0,   0, 0, -1, false,   0, false, false, 0 },
   { "test_case_1185230.mp4",        2, 416666,
                                            320, 240, 2,  5, false,   0, false, false, 2 },
   { "test_case_1187067.mp4",        1, 80000,
                                            160,  90, 0, -1, false,   0, false, false, 0 },
@@ -301,73 +301,70 @@ TEST(stagefright_MPEG4Metadata, test_cas
         << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
       EXPECT_EQ(None, metadata.GetNumberTracks(TrackInfo::kTextTrack).Ref())
         << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
       EXPECT_EQ(None, metadata.GetNumberTracks(static_cast<TrackInfo::TrackType>(-1)).Ref())
         << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
       EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kUndefinedTrack, 0).Ref());
       MP4Metadata::ResultAndTrackInfo trackInfo =
         metadata.GetTrackInfo(TrackInfo::kVideoTrack, 0);
-      if (tests[test].mNumberVideoTracks == 0 ||
-          tests[test].mNumberVideoTracks == E) {
-        EXPECT_TRUE(!trackInfo.Ref());
-      } else {
+      if (!!tests[test].mNumberVideoTracks) {
         ASSERT_TRUE(!!trackInfo.Ref());
         const VideoInfo* videoInfo = trackInfo.Ref()->GetAsVideoInfo();
         ASSERT_TRUE(!!videoInfo);
-        EXPECT_TRUE(videoInfo->IsValid());
-        EXPECT_TRUE(videoInfo->IsVideo());
+        EXPECT_TRUE(videoInfo->IsValid()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+        EXPECT_TRUE(videoInfo->IsVideo()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         EXPECT_EQ(tests[test].mVideoDuration,
-                  videoInfo->mDuration.ToMicroseconds());
-        EXPECT_EQ(tests[test].mWidth, videoInfo->mDisplay.width);
-        EXPECT_EQ(tests[test].mHeight, videoInfo->mDisplay.height);
+                  videoInfo->mDuration.ToMicroseconds()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+        EXPECT_EQ(tests[test].mWidth, videoInfo->mDisplay.width) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+        EXPECT_EQ(tests[test].mHeight, videoInfo->mDisplay.height) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
 
         MP4Metadata::ResultAndIndice indices =
           metadata.GetTrackIndice(videoInfo->mTrackId);
-        EXPECT_TRUE(!!indices.Ref());
+        EXPECT_TRUE(!!indices.Ref()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         for (size_t i = 0; i < indices.Ref()->Length(); i++) {
           Index::Indice data;
-          EXPECT_TRUE(indices.Ref()->GetIndice(i, data));
-          EXPECT_TRUE(data.start_offset <= data.end_offset);
-          EXPECT_TRUE(data.start_composition <= data.end_composition);
+          EXPECT_TRUE(indices.Ref()->GetIndice(i, data)) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+          EXPECT_TRUE(data.start_offset <= data.end_offset) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+          EXPECT_TRUE(data.start_composition <= data.end_composition) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         }
       }
       trackInfo = metadata.GetTrackInfo(TrackInfo::kAudioTrack, 0);
       if (tests[test].mNumberAudioTracks == 0 ||
           tests[test].mNumberAudioTracks == E) {
-        EXPECT_TRUE(!trackInfo.Ref());
+        EXPECT_TRUE(!trackInfo.Ref()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
       } else {
         ASSERT_TRUE(!!trackInfo.Ref());
         const AudioInfo* audioInfo = trackInfo.Ref()->GetAsAudioInfo();
         ASSERT_TRUE(!!audioInfo);
-        EXPECT_TRUE(audioInfo->IsValid());
-        EXPECT_TRUE(audioInfo->IsAudio());
+        EXPECT_TRUE(audioInfo->IsValid()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+        EXPECT_TRUE(audioInfo->IsAudio()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         EXPECT_EQ(tests[test].mAudioDuration,
-                  audioInfo->mDuration.ToMicroseconds());
-        EXPECT_EQ(tests[test].mAudioProfile, audioInfo->mProfile);
+                  audioInfo->mDuration.ToMicroseconds()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+        EXPECT_EQ(tests[test].mAudioProfile, audioInfo->mProfile) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         if (tests[test].mAudioDuration !=
             audioInfo->mDuration.ToMicroseconds()) {
           MOZ_RELEASE_ASSERT(false);
         }
 
         MP4Metadata::ResultAndIndice indices =
           metadata.GetTrackIndice(audioInfo->mTrackId);
-        EXPECT_TRUE(!!indices.Ref());
+        EXPECT_TRUE(!!indices.Ref()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         for (size_t i = 0; i < indices.Ref()->Length(); i++) {
           Index::Indice data;
-          EXPECT_TRUE(indices.Ref()->GetIndice(i, data));
-          EXPECT_TRUE(data.start_offset <= data.end_offset);
-          EXPECT_TRUE(int64_t(data.start_composition) <= int64_t(data.end_composition));
+          EXPECT_TRUE(indices.Ref()->GetIndice(i, data)) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+          EXPECT_TRUE(data.start_offset <= data.end_offset) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+          EXPECT_TRUE(int64_t(data.start_composition) <= int64_t(data.end_composition)) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
         }
       }
-      EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref());
-      EXPECT_FALSE(metadata.GetTrackInfo(static_cast<TrackInfo::TrackType>(-1), 0).Ref());
+      EXPECT_FALSE(metadata.GetTrackInfo(TrackInfo::kTextTrack, 0).Ref()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+      EXPECT_FALSE(metadata.GetTrackInfo(static_cast<TrackInfo::TrackType>(-1), 0).Ref()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
       // We can see anywhere in any MPEG4.
-      EXPECT_TRUE(metadata.CanSeek());
-      EXPECT_EQ(tests[test].mHasCrypto, metadata.Crypto().Ref()->valid);
+      EXPECT_TRUE(metadata.CanSeek()) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
+      EXPECT_EQ(tests[test].mHasCrypto, metadata.Crypto().Ref()->valid) << (rust ? "rust/" : "stagefright/") << tests[test].mFilename;
     }
   }
 }
 
 // Bug 1224019: This test produces way to much output, disabling for now.
 #if 0
 TEST(stagefright_MPEG4Metadata, test_case_mp4_subsets)
 {
--- a/media/webrtc/signaling/signaling.gyp
+++ b/media/webrtc/signaling/signaling.gyp
@@ -69,16 +69,17 @@
         './src/common/time_profiling',
         './src/media',
         './src/media-conduit',
         './src/mediapipeline',
         './src/peerconnection',
         './src/sdp/sipcc',
         '../../../dom/base',
         '../../../dom/media',
+        '../../../dom/media/platforms',
         '../../../media/mtransport',
         '../trunk',
         '../../libyuv/libyuv/include',
         '../../mtransport/third_party/nrappkit/src/util/libekr',
       ],
 
       #
       # DEPENDENCIES
@@ -219,37 +220,16 @@
           'include_dirs!': [
             '../trunk/webrtc',
           ],
           'include_dirs': [
             '../../../netwerk/srtp/src/include',
             '../../../netwerk/srtp/src/crypto/include',
           ],
         }],
-        ['moz_webrtc_omx==1', {
-          'sources': [
-            './src/media-conduit/WebrtcOMXH264VideoCodec.cpp',
-            './src/media-conduit/OMXVideoCodec.cpp',
-          ],
-          'include_dirs': [
-            # hack on hack to re-add it after SrtpFlow removes it
-            '../../../dom/media/omx',
-            '../../../gfx/layers/client',
-          ],
-          'cflags_mozilla': [
-            '-I$(ANDROID_SOURCE)/frameworks/av/include/media/stagefright',
-            '-I$(ANDROID_SOURCE)/frameworks/av/include',
-            '-I$(ANDROID_SOURCE)/frameworks/native/include/media/openmax',
-            '-I$(ANDROID_SOURCE)/frameworks/native/include',
-            '-I$(ANDROID_SOURCE)/frameworks/native/opengl/include',
-          ],
-          'defines' : [
-            'MOZ_WEBRTC_OMX'
-          ],
-        }],
         ['moz_webrtc_mediacodec==1', {
           'include_dirs': [
             '../../../widget/android',
           ],
           'sources': [
             './src/media-conduit/MediaCodecVideoCodec.h',
             './src/media-conduit/WebrtcMediaCodecVP8VideoCodec.h',
             './src/media-conduit/MediaCodecVideoCodec.cpp',
@@ -274,17 +254,19 @@
           'defines' : [
             'NO_CHROMIUM_LOGGING',
             'USE_FAKE_PCOBSERVER',
           ],
         }],
         ['build_for_standalone==0', {
           'sources': [
             './src/media-conduit/GmpVideoCodec.cpp',
+            './src/media-conduit/MediaDataDecoderCodec.cpp',
             './src/media-conduit/WebrtcGmpVideoCodec.cpp',
+            './src/media-conduit/WebrtcMediaDataDecoderCodec.cpp',
           ],
         }],
         ['build_for_standalone!=0', {
           'include_dirs': [
             './test'
           ],
           'defines' : [
             'NO_CHROMIUM_LOGGING',
--- a/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
+++ b/media/webrtc/signaling/src/media-conduit/MediaConduitInterface.h
@@ -101,31 +101,16 @@ public:
    * @param len  : Length of the RTCP packet
    * @result     : NS_OK on success, NS_ERROR_FAILURE otherwise
    */
   virtual nsresult SendRtcpPacket(const uint8_t* data, size_t len) = 0;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TransportInterface)
 };
 
 /**
- * This class wraps image object for VideoRenderer::RenderVideoFrame()
- * callback implementation to use for rendering.
- */
-class ImageHandle
-{
-public:
-  explicit ImageHandle(layers::Image* image) : mImage(image) {}
-
-  const RefPtr<layers::Image>& GetImage() const { return mImage; }
-
-private:
-  RefPtr<layers::Image> mImage;
-};
-
-/**
  * 1. Abstract renderer for video data
  * 2. This class acts as abstract interface between the video-engine and
  *    video-engine agnostic renderer implementation.
  * 3. Concrete implementation of this interface is responsible for
  *    processing and/or rendering the obtained raw video frame to appropriate
  *    output , say, <video>
  */
 class VideoRenderer
@@ -140,45 +125,31 @@ public:
    * @param height: current height of the video @ decoder
    * @param number_of_streams: number of participating video streams
    */
   virtual void FrameSizeChange(unsigned int width,
                                unsigned int height,
                                unsigned int number_of_streams) = 0;
 
   /**
-   * Callback Function reporting decoded I420 frame for processing.
-   * @param buffer: pointer to decoded video frame
+   * Callback Function reporting decoded frame for processing.
+   * @param buffer: reference to decoded video frame
    * @param buffer_size: size of the decoded frame
    * @param time_stamp: Decoder timestamp, typically 90KHz as per RTP
    * @render_time: Wall-clock time at the decoder for synchronization
    *                purposes in milliseconds
-   * @handle: opaque handle for image object of decoded video frame.
    * NOTE: If decoded video frame is passed through buffer , it is the
    * responsibility of the concrete implementations of this class to own copy
    * of the frame if needed for time longer than scope of this callback.
    * Such implementations should be quick in processing the frames and return
    * immediately.
-   * On the other hand, if decoded video frame is passed through handle, the
-   * implementations should keep a reference to the (ref-counted) image object
-   * inside until it's no longer needed.
    */
   virtual void RenderVideoFrame(const webrtc::VideoFrameBuffer& buffer,
                                 uint32_t time_stamp,
-                                int64_t render_time,
-                                const ImageHandle& handle) = 0;
-  virtual void RenderVideoFrame(const uint8_t* buffer_y,
-                                uint32_t y_stride,
-                                const uint8_t* buffer_u,
-                                uint32_t u_stride,
-                                const uint8_t* buffer_v,
-                                uint32_t v_stride,
-                                uint32_t time_stamp,
-                                int64_t render_time,
-                                const ImageHandle& handle) = 0;
+                                int64_t render_time) = 0;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(VideoRenderer)
 };
 
 
 /**
  * Generic Interface for representing Audio/Video Session
  * MediaSession conduit is identified by 2 main components
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/MediaDataDecoderCodec.cpp
@@ -0,0 +1,37 @@
+/* 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 "MediaDataDecoderCodec.h"
+#include "MediaPrefs.h"
+#include "WebrtcMediaDataDecoderCodec.h"
+
+namespace mozilla {
+
+/* static */ WebrtcVideoEncoder*
+MediaDataDecoderCodec::CreateEncoder(
+  webrtc::VideoCodecType aCodecType)
+{
+  return nullptr;
+}
+
+/* static */ WebrtcVideoDecoder*
+MediaDataDecoderCodec::CreateDecoder(
+  webrtc::VideoCodecType aCodecType)
+{
+  if (!MediaPrefs::MediaDataDecoderEnabled()) {
+    return nullptr;
+  }
+
+  switch (aCodecType) {
+    case webrtc::VideoCodecType::kVideoCodecVP8:
+    case webrtc::VideoCodecType::kVideoCodecVP9:
+    case webrtc::VideoCodecType::kVideoCodecH264:
+      break;
+    default:
+      return nullptr;
+  }
+  return new WebrtcMediaDataDecoder();
+}
+
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/MediaDataDecoderCodec.h
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MEDIA_DATA_DECODER_CODEC_H_
+#define MEDIA_DATA_DECODER_CODEC_H_
+
+#include "MediaConduitInterface.h"
+#include "webrtc/common_types.h"
+
+namespace mozilla {
+
+class WebrtcVideoDecoder;
+class WebrtcVideoEncoder;
+class MediaDataDecoderCodec
+{
+ public:
+  /**
+   * Create encoder object for codec type |aCodecType|. Return |nullptr| when
+   * failed.
+   */
+  static WebrtcVideoEncoder* CreateEncoder(
+    webrtc::VideoCodecType aCodecType);
+
+  /**
+   * Create decoder object for codec type |aCodecType|. Return |nullptr| when
+   * failed.
+   */
+  static WebrtcVideoDecoder* CreateDecoder(
+    webrtc::VideoCodecType aCodecType);
+};
+}
+
+#endif // MEDIA_DATA_DECODER_CODEC_H_
deleted file mode 100644
--- a/media/webrtc/signaling/src/media-conduit/OMXVideoCodec.cpp
+++ /dev/null
@@ -1,30 +0,0 @@
-/* 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 "OMXVideoCodec.h"
-
-#ifdef WEBRTC_GONK
-#include "WebrtcOMXH264VideoCodec.h"
-#endif
-
-namespace mozilla {
-
-WebrtcVideoEncoder*
-OMXVideoCodec::CreateEncoder(CodecType aCodecType)
-{
-  if (aCodecType == CODEC_H264) {
-    return new WebrtcOMXH264VideoEncoder();
-  }
-  return nullptr;
-}
-
-WebrtcVideoDecoder*
-OMXVideoCodec::CreateDecoder(CodecType aCodecType) {
-  if (aCodecType == CODEC_H264) {
-    return new WebrtcOMXH264VideoDecoder();
-  }
-  return nullptr;
-}
-
-}
deleted file mode 100644
--- a/media/webrtc/signaling/src/media-conduit/OMXVideoCodec.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/* 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 OMX_VIDEO_CODEC_H_
-#define OMX_VIDEO_CODEC_H_
-
-#include "MediaConduitInterface.h"
-
-namespace mozilla {
-class OMXVideoCodec {
- public:
-  enum CodecType {
-    CODEC_H264,
-  };
-
-  /**
-   * Create encoder object for codec type |aCodecType|. Return |nullptr| when
-   * failed.
-   */
-  static WebrtcVideoEncoder* CreateEncoder(CodecType aCodecType);
-
-  /**
-   * Create decoder object for codec type |aCodecType|. Return |nullptr| when
-   * failed.
-   */
-  static WebrtcVideoDecoder* CreateDecoder(CodecType aCodecType);
-};
-
-}
-
-#endif // OMX_VIDEO_CODEC_H_
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -47,16 +47,18 @@
 #include "OMXVideoCodec.h"
 #endif
 
 #ifdef MOZ_WEBRTC_MEDIACODEC
 #include "MediaCodecVideoCodec.h"
 #endif
 #include "WebrtcGmpVideoCodec.h"
 
+#include "MediaDataDecoderCodec.h"
+
 // for ntohs
 #ifdef _MSC_VER
 #include "Winsock2.h"
 #else
 #include <netinet/in.h>
 #endif
 
 #include <algorithm>
@@ -1432,16 +1434,22 @@ WebrtcVideoConduit::ConfigureRecvMediaCo
 webrtc::VideoDecoder*
 WebrtcVideoConduit::CreateDecoder(webrtc::VideoCodecType aType)
 {
   webrtc::VideoDecoder* decoder = nullptr;
 #ifdef MOZ_WEBRTC_MEDIACODEC
   bool enabled = false;
 #endif
 
+  // Attempt to create a decoder using MediaDataDecoder.
+  decoder = MediaDataDecoderCodec::CreateDecoder(aType);
+  if (decoder) {
+    return decoder;
+  }
+
   switch (aType) {
     case webrtc::VideoCodecType::kVideoCodecH264:
       // get an external decoder
 #ifdef MOZ_WEBRTC_OMX
       decoder = OMXVideoCodec::CreateDecoder(OMXVideoCodec::CodecType::CODEC_H264);
 #else
       decoder = GmpVideoCodec::CreateDecoder();
 #endif
@@ -2305,21 +2313,19 @@ WebrtcVideoConduit::OnFrame(const webrtc
                                  const_cast<unsigned char*>(video_frame.video_frame_buffer()->DataY()),
                                  reinterpret_cast<unsigned char*>(&timestamp),
                                  sizeof(timestamp), 0, 0);
     if (ok) {
       VideoLatencyUpdate(now - timestamp);
     }
   }
 
-  const ImageHandle img_handle(nullptr);
   mRenderer->RenderVideoFrame(*video_frame.video_frame_buffer(),
                               video_frame.timestamp(),
-                              video_frame.render_time_ms(),
-                              img_handle);
+                              video_frame.render_time_ms());
 }
 
 // Compare lists of codecs
 bool
 WebrtcVideoConduit::CodecsDifferent(const nsTArray<UniquePtr<VideoCodecConfig>>& a,
                                     const nsTArray<UniquePtr<VideoCodecConfig>>& b)
 {
   // return a != b;
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcMediaDataDecoderCodec.cpp
@@ -0,0 +1,255 @@
+/* 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 "WebrtcMediaDataDecoderCodec.h"
+#include "ImageContainer.h"
+#include "Layers.h"
+#include "PDMFactory.h"
+#include "VideoUtils.h"
+#include "mozilla/layers/ImageBridgeChild.h"
+#include "webrtc/base/keep_ref_until_done.h"
+
+namespace mozilla {
+
+WebrtcMediaDataDecoder::WebrtcMediaDataDecoder()
+  : mTaskQueue(
+      new TaskQueue(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
+                    "WebrtcMediaDataDecoder::mTaskQueue"))
+  , mImageContainer(layers::LayerManager::CreateImageContainer(
+      layers::ImageContainer::ASYNCHRONOUS))
+  , mFactory(new PDMFactory())
+  , mMonitor("WebrtcMediaDataDecoder")
+{
+}
+
+WebrtcMediaDataDecoder::~WebrtcMediaDataDecoder()
+{
+  mTaskQueue->BeginShutdown();
+  mTaskQueue->AwaitShutdownAndIdle();
+}
+
+int32_t
+WebrtcMediaDataDecoder::InitDecode(const webrtc::VideoCodec* aCodecSettings,
+                                   int32_t aNumberOfCores)
+{
+  nsCString codec;
+  switch (aCodecSettings->codecType) {
+    case webrtc::VideoCodecType::kVideoCodecVP8:
+      codec = "video/webm; codecs=vp8";
+      break;
+    case webrtc::VideoCodecType::kVideoCodecVP9:
+      codec = "video/webm; codecs=vp9";
+      break;
+    case webrtc::VideoCodecType::kVideoCodecH264:
+      codec = "video/avc";
+      break;
+    default:
+      return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  mTrackType = TrackInfo::kVideoTrack;
+
+  mInfo = VideoInfo(aCodecSettings->width, aCodecSettings->height);
+  mInfo.mMimeType = codec;
+
+  RefPtr<layers::KnowsCompositor> knowsCompositor =
+    layers::ImageBridgeChild::GetSingleton();
+
+  mDecoder = mFactory->CreateDecoder(
+    { mInfo,
+      mTaskQueue,
+      CreateDecoderParams::OptionSet(CreateDecoderParams::Option::LowLatency),
+      mTrackType,
+      mImageContainer,
+      knowsCompositor });
+
+  if (!mDecoder) {
+    return WEBRTC_VIDEO_CODEC_ERROR;
+  }
+
+  MonitorAutoLock lock(mMonitor);
+  bool done = false;
+  mDecoder->Init()->Then(mTaskQueue,
+                         __func__,
+                         [&](TrackInfo::TrackType) {
+                           MonitorAutoLock lock(mMonitor);
+                           done = true;
+                           mMonitor.Notify();
+                         },
+                         [&](const MediaResult& aError) {
+                           MonitorAutoLock lock(mMonitor);
+                           done = true;
+                           mError = aError;
+                           mMonitor.Notify();
+                         });
+
+  while (!done) {
+    mMonitor.Wait();
+  }
+
+  return NS_SUCCEEDED(mError) ? WEBRTC_VIDEO_CODEC_OK : WEBRTC_VIDEO_CODEC_ERROR;
+}
+
+int32_t
+WebrtcMediaDataDecoder::Decode(
+  const webrtc::EncodedImage& aInputImage,
+  bool aMissingFrames,
+  const webrtc::RTPFragmentationHeader* aFragmentation,
+  const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
+  int64_t aRenderTimeMs)
+{
+  if (!mCallback || !mDecoder) {
+    return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
+  }
+
+  if (!aInputImage._buffer || !aInputImage._length) {
+    return WEBRTC_VIDEO_CODEC_ERR_PARAMETER;
+  }
+
+  // Always start with a complete key frame.
+  if (mNeedKeyframe) {
+    if (aInputImage._frameType != webrtc::FrameType::kVideoFrameKey)
+      return WEBRTC_VIDEO_CODEC_ERROR;
+    // We have a key frame - is it complete?
+    if (aInputImage._completeFrame) {
+      mNeedKeyframe = false;
+    } else {
+      return WEBRTC_VIDEO_CODEC_ERROR;
+    }
+  }
+
+  RefPtr<MediaRawData> compressedFrame =
+    new MediaRawData(aInputImage._buffer, aInputImage._length);
+  if (!compressedFrame->Data()) {
+    return WEBRTC_VIDEO_CODEC_MEMORY;
+  }
+
+  compressedFrame->mTime =
+    media::TimeUnit::FromMicroseconds(aInputImage._timeStamp);
+  compressedFrame->mTimecode =
+    media::TimeUnit::FromMicroseconds(aRenderTimeMs * 1000);
+  compressedFrame->mKeyframe =
+    aInputImage._frameType == webrtc::FrameType::kVideoFrameKey;
+  {
+    MonitorAutoLock lock(mMonitor);
+    bool done = false;
+    mDecoder->Decode(compressedFrame)->Then(
+      mTaskQueue,
+      __func__,
+      [&](const MediaDataDecoder::DecodedData& aResults) {
+        MonitorAutoLock lock(mMonitor);
+        mResults = aResults;
+        done = true;
+        mMonitor.Notify();
+      },
+      [&](const MediaResult& aError) {
+        MonitorAutoLock lock(mMonitor);
+        mError = aError;
+        done = true;
+        mMonitor.Notify();
+      });
+
+    while (!done) {
+      mMonitor.Wait();
+    }
+
+    for (auto& frame : mResults) {
+      MOZ_ASSERT(frame->mType == MediaData::VIDEO_DATA);
+      RefPtr<VideoData> video = frame->As<VideoData>();
+      MOZ_ASSERT(video);
+      if (!video->mImage) {
+        // Nothing to display.
+        continue;
+      }
+      rtc::scoped_refptr<ImageBuffer> image(
+        new rtc::RefCountedObject<ImageBuffer>(Move(video->mImage)));
+
+      webrtc::VideoFrame videoFrame(image,
+                                    frame->mTime.ToMicroseconds(),
+                                    frame->mDuration.ToMicroseconds() * 1000,
+                                    aInputImage.rotation_);
+      mCallback->Decoded(videoFrame);
+    }
+    mResults.Clear();
+  }
+  return NS_SUCCEEDED(mError) ? WEBRTC_VIDEO_CODEC_OK
+                              : WEBRTC_VIDEO_CODEC_ERROR;
+}
+
+int32_t
+WebrtcMediaDataDecoder::RegisterDecodeCompleteCallback(
+  webrtc::DecodedImageCallback* aCallback)
+{
+  mCallback = aCallback;
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+int32_t
+WebrtcMediaDataDecoder::Release()
+{
+  MonitorAutoLock lock(mMonitor);
+  bool done = false;
+  mDecoder->Flush()
+    ->Then(mTaskQueue,
+           __func__,
+           [this]() { return mDecoder->Shutdown(); },
+           [this](const MediaResult& aError) { return mDecoder->Shutdown(); })
+    ->Then(mTaskQueue,
+           __func__,
+           [&]() {
+             MonitorAutoLock lock(mMonitor);
+             done = true;
+             mMonitor.Notify();
+           },
+           []() { MOZ_ASSERT_UNREACHABLE("Shutdown promise always resolved"); });
+
+  while (!done) {
+    mMonitor.Wait();
+  }
+
+  mDecoder = nullptr;
+  mNeedKeyframe = true;
+
+  return WEBRTC_VIDEO_CODEC_OK;
+}
+
+bool
+WebrtcMediaDataDecoder::OnTaskQueue() const
+{
+  return OwnerThread()->IsCurrentThreadIn();
+}
+
+ImageBuffer::ImageBuffer(RefPtr<layers::Image>&& aImage)
+  : webrtc::NativeHandleBuffer(aImage,
+                               aImage->GetSize().width,
+                               aImage->GetSize().height)
+  , mImage(Move(aImage))
+{
+}
+
+rtc::scoped_refptr<webrtc::VideoFrameBuffer>
+ImageBuffer::NativeToI420Buffer()
+{
+  RefPtr<layers::PlanarYCbCrImage> image = mImage->AsPlanarYCbCrImage();
+  if (!image) {
+    // TODO. YUV420 ReadBack, Image only provides a RGB readback.
+    return nullptr;
+  }
+  rtc::scoped_refptr<layers::PlanarYCbCrImage> refImage(image);
+  const layers::PlanarYCbCrData* data = image->GetData();
+  rtc::scoped_refptr<webrtc::VideoFrameBuffer> buf(
+    new rtc::RefCountedObject<webrtc::WrappedI420Buffer>(
+      data->mPicSize.width,
+      data->mPicSize.height,
+      data->mYChannel,
+      data->mYStride,
+      data->mCbChannel,
+      data->mCbCrStride,
+      data->mCrChannel,
+      data->mCbCrStride,
+      rtc::KeepRefUntilDone(refImage)));
+  return buf;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/media/webrtc/signaling/src/media-conduit/WebrtcMediaDataDecoderCodec.h
@@ -0,0 +1,83 @@
+/* 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 WebrtcMediaDataDecoderCodec_h__
+#define WebrtcMediaDataDecoderCodec_h__
+
+#include "MediaConduitInterface.h"
+#include "MediaInfo.h"
+#include "MediaResult.h"
+#include "PlatformDecoderModule.h"
+#include "webrtc/common_video/include/video_frame_buffer.h"
+#include "webrtc/modules/video_coding/include/video_codec_interface.h"
+
+namespace webrtc {
+  class DecodedImageCallback;
+}
+namespace mozilla {
+namespace layers {
+  class Image;
+  class ImageContainer;
+}
+
+class PDMFactory;
+class TaskQueue;
+
+class ImageBuffer : public webrtc::NativeHandleBuffer
+{
+public:
+  explicit ImageBuffer(RefPtr<layers::Image>&& aImage);
+  rtc::scoped_refptr<VideoFrameBuffer> NativeToI420Buffer() override;
+
+private:
+  RefPtr<layers::Image> mImage;
+};
+
+class WebrtcMediaDataDecoder : public WebrtcVideoDecoder
+{
+public:
+  WebrtcMediaDataDecoder();
+
+  // Implement VideoDecoder interface.
+  uint64_t PluginID() const override { return 0; }
+
+  int32_t InitDecode(const webrtc::VideoCodec* codecSettings,
+                     int32_t numberOfCores) override;
+
+  int32_t Decode(const webrtc::EncodedImage& inputImage,
+                 bool missingFrames,
+                 const webrtc::RTPFragmentationHeader* fragmentation,
+                 const webrtc::CodecSpecificInfo* codecSpecificInfo = NULL,
+                 int64_t renderTimeMs = -1) override;
+
+  int32_t RegisterDecodeCompleteCallback(
+    webrtc::DecodedImageCallback* callback) override;
+
+  int32_t Release() override;
+
+private:
+  ~WebrtcMediaDataDecoder();
+  void QueueFrame(MediaRawData* aFrame);
+  AbstractThread* OwnerThread() const { return mTaskQueue; }
+  bool OnTaskQueue() const;
+
+  const RefPtr<TaskQueue> mTaskQueue;
+  const RefPtr<layers::ImageContainer> mImageContainer;
+  const RefPtr<PDMFactory> mFactory;
+  RefPtr<MediaDataDecoder> mDecoder;
+  webrtc::DecodedImageCallback* mCallback = nullptr;
+  VideoInfo mInfo;
+  TrackInfo::TrackType mTrackType;
+  bool mNeedKeyframe = true;
+  MozPromiseRequestHolder<MediaDataDecoder::DecodePromise> mDecodeRequest;
+
+  Monitor mMonitor;
+  // Members below are accessed via mMonitor
+  MediaResult mError = NS_OK;
+  MediaDataDecoder::DecodedData mResults;
+};
+
+} // namespace mozilla
+
+#endif // WebrtcMediaDataDecoderCodec_h__
deleted file mode 100644
--- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.cpp
+++ /dev/null
@@ -1,1253 +0,0 @@
-/* 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 "CSFLog.h"
-
-#include "WebrtcOMXH264VideoCodec.h"
-
-// Android/Stagefright
-#include <avc_utils.h>
-#include <binder/ProcessState.h>
-#include <foundation/ABuffer.h>
-#include <foundation/AMessage.h>
-#include <gui/Surface.h>
-#include <media/ICrypto.h>
-#include <media/stagefright/MediaCodec.h>
-#include <media/stagefright/MediaDefs.h>
-#include <media/stagefright/MediaErrors.h>
-#include <media/stagefright/MetaData.h>
-#include <OMX_Component.h>
-using namespace android;
-
-// WebRTC
-//#include "webrtc/common_video/interface/texture_video_frame.h"
-#include "webrtc/video_engine/include/vie_external_codec.h"
-#include "runnable_utils.h"
-
-// Gecko
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
-#include "GonkBufferQueueProducer.h"
-#endif
-#include "GonkNativeWindow.h"
-#include "GrallocImages.h"
-#include "mozilla/Atomics.h"
-#include "mozilla/Mutex.h"
-#include "nsThreadUtils.h"
-#include "OMXCodecWrapper.h"
-#include "TextureClient.h"
-#include "mozilla/IntegerPrintfMacros.h"
-
-#define DEQUEUE_BUFFER_TIMEOUT_US (100 * 1000ll) // 100ms.
-#define START_DEQUEUE_BUFFER_TIMEOUT_US (10 * DEQUEUE_BUFFER_TIMEOUT_US) // 1s.
-#define DRAIN_THREAD_TIMEOUT_US  (1000 * 1000ll) // 1s.
-
-#define WOHVC_LOG_TAG "WebrtcOMXH264VideoCodec"
-#define CODEC_LOGV(...) CSFLogInfo(WOHVC_LOG_TAG, __VA_ARGS__)
-#define CODEC_LOGD(...) CSFLogDebug(WOHVC_LOG_TAG, __VA_ARGS__)
-#define CODEC_LOGI(...) CSFLogInfo(WOHVC_LOG_TAG, __VA_ARGS__)
-#define CODEC_LOGW(...) CSFLogWarn(WOHVC_LOG_TAG, __VA_ARGS__)
-#define CODEC_LOGE(...) CSFLogError(WOHVC_LOG_TAG, __VA_ARGS__)
-
-namespace mozilla {
-
-static const uint8_t kNALStartCode[] = { 0x00, 0x00, 0x00, 0x01 };
-enum {
-  kNALTypeIDR = 5,
-  kNALTypeSPS = 7,
-  kNALTypePPS = 8,
-};
-
-// NS_INLINE_DECL_THREADSAFE_REFCOUNTING() cannot be used directly in
-// ImageNativeHandle below because the return type of webrtc::NativeHandle
-// AddRef()/Release() conflicts with those defined in macro. To avoid another
-// copy/paste of ref-counting implementation here, this dummy base class
-// is created to proivde another level of indirection.
-class DummyRefCountBase {
-public:
-  // Use the name of real class for logging.
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DummyRefCountBase)
-protected:
-  // To make sure subclass will be deleted/destructed properly.
-  virtual ~DummyRefCountBase() {}
-};
-
-// This function implements 2 interafces:
-// 1. webrtc::NativeHandle: to wrap layers::Image object so decoded frames can
-//    be passed through WebRTC rendering pipeline using TextureVideoFrame.
-// 2. ImageHandle: for renderer to get the image object inside without knowledge
-//    about webrtc::NativeHandle.
-class ImageNativeHandle final
-  : public webrtc::NativeHandle
-  , public DummyRefCountBase
-{
-public:
-  ImageNativeHandle(layers::Image* aImage)
-    : mImage(aImage)
-  {}
-
-  // Implement webrtc::NativeHandle.
-  virtual void* GetHandle() override { return mImage.get(); }
-
-  virtual int AddRef() override
-  {
-    return DummyRefCountBase::AddRef();
-  }
-
-  virtual int Release() override
-  {
-    return DummyRefCountBase::Release();
-  }
-
-private:
-  RefPtr<layers::Image> mImage;
-};
-
-struct EncodedFrame
-{
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mTimestamp;
-  int64_t mRenderTimeMs;
-};
-
-static void
-ShutdownThread(nsCOMPtr<nsIThread>& aThread)
-{
-  aThread->Shutdown();
-}
-
-// Base runnable class to repeatly pull OMX output buffers in seperate thread.
-// How to use:
-// - implementing DrainOutput() to get output. Remember to return false to tell
-//   drain not to pop input queue.
-// - call QueueInput() to schedule a run to drain output. The input, aFrame,
-//   should contains corresponding info such as image size and timestamps for
-//   DrainOutput() implementation to construct data needed by encoded/decoded
-//   callbacks.
-// TODO: Bug 997110 - Revisit queue/drain logic. Current design assumes that
-//       encoder only generate one output buffer per input frame and won't work
-//       if encoder drops frames or generates multiple output per input.
-class OMXOutputDrain : public Runnable
-{
-public:
-  void Start() {
-    CODEC_LOGD("OMXOutputDrain starting");
-    MonitorAutoLock lock(mMonitor);
-    if (mThread == nullptr) {
-      NS_NewNamedThread("OMXOutputDrain", getter_AddRefs(mThread));
-    }
-    CODEC_LOGD("OMXOutputDrain started");
-    mEnding = false;
-    mThread->Dispatch(this, NS_DISPATCH_NORMAL);
-  }
-
-  void Stop() {
-    CODEC_LOGD("OMXOutputDrain stopping");
-    MonitorAutoLock lock(mMonitor);
-    mEnding = true;
-    lock.NotifyAll(); // In case Run() is waiting.
-
-    if (mThread != nullptr) {
-      MonitorAutoUnlock unlock(mMonitor);
-      CODEC_LOGD("OMXOutputDrain thread shutdown");
-      NS_DispatchToMainThread(
-        WrapRunnableNM<decltype(&ShutdownThread),
-                       nsCOMPtr<nsIThread> >(&ShutdownThread, mThread));
-      mThread = nullptr;
-    }
-    CODEC_LOGD("OMXOutputDrain stopped");
-  }
-
-  void QueueInput(const EncodedFrame& aFrame)
-  {
-    MonitorAutoLock lock(mMonitor);
-
-    MOZ_ASSERT(mThread);
-
-    mInputFrames.push(aFrame);
-    // Notify Run() about queued input and it can start working.
-    lock.NotifyAll();
-  }
-
-  NS_IMETHOD Run() override
-  {
-    MonitorAutoLock lock(mMonitor);
-    if (mEnding) {
-      return NS_OK;
-    }
-    MOZ_ASSERT(mThread);
-
-    while (true) {
-      if (mInputFrames.empty()) {
-        // Wait for new input.
-        lock.Wait();
-      }
-
-      if (mEnding) {
-        CODEC_LOGD("OMXOutputDrain Run() ending");
-        // Stop draining.
-        break;
-      }
-
-      MOZ_ASSERT(!mInputFrames.empty());
-      {
-        // Release monitor while draining because it's blocking.
-        MonitorAutoUnlock unlock(mMonitor);
-        DrainOutput();
-      }
-    }
-
-    CODEC_LOGD("OMXOutputDrain Ended");
-    return NS_OK;
-  }
-
-protected:
-  OMXOutputDrain()
-    : mMonitor("OMXOutputDrain monitor")
-    , mEnding(false)
-  {}
-
-  // Drain output buffer for input frame queue mInputFrames.
-  // mInputFrames contains info such as size and time of the input frames.
-  // We have to give a queue to handle encoder frame skips - we can input 10
-  // frames and get one back.  NOTE: any access of aInputFrames MUST be preceded
-  // locking mMonitor!
-
-  // Blocks waiting for decoded buffers, but for a limited period because
-  // we need to check for shutdown.
-  virtual bool DrainOutput() = 0;
-
-protected:
-  // This monitor protects all things below it, and is also used to
-  // wait/notify queued input.
-  Monitor mMonitor;
-  std::queue<EncodedFrame> mInputFrames;
-
-private:
-  // also protected by mMonitor
-  nsCOMPtr<nsIThread> mThread;
-  bool mEnding;
-};
-
-// Assumption: SPS is first paramset or is not present
-static bool IsParamSets(uint8_t* aData, size_t aSize)
-{
-  MOZ_ASSERT(aData && aSize > sizeof(kNALStartCode));
-  return (aData[sizeof(kNALStartCode)] & 0x1f) == kNALTypeSPS;
-}
-
-// get the length of any pre-pended SPS/PPS's
-static size_t ParamSetLength(uint8_t* aData, size_t aSize)
-{
-  const uint8_t* data = aData;
-  size_t size = aSize;
-  const uint8_t* nalStart = nullptr;
-  size_t nalSize = 0;
-  while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
-    if ((*nalStart & 0x1f) != kNALTypeSPS &&
-        (*nalStart & 0x1f) != kNALTypePPS) {
-      MOZ_ASSERT(nalStart - sizeof(kNALStartCode) >= aData);
-      return (nalStart - sizeof(kNALStartCode)) - aData; // SPS/PPS/iframe
-    }
-  }
-  return aSize; // it's only SPS/PPS
-}
-
-// H.264 decoder using stagefright.
-// It implements gonk native window callback to receive buffers from
-// MediaCodec::RenderOutputBufferAndRelease().
-class WebrtcOMXDecoder final : public GonkNativeWindowNewFrameCallback
-{
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WebrtcOMXDecoder)
-
-private:
-  virtual ~WebrtcOMXDecoder()
-  {
-    CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p OMX destructor", this);
-    if (mStarted) {
-      Stop();
-    }
-    if (mCodec != nullptr) {
-      mCodec->release();
-      mCodec.clear();
-    }
-    mLooper.clear();
-  }
-
-public:
-  WebrtcOMXDecoder(const char* aMimeType,
-                   webrtc::DecodedImageCallback* aCallback)
-    : mWidth(0)
-    , mHeight(0)
-    , mStarted(false)
-    , mCallback(aCallback)
-    , mDecodedFrameLock("WebRTC decoded frame lock")
-    , mEnding(false)
-  {
-    // Create binder thread pool required by stagefright.
-    android::ProcessState::self()->startThreadPool();
-
-    mLooper = new ALooper;
-    mLooper->start();
-    CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p creating decoder", this);
-    mCodec = MediaCodec::CreateByType(mLooper, aMimeType, false /* encoder */);
-    CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p OMX created", this);
-  }
-
-  // Find SPS in input data and extract picture width and height if found.
-  static status_t ExtractPicDimensions(uint8_t* aData, size_t aSize,
-                                       int32_t* aWidth, int32_t* aHeight)
-  {
-    MOZ_ASSERT(aData && aSize > sizeof(kNALStartCode));
-    if ((aData[sizeof(kNALStartCode)] & 0x1f) != kNALTypeSPS) {
-      return ERROR_MALFORMED;
-    }
-    sp<ABuffer> sps = new ABuffer(&aData[sizeof(kNALStartCode)], aSize - sizeof(kNALStartCode));
-    FindAVCDimensions(sps, aWidth, aHeight);
-    return OK;
-  }
-
-  // Configure decoder using image width/height.
-  status_t ConfigureWithPicDimensions(int32_t aWidth, int32_t aHeight)
-  {
-    MOZ_ASSERT(mCodec != nullptr);
-    if (mCodec == nullptr) {
-      return INVALID_OPERATION;
-    }
-
-    CODEC_LOGD("OMX:%p decoder width:%d height:%d", this, aWidth, aHeight);
-
-    sp<AMessage> config = new AMessage();
-    config->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC);
-    config->setInt32("width", aWidth);
-    config->setInt32("height", aHeight);
-    mWidth = aWidth;
-    mHeight = aHeight;
-
-    sp<Surface> surface = nullptr;
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
-    sp<IGraphicBufferProducer> producer;
-    sp<IGonkGraphicBufferConsumer> consumer;
-    GonkBufferQueue::createBufferQueue(&producer, &consumer);
-    mNativeWindow = new GonkNativeWindow(consumer);
-#else
-    mNativeWindow = new GonkNativeWindow();
-#endif
-    if (mNativeWindow.get()) {
-      // listen to buffers queued by MediaCodec::RenderOutputBufferAndRelease().
-      mNativeWindow->setNewFrameCallback(this);
-      // XXX remove buffer changes after a better solution lands - bug 1009420
-#if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 21
-      static_cast<GonkBufferQueueProducer*>(producer.get())->setSynchronousMode(false);
-      // More spare buffers to avoid OMX decoder waiting for native window
-      consumer->setMaxAcquiredBufferCount(WEBRTC_OMX_H264_MIN_DECODE_BUFFERS);
-      surface = new Surface(producer);
-#else
-      sp<GonkBufferQueue> bq = mNativeWindow->getBufferQueue();
-      bq->setSynchronousMode(false);
-      // More spare buffers to avoid OMX decoder waiting for native window
-      bq->setMaxAcquiredBufferCount(WEBRTC_OMX_H264_MIN_DECODE_BUFFERS);
-      surface = new Surface(bq);
-#endif
-    }
-    status_t result = mCodec->configure(config, surface, nullptr, 0);
-    if (result == OK) {
-      CODEC_LOGD("OMX:%p decoder configured", this);
-      result = Start();
-    }
-    return result;
-  }
-
-  status_t
-  FillInput(const webrtc::EncodedImage& aEncoded, bool aIsFirstFrame,
-            int64_t& aRenderTimeMs)
-  {
-    MOZ_ASSERT(mCodec != nullptr && aEncoded._buffer && aEncoded._length > 0);
-    if (mCodec == nullptr || !aEncoded._buffer || aEncoded._length == 0) {
-      return INVALID_OPERATION;
-    }
-
-    // Break input encoded data into NALUs and send each one to decode.
-    // 8x10 decoder doesn't allow picture coding NALs to be in the same buffer
-    // with SPS/PPS (BUFFER_FLAG_CODECCONFIG) per QC
-    const uint8_t* data = aEncoded._buffer;
-    size_t size = aEncoded._length;
-    const uint8_t* nalStart = nullptr;
-    size_t nalSize = 0;
-    status_t err = OK;
-
-    // this returns a pointer to the NAL byte (after the StartCode)
-    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
-      // Individual NALU inherits metadata from input encoded data.
-      webrtc::EncodedImage nalu(aEncoded);
-
-      nalu._buffer = const_cast<uint8_t*>(nalStart) - sizeof(kNALStartCode);
-      MOZ_ASSERT(nalu._buffer >= aEncoded._buffer);
-      nalu._length = nalSize + sizeof(kNALStartCode);
-      MOZ_ASSERT(nalu._buffer + nalu._length <= aEncoded._buffer + aEncoded._length);
-
-      size_t index;
-      err = mCodec->dequeueInputBuffer(&index,
-                                       aIsFirstFrame ? START_DEQUEUE_BUFFER_TIMEOUT_US : DEQUEUE_BUFFER_TIMEOUT_US);
-      if (err != OK) {
-        if (err != -EAGAIN) {
-          CODEC_LOGE("decode dequeue input buffer error:%d", err);
-        } else {
-          CODEC_LOGE("decode dequeue 100ms without a buffer (EAGAIN)");
-        }
-        return err;
-      }
-
-      // Prepend start code to buffer.
-      MOZ_ASSERT(memcmp(nalu._buffer, kNALStartCode, sizeof(kNALStartCode)) == 0);
-      const sp<ABuffer>& omxIn = mInputBuffers.itemAt(index);
-      MOZ_ASSERT(omxIn->capacity() >= nalu._length);
-      omxIn->setRange(0, nalu._length);
-      // Copying is needed because MediaCodec API doesn't support externally
-      // allocated buffer as input.
-      uint8_t* dst = omxIn->data();
-      memcpy(dst, nalu._buffer, nalu._length);
-      int64_t inputTimeUs = (nalu._timeStamp * 1000ll) / 90; // 90kHz -> us.
-      // Assign input flags according to input buffer NALU and frame types.
-      uint32_t flags;
-      int nalType = dst[sizeof(kNALStartCode)] & 0x1f;
-      switch (nalType) {
-        case kNALTypeSPS:
-        case kNALTypePPS:
-          flags = MediaCodec::BUFFER_FLAG_CODECCONFIG;
-          break;
-        case kNALTypeIDR:
-          flags = MediaCodec::BUFFER_FLAG_SYNCFRAME;
-          break;
-        default:
-          flags = 0;
-          break;
-      }
-      CODEC_LOGD("Decoder input: %d bytes (NAL 0x%02x), time %lld (%u), flags 0x%x",
-                 nalu._length, dst[sizeof(kNALStartCode)], inputTimeUs, nalu._timeStamp, flags);
-      err = mCodec->queueInputBuffer(index, 0, nalu._length, inputTimeUs, flags);
-      if (err == OK && !(flags & MediaCodec::BUFFER_FLAG_CODECCONFIG)) {
-        if (mOutputDrain == nullptr) {
-          mOutputDrain = new OutputDrain(this);
-          mOutputDrain->Start();
-        }
-        EncodedFrame frame;
-        frame.mWidth = mWidth;
-        frame.mHeight = mHeight;
-        frame.mTimestamp = nalu._timeStamp;
-        frame.mRenderTimeMs = aRenderTimeMs;
-        mOutputDrain->QueueInput(frame);
-      }
-    }
-
-    return err;
-  }
-
-  status_t
-  DrainOutput(std::queue<EncodedFrame>& aInputFrames, Monitor& aMonitor)
-  {
-    MOZ_ASSERT(mCodec != nullptr);
-    if (mCodec == nullptr) {
-      return INVALID_OPERATION;
-    }
-
-    size_t index = 0;
-    size_t outOffset = 0;
-    size_t outSize = 0;
-    int64_t outTime = -1ll;
-    uint32_t outFlags = 0;
-    status_t err = mCodec->dequeueOutputBuffer(&index, &outOffset, &outSize,
-                                               &outTime, &outFlags,
-                                               DRAIN_THREAD_TIMEOUT_US);
-    switch (err) {
-      case OK:
-        break;
-      case -EAGAIN:
-        // Not an error: output not available yet. Try later.
-        CODEC_LOGI("decode dequeue OMX output buffer timed out. Try later.");
-        return err;
-      case INFO_FORMAT_CHANGED:
-        // Not an error: will get this value when OMX output buffer is enabled,
-        // or when input size changed.
-        CODEC_LOGD("decode dequeue OMX output buffer format change");
-        return err;
-      case INFO_OUTPUT_BUFFERS_CHANGED:
-        // Not an error: will get this value when OMX output buffer changed
-        // (probably because of input size change).
-        CODEC_LOGD("decode dequeue OMX output buffer change");
-        err = mCodec->getOutputBuffers(&mOutputBuffers);
-        MOZ_ASSERT(err == OK);
-        return INFO_OUTPUT_BUFFERS_CHANGED;
-      default:
-        CODEC_LOGE("decode dequeue OMX output buffer error:%d", err);
-        // Return OK to instruct OutputDrain to drop input from queue.
-        MonitorAutoLock lock(aMonitor);
-        aInputFrames.pop();
-        return OK;
-    }
-
-    CODEC_LOGD("Decoder output: %d bytes, offset %u, time %lld, flags 0x%x",
-               outSize, outOffset, outTime, outFlags);
-    if (mCallback) {
-      EncodedFrame frame;
-      {
-        MonitorAutoLock lock(aMonitor);
-        frame = aInputFrames.front();
-        aInputFrames.pop();
-      }
-      {
-        // Store info of this frame. OnNewFrame() will need the timestamp later.
-        MutexAutoLock lock(mDecodedFrameLock);
-        if (mEnding) {
-          mCodec->releaseOutputBuffer(index);
-          return err;
-        }
-        mDecodedFrames.push(frame);
-      }
-      // Ask codec to queue buffer back to native window. OnNewFrame() will be
-      // called.
-      mCodec->renderOutputBufferAndRelease(index);
-      // Once consumed, buffer will be queued back to GonkNativeWindow for codec
-      // to dequeue/use.
-    } else {
-      mCodec->releaseOutputBuffer(index);
-    }
-
-    return err;
-  }
-
-  // Will be called when MediaCodec::RenderOutputBufferAndRelease() returns
-  // buffers back to native window for rendering.
-  void OnNewFrame() override
-  {
-    RefPtr<layers::TextureClient> buffer = mNativeWindow->getCurrentBuffer();
-    if (!buffer) {
-      CODEC_LOGE("Decoder NewFrame: Get null buffer");
-      return;
-    }
-
-    gfx::IntSize picSize(buffer->GetSize());
-    nsAutoPtr<layers::GrallocImage> grallocImage(new layers::GrallocImage());
-    grallocImage->AdoptData(buffer, picSize);
-
-    // Get timestamp of the frame about to render.
-    int64_t timestamp = -1;
-    int64_t renderTimeMs = -1;
-    {
-      MutexAutoLock lock(mDecodedFrameLock);
-      if (mDecodedFrames.empty()) {
-        return;
-      }
-      EncodedFrame decoded = mDecodedFrames.front();
-      timestamp = decoded.mTimestamp;
-      renderTimeMs = decoded.mRenderTimeMs;
-      mDecodedFrames.pop();
-    }
-    MOZ_ASSERT(timestamp >= 0 && renderTimeMs >= 0);
-
-    CODEC_LOGD("Decoder NewFrame: %dx%d, timestamp %lld, renderTimeMs %lld",
-               picSize.width, picSize.height, timestamp, renderTimeMs);
-
-    nsAutoPtr<webrtc::VideoFrame> videoFrame(new webrtc::VideoFrame(
-      new ImageNativeHandle(grallocImage.forget()),
-      picSize.width,
-      picSize.height,
-      timestamp,
-      renderTimeMs));
-    if (videoFrame != nullptr) {
-      mCallback->Decoded(*videoFrame);
-    }
-  }
-
-private:
-  class OutputDrain : public OMXOutputDrain
-  {
-  public:
-    OutputDrain(WebrtcOMXDecoder* aOMX)
-      : OMXOutputDrain()
-      , mOMX(aOMX)
-    {}
-
-  protected:
-    virtual bool DrainOutput() override
-    {
-      return (mOMX->DrainOutput(mInputFrames, mMonitor) == OK);
-    }
-
-  private:
-    WebrtcOMXDecoder* mOMX;
-  };
-
-  status_t Start()
-  {
-    MOZ_ASSERT(!mStarted);
-    if (mStarted) {
-      return OK;
-    }
-
-    {
-      MutexAutoLock lock(mDecodedFrameLock);
-      mEnding = false;
-    }
-    status_t err = mCodec->start();
-    if (err == OK) {
-      mStarted = true;
-      mCodec->getInputBuffers(&mInputBuffers);
-      mCodec->getOutputBuffers(&mOutputBuffers);
-    }
-
-    return err;
-  }
-
-  status_t Stop()
-  {
-    MOZ_ASSERT(mStarted);
-    if (!mStarted) {
-      return OK;
-    }
-
-    CODEC_LOGD("OMXOutputDrain decoder stopping");
-    // Drop all 'pending to render' frames.
-    {
-      MutexAutoLock lock(mDecodedFrameLock);
-      mEnding = true;
-      while (!mDecodedFrames.empty()) {
-        mDecodedFrames.pop();
-      }
-    }
-
-    if (mOutputDrain != nullptr) {
-      CODEC_LOGD("decoder's OutputDrain stopping");
-      mOutputDrain->Stop();
-      mOutputDrain = nullptr;
-    }
-
-    status_t err = mCodec->stop();
-    if (err == OK) {
-      mInputBuffers.clear();
-      mOutputBuffers.clear();
-      mStarted = false;
-    } else {
-      MOZ_ASSERT(false);
-    }
-    CODEC_LOGD("OMXOutputDrain decoder stopped");
-    return err;
-  }
-
-  sp<ALooper> mLooper;
-  sp<MediaCodec> mCodec; // OMXCodec
-  int mWidth;
-  int mHeight;
-  android::Vector<sp<ABuffer> > mInputBuffers;
-  android::Vector<sp<ABuffer> > mOutputBuffers;
-  bool mStarted;
-
-  sp<GonkNativeWindow> mNativeWindow;
-
-  RefPtr<OutputDrain> mOutputDrain;
-  webrtc::DecodedImageCallback* mCallback;
-
-  Mutex mDecodedFrameLock; // To protect mDecodedFrames and mEnding
-  std::queue<EncodedFrame> mDecodedFrames;
-  bool mEnding;
-};
-
-class EncOutputDrain : public OMXOutputDrain
-{
-public:
-  EncOutputDrain(OMXVideoEncoder* aOMX, webrtc::EncodedImageCallback* aCallback)
-    : OMXOutputDrain()
-    , mOMX(aOMX)
-    , mCallback(aCallback)
-    , mIsPrevFrameParamSets(false)
-  {}
-
-protected:
-  virtual bool DrainOutput() override
-  {
-    nsTArray<uint8_t> output;
-    int64_t timeUs = -1ll;
-    int flags = 0;
-    nsresult rv = mOMX->GetNextEncodedFrame(&output, &timeUs, &flags,
-                                            DRAIN_THREAD_TIMEOUT_US);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      // Fail to get encoded frame. The corresponding input frame should be
-      // removed.
-      // We'll treat this like a skipped frame
-      return true;
-    }
-
-    if (output.Length() == 0) {
-      // No encoded data yet. Try later.
-      CODEC_LOGD("OMX: (encode no output available this time)");
-      return false;
-    }
-
-    // Conversion to us rounds down, so we need to round up for us->90KHz
-    uint32_t target_timestamp = (timeUs * 90ll + 999) / 1000; // us -> 90KHz
-    // 8x10 v2.0 encoder doesn't set this reliably:
-    //bool isParamSets = (flags & MediaCodec::BUFFER_FLAG_CODECCONFIG);
-    // Assume that SPS/PPS will be at the start of any buffer
-    // Assume PPS will not be in a separate buffer - SPS/PPS or SPS/PPS/iframe
-    bool isParamSets = IsParamSets(output.Elements(), output.Length());
-    bool isIFrame = (flags & MediaCodec::BUFFER_FLAG_SYNCFRAME);
-    CODEC_LOGD("OMX: encoded frame (%d): time %lld (%u), flags x%x",
-               output.Length(), timeUs, target_timestamp, flags);
-    // Should not be parameter sets and I-frame at the same time.
-    // Except that it is possible, apparently, after an encoder re-config (bug 1063883)
-    // MOZ_ASSERT(!(isParamSets && isIFrame));
-
-    if (mCallback) {
-      // Implementation here assumes encoder output to be a buffer containing
-      // parameter sets(SPS + PPS) followed by a series of buffers, each for
-      // one input frame.
-      // TODO: handle output violating this assumpton in bug 997110.
-      webrtc::EncodedImage encoded(output.Elements(), output.Length(),
-                                   output.Capacity());
-      encoded._frameType = (isParamSets || isIFrame) ?
-                           webrtc::kKeyFrame : webrtc::kDeltaFrame;
-      EncodedFrame input_frame;
-      {
-        MonitorAutoLock lock(mMonitor);
-        // will sps/pps have the same timestamp as their iframe? Initial one on 8x10 has
-        // 0 timestamp.
-        if (isParamSets) {
-          // Let's assume it was the first item in the queue, but leave it there since an
-          // IDR will follow
-          input_frame = mInputFrames.front();
-        } else {
-          do {
-            if (mInputFrames.empty()) {
-              // Let's assume it was the last item in the queue, but leave it there
-              mInputFrames.push(input_frame);
-              CODEC_LOGE("OMX: encoded timestamp %u which doesn't match input queue!! (head %u)",
-                         target_timestamp, input_frame.mTimestamp);
-              break;
-            }
-
-            input_frame = mInputFrames.front();
-            mInputFrames.pop();
-            if (input_frame.mTimestamp != target_timestamp) {
-              CODEC_LOGD("OMX: encoder skipped frame timestamp %u", input_frame.mTimestamp);
-            }
-          } while (input_frame.mTimestamp != target_timestamp);
-        }
-      }
-
-      encoded._encodedWidth = input_frame.mWidth;
-      encoded._encodedHeight = input_frame.mHeight;
-      encoded._timeStamp = input_frame.mTimestamp;
-      encoded.capture_time_ms_ = input_frame.mRenderTimeMs;
-      encoded._completeFrame = true;
-
-      CODEC_LOGD("Encoded frame: %d bytes, %dx%d, is_param %d, is_iframe %d, timestamp %u, captureTimeMs %" PRIu64,
-                 encoded._length, encoded._encodedWidth, encoded._encodedHeight,
-                 isParamSets, isIFrame, encoded._timeStamp, encoded.capture_time_ms_);
-      // Prepend SPS/PPS to I-frames unless they were sent last time.
-      SendEncodedDataToCallback(encoded, isIFrame && !mIsPrevFrameParamSets && !isParamSets);
-      // This will be true only for the frame following a paramset block!  So if we're
-      // working with a correct encoder that generates SPS/PPS then iframe always, we
-      // won't try to insert.  (also, don't set if we get SPS/PPS/iframe in one buffer)
-      mIsPrevFrameParamSets = isParamSets && !isIFrame;
-      if (isParamSets) {
-        // copy off the param sets for inserting later
-        mParamSets.Clear();
-        // since we may have SPS/PPS or SPS/PPS/iframe
-        size_t length = ParamSetLength(encoded._buffer, encoded._length);
-        MOZ_ASSERT(length > 0);
-        mParamSets.AppendElements(encoded._buffer, length);
-      }
-    }
-
-    return !isParamSets; // not really needed anymore
-  }
-
-private:
-  // Send encoded data to callback.The data will be broken into individual NALUs
-  // if necessary and sent to callback one by one. This function can also insert
-  // SPS/PPS NALUs in front of input data if requested.
-  void SendEncodedDataToCallback(webrtc::EncodedImage& aEncodedImage,
-                                 bool aPrependParamSets)
-  {
-    if (aPrependParamSets) {
-      webrtc::EncodedImage prepend(aEncodedImage);
-      // Insert current parameter sets in front of the input encoded data.
-      MOZ_ASSERT(mParamSets.Length() > sizeof(kNALStartCode)); // Start code + ...
-      prepend._length = mParamSets.Length();
-      prepend._buffer = mParamSets.Elements();
-      // Break into NALUs and send.
-      CODEC_LOGD("Prepending SPS/PPS: %d bytes, timestamp %u, captureTimeMs %" PRIu64,
-                 prepend._length, prepend._timeStamp, prepend.capture_time_ms_);
-      SendEncodedDataToCallback(prepend, false);
-    }
-
-    struct nal_entry {
-      uint32_t offset;
-      uint32_t size;
-    };
-    AutoTArray<nal_entry, 1> nals;
-
-    // Break input encoded data into NALUs and send each one to callback.
-    const uint8_t* data = aEncodedImage._buffer;
-    size_t size = aEncodedImage._length;
-    const uint8_t* nalStart = nullptr;
-    size_t nalSize = 0;
-    while (getNextNALUnit(&data, &size, &nalStart, &nalSize, true) == OK) {
-      // XXX optimize by making buffer an offset
-      nal_entry nal = {((uint32_t) (nalStart - aEncodedImage._buffer)), (uint32_t) nalSize};
-      nals.AppendElement(nal);
-    }
-
-    size_t num_nals = nals.Length();
-    if (num_nals > 0) {
-      webrtc::RTPFragmentationHeader fragmentation;
-      fragmentation.VerifyAndAllocateFragmentationHeader(num_nals);
-      for (size_t i = 0; i < num_nals; i++) {
-        fragmentation.fragmentationOffset[i] = nals[i].offset;
-        fragmentation.fragmentationLength[i] = nals[i].size;
-      }
-      webrtc::EncodedImage unit(aEncodedImage);
-      unit._completeFrame = true;
-
-      mCallback->Encoded(unit, nullptr, &fragmentation);
-    }
-  }
-
-  OMXVideoEncoder* mOMX;
-  webrtc::EncodedImageCallback* mCallback;
-  bool mIsPrevFrameParamSets;
-  nsTArray<uint8_t> mParamSets;
-};
-
-// Encoder.
-WebrtcOMXH264VideoEncoder::WebrtcOMXH264VideoEncoder()
-  : mOMX(nullptr)
-  , mCallback(nullptr)
-  , mWidth(0)
-  , mHeight(0)
-  , mFrameRate(0)
-  , mBitRateKbps(0)
-#ifdef OMX_IDR_NEEDED_FOR_BITRATE
-  , mBitRateAtLastIDR(0)
-#endif
-  , mOMXConfigured(false)
-  , mOMXReconfigure(false)
-{
-  mReservation = new OMXCodecReservation(true);
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p constructed", this);
-}
-
-int32_t
-WebrtcOMXH264VideoEncoder::InitEncode(const webrtc::VideoCodec* aCodecSettings,
-                                      int32_t aNumOfCores,
-                                      size_t aMaxPayloadSize)
-{
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p init", this);
-
-  if (mOMX == nullptr) {
-    nsAutoPtr<OMXVideoEncoder> omx(OMXCodecWrapper::CreateAVCEncoder());
-    if (NS_WARN_IF(omx == nullptr)) {
-      return WEBRTC_VIDEO_CODEC_ERROR;
-    }
-    mOMX = omx.forget();
-    CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p OMX created", this);
-  }
-
-  if (!mReservation->ReserveOMXCodec()) {
-    CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p Encoder in use", this);
-    mOMX = nullptr;
-    return WEBRTC_VIDEO_CODEC_ERROR;
-  }
-
-  // Defer configuration until 1st frame is received because this function will
-  // be called more than once, and unfortunately with incorrect setting values
-  // at first.
-  mWidth = aCodecSettings->width;
-  mHeight = aCodecSettings->height;
-  mFrameRate = aCodecSettings->maxFramerate;
-  mBitRateKbps = aCodecSettings->startBitrate;
-  // XXX handle maxpayloadsize (aka mode 0/1)
-
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p OMX Encoder reserved", this);
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-int32_t
-WebrtcOMXH264VideoEncoder::Encode(const webrtc::VideoFrame& aInputImage,
-                                  const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
-                                  const std::vector<webrtc::FrameType>* aFrameTypes)
-{
-  MOZ_ASSERT(mOMX != nullptr);
-  if (mOMX == nullptr) {
-    return WEBRTC_VIDEO_CODEC_ERROR;
-  }
-
-  // Have to reconfigure for resolution or framerate changes :-(
-  // ~220ms initial configure on 8x10, 50-100ms for re-configure it appears
-  // XXX drop frames while this is happening?
-  if (aInputImage.width() < 0 || (uint32_t)aInputImage.width() != mWidth ||
-      aInputImage.height() < 0 || (uint32_t)aInputImage.height() != mHeight) {
-    mWidth = aInputImage.width();
-    mHeight = aInputImage.height();
-    mOMXReconfigure = true;
-  }
-
-  if (!mOMXConfigured || mOMXReconfigure) {
-    if (mOMXConfigured) {
-      CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p reconfiguring encoder %dx%d @ %u fps",
-                 this, mWidth, mHeight, mFrameRate);
-      mOMXConfigured = false;
-    }
-    mOMXReconfigure = false;
-    // XXX This can take time.  Encode() likely assumes encodes are queued "quickly" and
-    // don't block the input too long.  Frames may build up.
-
-    // XXX take from negotiated SDP in codecSpecific data
-    OMX_VIDEO_AVCLEVELTYPE level = OMX_VIDEO_AVCLevel3;
-    // OMX_Video_ControlRateConstant is not supported on QC 8x10
-    OMX_VIDEO_CONTROLRATETYPE bitrateMode = OMX_Video_ControlRateConstantSkipFrames;
-
-    // Set up configuration parameters for AVC/H.264 encoder.
-    sp<AMessage> format = new AMessage;
-    // Fixed values
-    format->setString("mime", MEDIA_MIMETYPE_VIDEO_AVC);
-    // XXX We should only set to < infinity if we're not using any recovery RTCP options
-    // However, we MUST set it to a lower value because the 8x10 rate controller
-    // only changes rate at GOP boundaries.... but it also changes rate on requested GOPs
-
-    // Too long and we have very low bitrates for the first second or two... plus
-    // bug 1014921 means we have to force them every ~3 seconds or less.
-    format->setInt32("i-frame-interval", 4 /* seconds */);
-    // See mozilla::layers::GrallocImage, supports YUV 4:2:0, CbCr width and
-    // height is half that of Y
-    format->setInt32("color-format", OMX_COLOR_FormatYUV420SemiPlanar);
-    format->setInt32("profile", OMX_VIDEO_AVCProfileBaseline);
-    format->setInt32("level", level);
-    format->setInt32("bitrate-mode", bitrateMode);
-    format->setInt32("store-metadata-in-buffers", 0);
-    // XXX Unfortunately, 8x10 doesn't support this, but ask anyways
-    format->setInt32("prepend-sps-pps-to-idr-frames", 1);
-    // Input values.
-    format->setInt32("width", mWidth);
-    format->setInt32("height", mHeight);
-    format->setInt32("stride", mWidth);
-    format->setInt32("slice-height", mHeight);
-    format->setInt32("frame-rate", mFrameRate);
-    format->setInt32("bitrate", mBitRateKbps*1000);
-
-    CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p configuring encoder %dx%d @ %d fps, rate %d kbps",
-               this, mWidth, mHeight, mFrameRate, mBitRateKbps);
-    nsresult rv = mOMX->ConfigureDirect(format,
-                                        OMXVideoEncoder::BlobFormat::AVC_NAL);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      CODEC_LOGE("WebrtcOMXH264VideoEncoder:%p FAILED configuring encoder %d", this, int(rv));
-      return WEBRTC_VIDEO_CODEC_ERROR;
-    }
-    mOMXConfigured = true;
-#ifdef OMX_IDR_NEEDED_FOR_BITRATE
-    mLastIDRTime = TimeStamp::Now();
-    mBitRateAtLastIDR = mBitRateKbps;
-#endif
-  }
-
-  if (aFrameTypes && aFrameTypes->size() &&
-      ((*aFrameTypes)[0] == webrtc::kKeyFrame)) {
-    mOMX->RequestIDRFrame();
-#ifdef OMX_IDR_NEEDED_FOR_BITRATE
-    mLastIDRTime = TimeStamp::Now();
-    mBitRateAtLastIDR = mBitRateKbps;
-  } else if (mBitRateKbps != mBitRateAtLastIDR) {
-    // 8x10 OMX codec requires a keyframe to shift bitrates!
-    TimeStamp now = TimeStamp::Now();
-    if (mLastIDRTime.IsNull()) {
-      // paranoia
-      mLastIDRTime = now;
-    }
-    int32_t timeSinceLastIDR = (now - mLastIDRTime).ToMilliseconds();
-
-    // Balance asking for IDRs too often against direction and amount of bitrate change.
-
-    // HACK for bug 1014921: 8x10 has encode/decode mismatches that build up errors
-    // if you go too long without an IDR.  In normal use, bitrate will change often
-    // enough to never hit this time limit.
-    if ((timeSinceLastIDR > 3000) ||
-        (mBitRateKbps < (mBitRateAtLastIDR * 8)/10) ||
-        (timeSinceLastIDR < 300 && mBitRateKbps < (mBitRateAtLastIDR * 9)/10) ||
-        (timeSinceLastIDR < 1000 && mBitRateKbps < (mBitRateAtLastIDR * 97)/100) ||
-        (timeSinceLastIDR >= 1000 && mBitRateKbps < mBitRateAtLastIDR) ||
-        (mBitRateKbps > (mBitRateAtLastIDR * 15)/10) ||
-        (timeSinceLastIDR < 500 && mBitRateKbps > (mBitRateAtLastIDR * 13)/10) ||
-        (timeSinceLastIDR < 1000 && mBitRateKbps > (mBitRateAtLastIDR * 11)/10) ||
-        (timeSinceLastIDR >= 1000 && mBitRateKbps > mBitRateAtLastIDR)) {
-      CODEC_LOGD("Requesting IDR for bitrate change from %u to %u (time since last idr %dms)",
-                 mBitRateAtLastIDR, mBitRateKbps, timeSinceLastIDR);
-
-      mOMX->RequestIDRFrame();
-      mLastIDRTime = now;
-      mBitRateAtLastIDR = mBitRateKbps;
-    }
-#endif
-  }
-
-  // Wrap VideoFrame input with PlanarYCbCrImage for OMXVideoEncoder.
-  layers::PlanarYCbCrData yuvData;
-  yuvData.mYChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kYPlane));
-  yuvData.mYSize = gfx::IntSize(aInputImage.width(), aInputImage.height());
-  yuvData.mYStride = aInputImage.stride(webrtc::kYPlane);
-  MOZ_ASSERT(aInputImage.stride(webrtc::kUPlane) == aInputImage.stride(webrtc::kVPlane));
-  yuvData.mCbCrStride = aInputImage.stride(webrtc::kUPlane);
-  yuvData.mCbChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kUPlane));
-  yuvData.mCrChannel = const_cast<uint8_t*>(aInputImage.buffer(webrtc::kVPlane));
-  yuvData.mCbCrSize = gfx::IntSize((yuvData.mYSize.width + 1) / 2,
-                                   (yuvData.mYSize.height + 1) / 2);
-  yuvData.mPicSize = yuvData.mYSize;
-  yuvData.mStereoMode = StereoMode::MONO;
-  layers::RecyclingPlanarYCbCrImage img(nullptr);
-  // AdoptData() doesn't need AllocateAndGetNewBuffer(); OMXVideoEncoder is ok with this
-  img.AdoptData(yuvData);
-
-  CODEC_LOGD("Encode frame: %dx%d, timestamp %u (%lld), renderTimeMs %" PRIu64,
-             aInputImage.width(), aInputImage.height(),
-             aInputImage.timestamp(), aInputImage.timestamp() * 1000ll / 90,
-             aInputImage.render_time_ms());
-
-  nsresult rv = mOMX->Encode(&img,
-                             yuvData.mYSize.width,
-                             yuvData.mYSize.height,
-                             aInputImage.timestamp() * 1000ll / 90, // 90kHz -> us.
-                             0);
-  if (rv == NS_OK) {
-    if (mOutputDrain == nullptr) {
-      mOutputDrain = new EncOutputDrain(mOMX, mCallback);
-      mOutputDrain->Start();
-    }
-    EncodedFrame frame;
-    frame.mWidth = mWidth;
-    frame.mHeight = mHeight;
-    frame.mTimestamp = aInputImage.timestamp();
-    frame.mRenderTimeMs = aInputImage.render_time_ms();
-    mOutputDrain->QueueInput(frame);
-  }
-
-  return (rv == NS_OK) ? WEBRTC_VIDEO_CODEC_OK : WEBRTC_VIDEO_CODEC_ERROR;
-}
-
-int32_t
-WebrtcOMXH264VideoEncoder::RegisterEncodeCompleteCallback(
-    webrtc::EncodedImageCallback* aCallback)
-{
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p set callback:%p", this, aCallback);
-  MOZ_ASSERT(aCallback);
-  mCallback = aCallback;
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-int32_t
-WebrtcOMXH264VideoEncoder::Release()
-{
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p will be released", this);
-
-  if (mOutputDrain != nullptr) {
-    mOutputDrain->Stop();
-    mOutputDrain = nullptr;
-  }
-  mOMXConfigured = false;
-  bool hadOMX = !!mOMX;
-  mOMX = nullptr;
-  if (hadOMX) {
-    mReservation->ReleaseOMXCodec();
-  }
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p released", this);
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-WebrtcOMXH264VideoEncoder::~WebrtcOMXH264VideoEncoder()
-{
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p will be destructed", this);
-
-  Release();
-}
-
-// Inform the encoder of the new packet loss rate and the round-trip time of
-// the network. aPacketLossRate is fraction lost and can be 0~255
-// (255 means 100% lost).
-// Note: stagefright doesn't handle these parameters.
-int32_t
-WebrtcOMXH264VideoEncoder::SetChannelParameters(uint32_t aPacketLossRate,
-                                                int64_t aRoundTripTimeMs)
-{
-  CODEC_LOGD("WebrtcOMXH264VideoEncoder:%p set channel packet loss:%u, rtt:%" PRIi64,
-             this, aPacketLossRate, aRoundTripTimeMs);
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-// TODO: Bug 997567. Find the way to support frame rate change.
-int32_t
-WebrtcOMXH264VideoEncoder::SetRates(uint32_t aBitRateKbps, uint32_t aFrameRate)
-{
-  CODEC_LOGE("WebrtcOMXH264VideoEncoder:%p set bitrate:%u, frame rate:%u (%u))",
-             this, aBitRateKbps, aFrameRate, mFrameRate);
-  MOZ_ASSERT(mOMX != nullptr);
-  if (mOMX == nullptr) {
-    return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
-  }
-
-  // XXX Should use StageFright framerate change, perhaps only on major changes of framerate.
-
-  // Without Stagefright support, Algorithm should be:
-  // if (frameRate < 50% of configured) {
-  //   drop framerate to next step down that includes current framerate within 50%
-  // } else if (frameRate > configured) {
-  //   change config to next step up that includes current framerate
-  // }
-#if !defined(TEST_OMX_FRAMERATE_CHANGES)
-  if (aFrameRate > mFrameRate ||
-      aFrameRate < mFrameRate/2) {
-    uint32_t old_rate = mFrameRate;
-    if (aFrameRate >= 15) {
-      mFrameRate = 30;
-    } else if (aFrameRate >= 10) {
-      mFrameRate = 20;
-    } else if (aFrameRate >= 8) {
-      mFrameRate = 15;
-    } else /* if (aFrameRate >= 5)*/ {
-      // don't go lower; encoder may not be stable
-      mFrameRate = 10;
-    }
-    if (mFrameRate < aFrameRate) { // safety
-      mFrameRate = aFrameRate;
-    }
-    if (old_rate != mFrameRate) {
-      mOMXReconfigure = true;  // force re-configure on next frame
-    }
-  }
-#else
-  // XXX for testing, be wild!
-  if (aFrameRate != mFrameRate) {
-    mFrameRate = aFrameRate;
-    mOMXReconfigure = true;  // force re-configure on next frame
-  }
-#endif
-
-  // XXX Limit bitrate for 8x10 devices to a specific level depending on fps and resolution
-  // mBitRateKbps = LimitBitrate8x10(mWidth, mHeight, mFrameRate, aBitRateKbps);
-  // Rely on global single setting (~720 kbps for HVGA@30fps) for now
-  if (aBitRateKbps > 700) {
-    aBitRateKbps = 700;
-  }
-  mBitRateKbps = aBitRateKbps;
-  nsresult rv = mOMX->SetBitrate(mBitRateKbps);
-  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetBitrate failed");
-  return NS_FAILED(rv) ? WEBRTC_VIDEO_CODEC_OK : WEBRTC_VIDEO_CODEC_ERROR;
-}
-
-// Decoder.
-WebrtcOMXH264VideoDecoder::WebrtcOMXH264VideoDecoder()
-  : mCallback(nullptr)
-  , mOMX(nullptr)
-{
-  mReservation = new OMXCodecReservation(false);
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p will be constructed", this);
-}
-
-int32_t
-WebrtcOMXH264VideoDecoder::InitDecode(const webrtc::VideoCodec* aCodecSettings,
-                                      int32_t aNumOfCores)
-{
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p init OMX:%p", this, mOMX.get());
-
-  if (!mReservation->ReserveOMXCodec()) {
-    CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p Decoder in use", this);
-    return WEBRTC_VIDEO_CODEC_ERROR;
-  }
-
-  // Defer configuration until SPS/PPS NALUs (where actual decoder config
-  // values can be extracted) are received.
-
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p OMX Decoder reserved", this);
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-int32_t
-WebrtcOMXH264VideoDecoder::Decode(const webrtc::EncodedImage& aInputImage,
-                                  bool aMissingFrames,
-                                  const webrtc::RTPFragmentationHeader* aFragmentation,
-                                  const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
-                                  int64_t aRenderTimeMs)
-{
-  if (aInputImage._length== 0 || !aInputImage._buffer) {
-    return WEBRTC_VIDEO_CODEC_ERROR;
-  }
-
-  bool configured = !!mOMX;
-  if (!configured) {
-    // Search for SPS NALU in input to get width/height config.
-    int32_t width;
-    int32_t height;
-    status_t result = WebrtcOMXDecoder::ExtractPicDimensions(aInputImage._buffer,
-                                                             aInputImage._length,
-                                                             &width, &height);
-    if (result != OK) {
-      // Cannot config decoder because SPS haven't been seen.
-      CODEC_LOGI("WebrtcOMXH264VideoDecoder:%p missing SPS in input (nal 0x%02x, len %d)",
-                 this, aInputImage._buffer[sizeof(kNALStartCode)] & 0x1f, aInputImage._length);
-      return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
-    }
-    RefPtr<WebrtcOMXDecoder> omx = new WebrtcOMXDecoder(MEDIA_MIMETYPE_VIDEO_AVC,
-                                                        mCallback);
-    result = omx->ConfigureWithPicDimensions(width, height);
-    if (NS_WARN_IF(result != OK)) {
-      return WEBRTC_VIDEO_CODEC_UNINITIALIZED;
-    }
-    CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p start OMX", this);
-    mOMX = omx;
-  }
-
-  bool feedFrame = true;
-  while (feedFrame) {
-    status_t err = mOMX->FillInput(aInputImage, !configured, aRenderTimeMs);
-    feedFrame = (err == -EAGAIN); // No input buffer available. Try again.
-  }
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-int32_t
-WebrtcOMXH264VideoDecoder::RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* aCallback)
-{
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p set callback:%p", this, aCallback);
-  MOZ_ASSERT(aCallback);
-  mCallback = aCallback;
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-int32_t
-WebrtcOMXH264VideoDecoder::Release()
-{
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p will be released", this);
-
-  mOMX = nullptr; // calls Stop()
-  mReservation->ReleaseOMXCodec();
-
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-WebrtcOMXH264VideoDecoder::~WebrtcOMXH264VideoDecoder()
-{
-  CODEC_LOGD("WebrtcOMXH264VideoDecoder:%p will be destructed", this);
-  Release();
-}
-
-int32_t
-WebrtcOMXH264VideoDecoder::Reset()
-{
-  CODEC_LOGW("WebrtcOMXH264VideoDecoder::Reset() will NOT reset decoder");
-  return WEBRTC_VIDEO_CODEC_OK;
-}
-
-}
deleted file mode 100644
--- a/media/webrtc/signaling/src/media-conduit/WebrtcOMXH264VideoCodec.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/* 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 WEBRTC_GONK
-#pragma error WebrtcOMXH264VideoCodec works only on B2G.
-#endif
-
-#ifndef WEBRTC_OMX_H264_CODEC_H_
-#define WEBRTC_OMX_H264_CODEC_H_
-
-#include "AudioConduit.h"
-#include "VideoConduit.h"
-#include <foundation/ABase.h>
-#include <utils/RefBase.h>
-#include "OMXCodecWrapper.h"
-
-namespace android {
-  class OMXVideoEncoder;
-}
-
-namespace mozilla {
-
-class WebrtcOMXDecoder;
-class OMXOutputDrain;
-
-// XXX see if we can reduce this
-#define WEBRTC_OMX_H264_MIN_DECODE_BUFFERS 10
-#define OMX_IDR_NEEDED_FOR_BITRATE 0
-
-class WebrtcOMXH264VideoEncoder : public WebrtcVideoEncoder
-{
-public:
-  WebrtcOMXH264VideoEncoder();
-
-  virtual ~WebrtcOMXH264VideoEncoder();
-
-  // Implement VideoEncoder interface.
-  virtual uint64_t PluginID() const override { return 0; }
-
-  virtual int32_t InitEncode(const webrtc::VideoCodec* aCodecSettings,
-                             int32_t aNumOfCores,
-                             size_t aMaxPayloadSize) override;
-
-  virtual int32_t Encode(const webrtc::VideoFrame& aInputImage,
-                         const webrtc::CodecSpecificInfo* aCodecSpecificInfo,
-                         const std::vector<webrtc::FrameType>* aFrameTypes) override;
-
-  virtual int32_t RegisterEncodeCompleteCallback(webrtc::EncodedImageCallback* aCallback) override;
-
-  virtual int32_t Release() override;
-
-  virtual int32_t SetChannelParameters(uint32_t aPacketLossRate,
-                                       int64_t aRoundTripTimeMs) override;
-
-  virtual int32_t SetRates(uint32_t aBitRate, uint32_t aFrameRate) override;
-
-private:
-  nsAutoPtr<android::OMXVideoEncoder> mOMX;
-  android::sp<android::OMXCodecReservation> mReservation;
-
-  webrtc::EncodedImageCallback* mCallback;
-  RefPtr<OMXOutputDrain> mOutputDrain;
-  uint32_t mWidth;
-  uint32_t mHeight;
-  uint32_t mFrameRate;
-  uint32_t mBitRateKbps;
-#ifdef OMX_IDR_NEEDED_FOR_BITRATE
-  uint32_t mBitRateAtLastIDR;
-  TimeStamp mLastIDRTime;
-#endif
-  bool mOMXConfigured;
-  bool mOMXReconfigure;
-  webrtc::EncodedImage mEncodedImage;
-};
-
-class WebrtcOMXH264VideoDecoder : public WebrtcVideoDecoder
-{
-public:
-  WebrtcOMXH264VideoDecoder();
-
-  virtual ~WebrtcOMXH264VideoDecoder();
-
-  // Implement VideoDecoder interface.
-  virtual uint64_t PluginID() const override { return 0; }
-
-  virtual int32_t InitDecode(const webrtc::VideoCodec* aCodecSettings,
-                             int32_t aNumOfCores) override;
-  virtual int32_t Decode(const webrtc::EncodedImage& aInputImage,
-                         bool aMissingFrames,
-                         const webrtc::RTPFragmentationHeader* aFragmentation,
-                         const webrtc::CodecSpecificInfo* aCodecSpecificInfo = nullptr,
-                         int64_t aRenderTimeMs = -1) override;
-  virtual int32_t RegisterDecodeCompleteCallback(webrtc::DecodedImageCallback* callback) override;
-
-  virtual int32_t Release() override;
-
-  virtual int32_t Reset() override;
-
-private:
-  webrtc::DecodedImageCallback* mCallback;
-  RefPtr<WebrtcOMXDecoder> mOMX;
-  android::sp<android::OMXCodecReservation> mReservation;
-};
-
-}
-
-#endif // WEBRTC_OMX_H264_CODEC_H_
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -2122,17 +2122,18 @@ void MediaPipelineReceiveAudio::DetachMe
 
     if (stream_->GraphImpl()) {
       stream_->RemoveListener(listener_);
     }
     stream_ = nullptr;
   }
 }
 
-nsresult MediaPipelineReceiveAudio::Init() {
+nsresult MediaPipelineReceiveAudio::Init()
+{
   ASSERT_ON_THREAD(main_thread_);
   MOZ_MTLOG(ML_DEBUG, __FUNCTION__);
 
   description_ = pc_ + "| Receive audio[";
   description_ += track_id_;
   description_ += "]";
 
   listener_->AddSelf();
@@ -2143,125 +2144,112 @@ nsresult MediaPipelineReceiveAudio::Init
 void MediaPipelineReceiveAudio::SetPrincipalHandle_m(const PrincipalHandle& principal_handle)
 {
   listener_->SetPrincipalHandle_m(principal_handle);
 }
 
 class MediaPipelineReceiveVideo::PipelineListener
   : public GenericReceiveListener {
 public:
-  PipelineListener(SourceMediaStream * source, TrackID track_id)
-    : GenericReceiveListener(source, track_id),
-      width_(0),
-      height_(0),
-      image_container_(),
-      image_(),
-      monitor_("Video PipelineListener")
+  PipelineListener(SourceMediaStream* source, TrackID track_id)
+    : GenericReceiveListener(source, track_id)
+    , image_container_()
+    , image_()
+    , mutex_("Video PipelineListener")
   {
     image_container_ =
       LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS);
   }
 
   // Implement MediaStreamListener
   void NotifyPull(MediaStreamGraph* graph, StreamTime desired_time) override
   {
-    ReentrantMonitorAutoEnter enter(monitor_);
+    MutexAutoLock lock(mutex_);
 
     RefPtr<Image> image = image_;
     StreamTime delta = desired_time - played_ticks_;
 
     // Don't append if we've already provided a frame that supposedly
     // goes past the current aDesiredTime Doing so means a negative
     // delta and thus messes up handling of the graph
     if (delta > 0) {
       VideoSegment segment;
-      segment.AppendFrame(image.forget(), delta, IntSize(width_, height_),
-                          principal_handle_);
+      IntSize size = image ? image->GetSize() : IntSize(width_, height_);
+      segment.AppendFrame(image.forget(), delta, size, principal_handle_);
       // Handle track not actually added yet or removed/finished
       if (source_->AppendToTrack(track_id_, &segment)) {
         played_ticks_ = desired_time;
       } else {
         MOZ_MTLOG(ML_ERROR, "AppendToTrack failed");
         return;
       }
     }
   }
 
   // Accessors for external writes from the renderer
   void FrameSizeChange(unsigned int width,
                        unsigned int height,
                        unsigned int number_of_streams) {
-    ReentrantMonitorAutoEnter enter(monitor_);
+    MutexAutoLock enter(mutex_);
 
     width_ = width;
     height_ = height;
   }
 
   void RenderVideoFrame(const webrtc::VideoFrameBuffer& buffer,
                         uint32_t time_stamp,
-                        int64_t render_time,
-                        const RefPtr<layers::Image>& video_image)
+                        int64_t render_time)
   {
-    RenderVideoFrame(buffer.DataY(),
-                     buffer.StrideY(),
-                     buffer.DataU(),
-                     buffer.StrideU(),
-                     buffer.DataV(),
-                     buffer.StrideV(),
-                     time_stamp, render_time, video_image);
-  }
+    if (buffer.native_handle()) {
+      // We assume that only native handles are used with the
+      // WebrtcMediaDataDecoderCodec decoder.
+      RefPtr<Image> image = static_cast<Image*>(buffer.native_handle());
+      MutexAutoLock lock(mutex_);
+      image_ = image;
+      return;
+    }
 
-  void RenderVideoFrame(const uint8_t* buffer_y,
-                        uint32_t y_stride,
-                        const uint8_t* buffer_u,
-                        uint32_t u_stride,
-                        const uint8_t* buffer_v,
-                        uint32_t v_stride,
-                        uint32_t time_stamp,
-                        int64_t render_time,
-                        const RefPtr<layers::Image>& video_image)
-  {
-    ReentrantMonitorAutoEnter enter(monitor_);
+    MOZ_ASSERT(buffer.DataY());
+    // Create a video frame using |buffer|.
+    RefPtr<PlanarYCbCrImage> yuvImage =
+      image_container_->CreatePlanarYCbCrImage();
 
-    if (buffer_y) {
-      // Create a video frame using |buffer|.
-      RefPtr<PlanarYCbCrImage> yuvImage = image_container_->CreatePlanarYCbCrImage();
+    PlanarYCbCrData yuvData;
+    yuvData.mYChannel = const_cast<uint8_t*>(buffer.DataY());
+    yuvData.mYSize = IntSize(buffer.width(), buffer.height());
+    yuvData.mYStride = buffer.StrideY();
+    MOZ_ASSERT(buffer.StrideU() == buffer.StrideV());
+    yuvData.mCbCrStride = buffer.StrideU();
+    yuvData.mCbChannel = const_cast<uint8_t*>(buffer.DataU());
+    yuvData.mCrChannel = const_cast<uint8_t*>(buffer.DataV());
+    yuvData.mCbCrSize =
+      IntSize((buffer.width() + 1) >> 1, (buffer.height() + 1) >> 1);
+    yuvData.mPicX = 0;
+    yuvData.mPicY = 0;
+    yuvData.mPicSize = IntSize(buffer.width(), buffer.height());
+    yuvData.mStereoMode = StereoMode::MONO;
 
-      PlanarYCbCrData yuvData;
-      yuvData.mYChannel = const_cast<uint8_t*>(buffer_y);
-      yuvData.mYSize = IntSize(y_stride, height_);
-      yuvData.mYStride = y_stride;
-      MOZ_ASSERT(u_stride == v_stride);
-      yuvData.mCbCrStride = u_stride;
-      yuvData.mCbChannel = const_cast<uint8_t*>(buffer_u);
-      yuvData.mCrChannel = const_cast<uint8_t*>(buffer_v);
-      yuvData.mCbCrSize = IntSize(yuvData.mCbCrStride, (height_ + 1) >> 1);
-      yuvData.mPicX = 0;
-      yuvData.mPicY = 0;
-      yuvData.mPicSize = IntSize(width_, height_);
-      yuvData.mStereoMode = StereoMode::MONO;
+    if (!yuvImage->CopyData(yuvData)) {
+      MOZ_ASSERT(false);
+      return;
+    }
 
-      if (!yuvImage->CopyData(yuvData)) {
-        MOZ_ASSERT(false);
-        return;
-      }
-
-      image_ = yuvImage;
-    }
+    MutexAutoLock lock(mutex_);
+    image_ = yuvImage;
   }
 
 private:
   int width_;
   int height_;
   RefPtr<layers::ImageContainer> image_container_;
   RefPtr<layers::Image> image_;
-  mozilla::ReentrantMonitor monitor_; // Monitor for processing WebRTC frames.
-                                      // Protects image_ against:
-                                      // - Writing from the GIPS thread
-                                      // - Reading from the MSG thread
+  Mutex mutex_; // Mutex for processing WebRTC frames.
+                // Protects image_ against:
+                // - Writing from the GIPS thread
+                // - Reading from the MSG thread
 };
 
 class MediaPipelineReceiveVideo::PipelineRenderer : public mozilla::VideoRenderer
 {
 public:
   explicit PipelineRenderer(MediaPipelineReceiveVideo *pipeline) :
     pipeline_(pipeline) {}
 
@@ -2272,39 +2260,19 @@ public:
                        unsigned int height,
                        unsigned int number_of_streams) override
   {
     pipeline_->listener_->FrameSizeChange(width, height, number_of_streams);
   }
 
   void RenderVideoFrame(const webrtc::VideoFrameBuffer& buffer,
                         uint32_t time_stamp,
-                        int64_t render_time,
-                        const ImageHandle& handle) override
+                        int64_t render_time) override
   {
-    pipeline_->listener_->RenderVideoFrame(buffer,
-                                           time_stamp, render_time,
-                                           handle.GetImage());
-  }
-
-  void RenderVideoFrame(const uint8_t* buffer_y,
-                        uint32_t y_stride,
-                        const uint8_t* buffer_u,
-                        uint32_t u_stride,
-                        const uint8_t* buffer_v,
-                        uint32_t v_stride,
-                        uint32_t time_stamp,
-                        int64_t render_time,
-                        const ImageHandle& handle) override
-  {
-    pipeline_->listener_->RenderVideoFrame(buffer_y, y_stride,
-                                           buffer_u, u_stride,
-                                           buffer_v, v_stride,
-                                           time_stamp, render_time,
-                                           handle.GetImage());
+    pipeline_->listener_->RenderVideoFrame(buffer, time_stamp, render_time);
   }
 
 private:
   MediaPipelineReceiveVideo *pipeline_;  // Raw pointer to avoid cycles
 };
 
 
 MediaPipelineReceiveVideo::MediaPipelineReceiveVideo(
--- a/mfbt/Assertions.h
+++ b/mfbt/Assertions.h
@@ -273,16 +273,20 @@ static MOZ_COLD MOZ_NORETURN MOZ_NEVER_I
 
 /*
  * MOZ_CRASH_UNSAFE_OOL(explanation-string) can be used if the explanation
  * string cannot be a string literal (but no other processing needs to be done
  * on it). A regular MOZ_CRASH() is preferred wherever possible, as passing
  * arbitrary strings from a potentially compromised process is not without risk.
  * If the string being passed is the result of a printf-style function,
  * consider using MOZ_CRASH_UNSAFE_PRINTF instead.
+ *
+ * @note This macro causes data collection because crash strings are annotated
+ * to crash-stats and are publicly visible. Firefox data stewards must do data
+ * review on usages of this macro.
  */
 #ifndef DEBUG
 MFBT_API MOZ_COLD MOZ_NORETURN MOZ_NEVER_INLINE void
 MOZ_CrashOOL(int aLine, const char* aReason);
 #  define MOZ_CRASH_UNSAFE_OOL(reason) MOZ_CrashOOL(__LINE__, reason)
 #else
 MFBT_API MOZ_COLD MOZ_NORETURN MOZ_NEVER_INLINE void
 MOZ_CrashOOL(const char* aFilename, int aLine, const char* aReason);
@@ -306,16 +310,20 @@ MOZ_CrashPrintf(const char* aFilename, i
 
 /*
  * MOZ_CRASH_UNSAFE_PRINTF(format, arg1 [, args]) can be used when more
  * information is desired than a string literal can supply. The caller provides
  * a printf-style format string, which must be a string literal and between
  * 1 and 4 additional arguments. A regular MOZ_CRASH() is preferred wherever
  * possible, as passing arbitrary strings to printf from a potentially
  * compromised process is not without risk.
+ *
+ * @note This macro causes data collection because crash strings are annotated
+ * to crash-stats and are publicly visible. Firefox data stewards must do data
+ * review on usages of this macro.
  */
 #define MOZ_CRASH_UNSAFE_PRINTF(format, ...) \
    do { \
      static_assert( \
        MOZ_ARG_COUNT(__VA_ARGS__) > 0, \
        "Did you forget arguments to MOZ_CRASH_UNSAFE_PRINTF? " \
        "Or maybe you want MOZ_CRASH instead?"); \
      static_assert( \
--- a/mobile/android/components/extensions/ext-utils.js
+++ b/mobile/android/components/extensions/ext-utils.js
@@ -393,16 +393,20 @@ class Tab extends TabBase {
   get index() {
     return this.window.BrowserApp.tabs.indexOf(this.nativeTab);
   }
 
   get mutedInfo() {
     return {muted: false};
   }
 
+  get lastAccessed() {
+    return this.nativeTab.lastTouchedAt;
+  }
+
   get pinned() {
     return false;
   }
 
   get active() {
     return this.nativeTab.getActive();
   }
 
--- a/mobile/android/components/extensions/schemas/tabs.json
+++ b/mobile/android/components/extensions/schemas/tabs.json
@@ -59,16 +59,17 @@
           "id": {"type": "integer", "minimum": -1, "optional": true, "description": "The ID of the tab. Tab IDs are unique within a browser session. Under some circumstances a Tab may not be assigned an ID, for example when querying foreign tabs using the $(ref:sessions) API, in which case a session ID may be present. Tab ID can also be set to $(ref:tabs.TAB_ID_NONE) for apps and devtools windows."},
           "index": {"type": "integer", "minimum": -1, "description": "The zero-based index of the tab within its window."},
           "windowId": {"type": "integer", "minimum": 0, "description": "The ID of the window the tab is contained within."},
           "openerTabId": {"unsupported": true, "type": "integer", "minimum": 0, "optional": true, "description": "The ID of the tab that opened this tab, if any. This property is only present if the opener tab still exists."},
           "selected": {"type": "boolean", "description": "Whether the tab is selected.", "deprecated": "Please use $(ref:tabs.Tab.highlighted).", "unsupported": true},
           "highlighted": {"type": "boolean", "description": "Whether the tab is highlighted. Works as an alias of active."},
           "active": {"type": "boolean", "description": "Whether the tab is active in its window. (Does not necessarily mean the window is focused.)"},
           "pinned": {"type": "boolean", "description": "Whether the tab is pinned."},
+          "lastAccessed": {"type": "integer", "optional": true, "description": "The last time the tab was accessed as the number of milliseconds since epoch."},
           "audible": {"type": "boolean", "optional": true, "description": "Whether the tab has produced sound over the past couple of seconds (but it might not be heard if also muted). Equivalent to whether the speaker audio indicator is showing."},
           "mutedInfo": {"$ref": "MutedInfo", "optional": true, "description": "Current tab muted state and the reason for the last state change."},
           "url": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL the tab is displaying. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "title": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The title of the tab. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission."},
           "favIconUrl": {"type": "string", "optional": true, "permissions": ["tabs"], "description": "The URL of the tab's favicon. This property is only present if the extension's manifest includes the <code>\"tabs\"</code> permission. It may also be an empty string if the tab is loading."},
           "status": {"type": "string", "optional": true, "description": "Either <em>loading</em> or <em>complete</em>."},
           "incognito": {"type": "boolean", "description": "Whether the tab is in an incognito window."},
           "width": {"type": "integer", "optional": true, "description": "The width of the tab in pixels."},
--- a/mobile/android/components/extensions/test/mochitest/mochitest.ini
+++ b/mobile/android/components/extensions/test/mochitest/mochitest.ini
@@ -21,13 +21,14 @@ tags = webextensions
 [test_ext_tabs_executeScript.html]
 [test_ext_tabs_executeScript_bad.html]
 skip-if = true # Currently fails in emulator runs
 [test_ext_tabs_executeScript_good.html]
 [test_ext_tabs_executeScript_no_create.html]
 [test_ext_tabs_executeScript_runAt.html]
 [test_ext_tabs_getCurrent.html]
 [test_ext_tabs_insertCSS.html]
+[test_ext_tabs_lastAccessed.html]
 [test_ext_tabs_reload.html]
 [test_ext_tabs_reload_bypass_cache.html]
 [test_ext_tabs_onUpdated.html]
 [test_ext_tabs_sendMessage.html]
 [test_ext_tabs_update_url.html]
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_tabs_lastAccessed.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Tabs lastAccessed Test</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+  <script type="text/javascript" src="head.js"></script>
+  <link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+add_task(async function testLastAccessed() {
+  let past = Date.now();
+
+  window.open("https://example.com/?1");
+  window.open("https://example.com/?2");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      permissions: ["tabs"],
+    },
+    async background() {
+      browser.test.onMessage.addListener(async function(msg, past) {
+        if (msg !== "past") {
+          return;
+        }
+
+        let [tab1] = await browser.tabs.query({url: "https://example.com/?1"});
+        let [tab2] = await browser.tabs.query({url: "https://example.com/?2"});
+
+        browser.test.assertTrue(tab1 && tab2, "Expected tabs were found");
+
+        let now = Date.now();
+
+        browser.test.assertTrue(past < tab1.lastAccessed &&
+                                tab1.lastAccessed < tab2.lastAccessed &&
+                                tab2.lastAccessed <= now,
+                                "lastAccessed timestamps are recent and in the right order");
+
+        await browser.tabs.remove([tab1.id, tab2.id]);
+
+        browser.test.notifyPass("tabs.lastAccessed");
+      });
+    },
+  });
+
+  await extension.startup();
+  await extension.sendMessage("past", past);
+  await extension.awaitFinish("tabs.lastAccessed");
+  await extension.unload();
+});
+</script>
+
+</body>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -558,16 +558,19 @@ pref("media.peerconnection.capture_delay
 pref("media.getusermedia.playout_delay", 50);
 pref("media.navigator.audio.full_duplex", true);
 #else
 // *BSD, others - merely a guess for now
 pref("media.peerconnection.capture_delay", 50);
 pref("media.getusermedia.playout_delay", 50);
 pref("media.navigator.audio.full_duplex", false);
 #endif
+// Use MediaDataDecoder API for WebRTC, this includes hardware acceleration for
+// decoding.
+pref("media.navigator.mediadatadecoder_enabled", false);
 #endif
 
 pref("dom.webaudio.enabled", true);
 
 #if !defined(ANDROID)
 pref("media.getusermedia.screensharing.enabled", true);
 #endif
 
--- a/npm-shrinkwrap.json
+++ b/npm-shrinkwrap.json
@@ -175,17 +175,17 @@
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
       "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
     },
     "d": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz",
       "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=",
       "requires": {
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.24"
       }
     },
     "debug": {
       "version": "2.6.8",
       "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz",
       "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=",
       "requires": {
         "ms": "2.0.0"
@@ -258,75 +258,75 @@
       }
     },
     "entities": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz",
       "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA="
     },
     "es5-ext": {
-      "version": "0.10.23",
-      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.23.tgz",
-      "integrity": "sha1-dXi1G+l0IHpUh4IbVlOMIk5Oezg=",
+      "version": "0.10.24",
+      "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.24.tgz",
+      "integrity": "sha1-pVh3yZJLwMjZvTwsvhdJWsFwmxQ=",
       "requires": {
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
       }
     },
     "es6-iterator": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.1.tgz",
       "integrity": "sha1-jjGcnwRTv1ddN0lAplWSDlnKVRI=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.24",
         "es6-symbol": "3.1.1"
       }
     },
     "es6-map": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz",
       "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.24",
         "es6-iterator": "2.0.1",
         "es6-set": "0.1.5",
         "es6-symbol": "3.1.1",
         "event-emitter": "0.3.5"
       }
     },
     "es6-set": {
       "version": "0.1.5",
       "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz",
       "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.24",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1",
         "event-emitter": "0.3.5"
       }
     },
     "es6-symbol": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz",
       "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.24"
       }
     },
     "es6-weak-map": {
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz",
       "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23",
+        "es5-ext": "0.10.24",
         "es6-iterator": "2.0.1",
         "es6-symbol": "3.1.1"
       }
     },
     "escape-string-regexp": {
       "version": "1.0.5",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
@@ -360,17 +360,17 @@
         "file-entry-cache": "2.0.0",
         "glob": "7.1.2",
         "globals": "9.18.0",
         "ignore": "3.3.3",
         "imurmurhash": "0.1.4",
         "inquirer": "0.12.0",
         "is-my-json-valid": "2.16.0",
         "is-resolvable": "1.0.0",
-        "js-yaml": "3.8.4",
+        "js-yaml": "3.9.0",
         "json-stable-stringify": "1.0.1",
         "levn": "0.3.0",
         "lodash": "4.17.4",
         "mkdirp": "0.5.1",
         "natural-compare": "1.4.0",
         "optionator": "0.8.2",
         "path-is-inside": "1.0.2",
         "pluralize": "1.2.1",
@@ -429,19 +429,19 @@
       "resolved": "https://registry.npmjs.org/espree/-/espree-3.4.3.tgz",
       "integrity": "sha1-KRC1zNSc6JPC//+qtP2LOjG4I3Q=",
       "requires": {
         "acorn": "5.1.1",
         "acorn-jsx": "3.0.1"
       }
     },
     "esprima": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
-      "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
+      "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw=="
     },
     "esquery": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz",
       "integrity": "sha1-z7qLV9f7qT8XKYqKAGoEzaE9gPo=",
       "requires": {
         "estraverse": "4.2.0"
       }
@@ -466,17 +466,17 @@
       "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs="
     },
     "event-emitter": {
       "version": "0.3.5",
       "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
       "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=",
       "requires": {
         "d": "1.0.0",
-        "es5-ext": "0.10.23"
+        "es5-ext": "0.10.24"
       }
     },
     "exit-hook": {
       "version": "1.1.1",
       "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
       "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g="
     },
     "fast-levenshtein": {
@@ -714,22 +714,22 @@
       "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
     },
     "js-tokens": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
       "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls="
     },
     "js-yaml": {
-      "version": "3.8.4",
-      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.8.4.tgz",
-      "integrity": "sha1-UgtFZPhlc7qWZir4Woyvp7S1pvY=",
+      "version": "3.9.0",
+      "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.9.0.tgz",
+      "integrity": "sha512-0LoUNELX4S+iofCT8f4uEHIiRBR+c2AINyC8qRWfC6QNruLtxVZRJaPcu/xwMgFIgDxF25tGHaDjvxzJCNE9yw==",
       "requires": {
         "argparse": "1.0.9",
-        "esprima": "3.1.3"
+        "esprima": "4.0.0"
       }
     },
     "json-stable-stringify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz",
       "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=",
       "requires": {
         "jsonify": "0.0.0"
--- a/package.json
+++ b/package.json
@@ -1,19 +1,14 @@
 {
   "name": "mozillaeslintsetup",
   "description": "This package file is for setup of ESLint only for editor integration.",
   "repository": {},
   "license": "MPL-2.0",
   "dependencies": {
-    "escope": "^3.6.0",
     "eslint": "3.19.0",
     "eslint-plugin-html": "2.0.3",
     "eslint-plugin-mozilla": "file:tools/lint/eslint/eslint-plugin-mozilla",
     "eslint-plugin-no-unsanitized": "2.0.1",
     "eslint-plugin-react": "7.1.0",
-    "eslint-plugin-spidermonkey-js": "file:tools/lint/eslint/eslint-plugin-spidermonkey-js",
-    "espree": "^3.4.0",
-    "estraverse": "^4.2.0",
-    "ini-parser": "^0.0.2",
-    "sax": "^1.2.2"
+    "eslint-plugin-spidermonkey-js": "file:tools/lint/eslint/eslint-plugin-spidermonkey-js"
   }
 }
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1136,16 +1136,17 @@ source = "registry+https://github.com/ru
 name = "glutin_app"
 version = "0.0.1"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "compositing 0.0.1",
  "euclid 0.15.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libservo 0.0.1",
  "log 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "osmesa-src 12.0.1 (git+https://github.com/servo/osmesa-src)",
  "osmesa-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
--- a/servo/components/compositing/compositor.rs
+++ b/servo/components/compositing/compositor.rs
@@ -504,20 +504,17 @@ impl<Window: WindowMethods> IOCompositor
 
                 // Inform the embedder that the load has finished.
                 //
                 // TODO(pcwalton): Specify which frame's load completed.
                 self.window.load_end();
             }
 
             (Msg::AllowNavigation(url, response_chan), ShutdownState::NotShuttingDown) => {
-                let allow = self.window.allow_navigation(url);
-                if let Err(e) = response_chan.send(allow) {
-                    warn!("Failed to send allow_navigation result ({}).", e);
-                }
+                self.window.allow_navigation(url, response_chan);
             }
 
             (Msg::Recomposite(reason), ShutdownState::NotShuttingDown) => {
                 self.composition_request = CompositionRequest::CompositeNow(reason)
             }
 
             (Msg::KeyEvent(ch, key, state, modified), ShutdownState::NotShuttingDown) => {
                 if state == KeyState::Pressed {
--- a/servo/components/compositing/windowing.rs
+++ b/servo/components/compositing/windowing.rs
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Abstract windowing methods. The concrete implementations of these can be found in `platform/`.
 
 use compositor_thread::EventLoopWaker;
 use euclid::{Point2D, Size2D};
 use euclid::{TypedPoint2D, TypedRect, ScaleFactor, TypedSize2D};
 use gleam::gl;
+use ipc_channel::ipc::IpcSender;
 use msg::constellation_msg::{Key, KeyModifiers, KeyState, TraversalDirection};
 use net_traits::net_error_list::NetError;
 use script_traits::{DevicePixel, LoadData, MouseButton, TouchEventType, TouchId, TouchpadPressurePhase};
 use servo_geometry::DeviceIndependentPixel;
 use servo_url::ServoUrl;
 use std::fmt::{Debug, Error, Formatter};
 use std::rc::Rc;
 use style_traits::cursor::Cursor;
@@ -126,17 +127,17 @@ pub trait WindowMethods {
     fn status(&self, Option<String>);
     /// Called when the browser has started loading a frame.
     fn load_start(&self);
     /// Called when the browser is done loading a frame.
     fn load_end(&self);
     /// Called when the browser encounters an error while loading a URL
     fn load_error(&self, code: NetError, url: String);
     /// Wether or not to follow a link
-    fn allow_navigation(&self, url: ServoUrl) -> bool;
+    fn allow_navigation(&self, url: ServoUrl, IpcSender<bool>);
     /// Called when the <head> tag has finished parsing
     fn head_parsed(&self);
     /// Called when the history state has changed.
     fn history_changed(&self, Vec<LoadData>, usize);
 
     /// Returns the scale factor of the system (device pixels / device independent pixels).
     fn hidpi_factor(&self) -> ScaleFactor<f32, DeviceIndependentPixel, DevicePixel>;
 
--- a/servo/components/gfx/font_cache_thread.rs
+++ b/servo/components/gfx/font_cache_thread.rs
@@ -211,17 +211,18 @@ impl FontCache {
                     Some(url) => url.clone(),
                     None => return,
                 };
 
                 let request = RequestInit {
                     url: url.clone(),
                     type_: RequestType::Font,
                     destination: Destination::Font,
-                    origin: url.clone(),
+                    // TODO: Add a proper origin - Can't import GlobalScope from gfx
+                    // We can leave origin to be set by default
                     .. RequestInit::default()
                 };
 
                 let channel_to_self = self.channel_to_self.clone();
                 let bytes = Mutex::new(Vec::new());
                 let response_valid = Mutex::new(false);
                 debug!("Loading @font-face {} from {}", family_name, url);
                 fetch_async(request, &self.core_resource_thread, move |response| {
--- a/servo/components/layout/display_list_builder.rs
+++ b/servo/components/layout/display_list_builder.rs
@@ -51,27 +51,26 @@ use style::computed_values::{background_
 use style::computed_values::{background_repeat, border_style, cursor};
 use style::computed_values::{image_rendering, overflow_x, pointer_events, position, visibility};
 use style::logical_geometry::{LogicalPoint, LogicalRect, LogicalSize, WritingMode};
 use style::properties::{self, ServoComputedValues};
 use style::properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
 use style::properties::style_structs;
 use style::servo::restyle_damage::REPAINT;
 use style::values::{Either, RGBA};
-use style::values::computed::{Angle, Gradient, GradientItem, LengthOrPercentage};
+use style::values::computed::{Angle, Gradient, GradientItem, LengthOrPercentage, Percentage};
 use style::values::computed::{LengthOrPercentageOrAuto, NumberOrPercentage, Position};
 use style::values::computed::effects::SimpleShadow;
 use style::values::computed::image::{EndingShape, LineDirection};
 use style::values::generics::background::BackgroundSize;
 use style::values::generics::effects::Filter;
 use style::values::generics::image::{Circle, Ellipse, EndingShape as GenericEndingShape};
 use style::values::generics::image::{GradientItem as GenericGradientItem, GradientKind};
 use style::values::generics::image::{Image, ShapeExtent};
 use style::values::generics::image::PaintWorklet;
-use style::values::specified::length::Percentage;
 use style::values::specified::position::{X, Y};
 use style_traits::CSSPixel;
 use style_traits::cursor::Cursor;
 use table_cell::CollapsedBordersForCell;
 use webrender_api::{ClipId, ColorF, ComplexClipRegion, GradientStop, LocalClip, RepeatMode};
 use webrender_api::{ScrollPolicy, TransformStyle};
 use webrender_helpers::{ToBorderRadius, ToMixBlendMode, ToRectF, ToTransformStyle};
 
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -128,17 +128,16 @@ use style::selector_parser::SnapshotMap;
 use style::servo::restyle_damage::{REFLOW, REFLOW_OUT_OF_FLOW, REPAINT, REPOSITION, STORE_OVERFLOW};
 use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
 use style::stylearc::Arc as StyleArc;
 use style::stylesheets::{Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets};
 use style::stylist::{ExtraStyleData, Stylist};
 use style::thread_state;
 use style::timer::Timer;
 use style::traversal::{DomTraversal, TraversalDriver, TraversalFlags};
-use style::values::CompactCowStr;
 
 /// Information needed by the layout thread.
 pub struct LayoutThread {
     /// The ID of the pipeline that we belong to.
     id: PipelineId,
 
     /// The ID of the top-level browsing context that we belong to.
     top_level_browsing_context_id: TopLevelBrowsingContextId,
@@ -709,17 +708,17 @@ impl LayoutThread {
                 self.create_layout_thread(info)
             }
             Msg::SetFinalUrl(final_url) => {
                 self.url = final_url;
             },
             Msg::RegisterPaint(name, mut properties, painter) => {
                 debug!("Registering the painter");
                 let properties = properties.drain(..)
-                    .filter_map(|name| PropertyId::parse(CompactCowStr::from(&*name)).ok().map(|id| (name.clone(), id)))
+                    .filter_map(|name| PropertyId::parse(&*name).ok().map(|id| (name.clone(), id)))
                     .filter(|&(_, ref id)| id.as_shorthand().is_err())
                     .collect();
                 let registered_painter = RegisteredPainter {
                     name: name.clone(),
                     properties: properties,
                     painter: painter,
                 };
                 self.registered_painters.write()
--- a/servo/components/net/http_loader.rs
+++ b/servo/components/net/http_loader.rs
@@ -1366,17 +1366,17 @@ fn cors_check(request: &Request, respons
     // Step 4
     let origin = match origin {
         AccessControlAllowOrigin::Value(origin) => origin,
         // if it's Any or Null at this point, there's nothing to do but return Err(())
         _ => return Err(())
     };
 
     match request.origin {
-        Origin::Origin(ref o) if o.ascii_serialization() == origin => {},
+        Origin::Origin(ref o) if o.ascii_serialization() == origin.trim() => {},
         _ => return Err(())
     }
 
     // Step 5
     if request.credentials_mode != CredentialsMode::Include {
         return Ok(());
     }
 
--- a/servo/components/net_traits/request.rs
+++ b/servo/components/net_traits/request.rs
@@ -153,19 +153,17 @@ pub struct RequestInit {
     pub type_: Type,
     pub destination: Destination,
     pub synchronous: bool,
     pub mode: RequestMode,
     pub cache_mode: CacheMode,
     pub use_cors_preflight: bool,
     pub credentials_mode: CredentialsMode,
     pub use_url_credentials: bool,
-    // this should actually be set by fetch, but fetch
-    // doesn't have info about the client right now
-    pub origin: ServoUrl,
+    pub origin: ImmutableOrigin,
     // XXXManishearth these should be part of the client object
     pub referrer_url: Option<ServoUrl>,
     pub referrer_policy: Option<ReferrerPolicy>,
     pub pipeline_id: Option<PipelineId>,
     pub redirect_mode: RedirectMode,
     pub integrity_metadata: String,
     // to keep track of redirects
     pub url_list: Vec<ServoUrl>,
@@ -183,17 +181,17 @@ impl Default for RequestInit {
             type_: Type::None,
             destination: Destination::None,
             synchronous: false,
             mode: RequestMode::NoCors,
             cache_mode: CacheMode::Default,
             use_cors_preflight: false,
             credentials_mode: CredentialsMode::Omit,
             use_url_credentials: false,
-            origin: ServoUrl::parse("about:blank").unwrap(),
+            origin: ImmutableOrigin::new_opaque(),
             referrer_url: None,
             referrer_policy: None,
             pipeline_id: None,
             redirect_mode: RedirectMode::Follow,
             integrity_metadata: "".to_owned(),
             url_list: vec![],
         }
     }
@@ -297,17 +295,17 @@ impl Request {
             url_list: vec![url],
             redirect_count: 0,
             response_tainting: ResponseTainting::Basic,
         }
     }
 
     pub fn from_init(init: RequestInit) -> Request {
         let mut req = Request::new(init.url.clone(),
-                                   Some(Origin::Origin(init.origin.origin())),
+                                   Some(Origin::Origin(init.origin)),
                                    init.pipeline_id);
         req.method = init.method;
         req.headers = init.headers;
         req.unsafe_request = init.unsafe_request;
         req.body = init.body;
         req.service_workers_mode = init.service_workers_mode;
         req.type_ = init.type_;
         req.destination = init.destination;
--- a/servo/components/script/dom/cssstyledeclaration.rs
+++ b/servo/components/script/dom/cssstyledeclaration.rs
@@ -291,28 +291,28 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-item
     fn Item(&self, index: u32) -> DOMString {
         self.IndexedGetter(index).unwrap_or_default()
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertyvalue
     fn GetPropertyValue(&self, property: DOMString) -> DOMString {
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property
             return DOMString::new()
         };
         self.get_property_value(id)
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-getpropertypriority
     fn GetPropertyPriority(&self, property: DOMString) -> DOMString {
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property
             return DOMString::new()
         };
 
         self.owner.with_block(|pdb| {
             if pdb.property_priority(&id).important() {
@@ -326,34 +326,34 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setproperty
     fn SetProperty(&self,
                    property: DOMString,
                    value: DOMString,
                    priority: DOMString)
                    -> ErrorResult {
         // Step 3
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unknown property
             return Ok(())
         };
         self.set_property(id, value, priority)
     }
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-setpropertypriority
     fn SetPropertyPriority(&self, property: DOMString, priority: DOMString) -> ErrorResult {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
         // Step 2 & 3
-        let id = match PropertyId::parse(property.into()) {
+        let id = match PropertyId::parse(&property) {
             Ok(id) => id,
             Err(..) => return Ok(()), // Unkwown property
         };
 
         // Step 4
         let importance = match &*priority {
             "" => Importance::Normal,
             p if p.eq_ignore_ascii_case("important") => Importance::Important,
@@ -375,17 +375,17 @@ impl CSSStyleDeclarationMethods for CSSS
 
     // https://dev.w3.org/csswg/cssom/#dom-cssstyledeclaration-removeproperty
     fn RemoveProperty(&self, property: DOMString) -> Fallible<DOMString> {
         // Step 1
         if self.readonly {
             return Err(Error::NoModificationAllowed);
         }
 
-        let id = if let Ok(id) = PropertyId::parse(property.into()) {
+        let id = if let Ok(id) = PropertyId::parse(&property) {
             id
         } else {
             // Unkwown property, cannot be there to remove.
             return Ok(DOMString::new())
         };
 
         let mut string = String::new();
         self.owner.mutate_associated_block(|mut pdb, mut changed| {
--- a/servo/components/script/dom/dedicatedworkerglobalscope.rs
+++ b/servo/components/script/dom/dedicatedworkerglobalscope.rs
@@ -155,16 +155,17 @@ impl DedicatedWorkerGlobalScope {
                             parent_sender: Box<ScriptChan + Send>,
                             own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
                             receiver: Receiver<(TrustedWorkerAddress, WorkerScriptMsg)>,
                             worker_load_origin: WorkerScriptLoadOrigin,
                             closing: Arc<AtomicBool>) {
         let serialized_worker_url = worker_url.to_string();
         let name = format!("WebWorker for {}", serialized_worker_url);
         let top_level_browsing_context_id = TopLevelBrowsingContextId::installed();
+        let origin = GlobalScope::current().expect("No current global object").origin().immutable().clone();
 
         thread::Builder::new().name(name).spawn(move || {
             thread_state::initialize(thread_state::SCRIPT | thread_state::IN_WORKER);
 
             if let Some(top_level_browsing_context_id) = top_level_browsing_context_id {
                 TopLevelBrowsingContextId::install(top_level_browsing_context_id);
             }
 
@@ -174,20 +175,20 @@ impl DedicatedWorkerGlobalScope {
             let WorkerScriptLoadOrigin { referrer_url, referrer_policy, pipeline_id } = worker_load_origin;
 
             let request = RequestInit {
                 url: worker_url.clone(),
                 type_: RequestType::Script,
                 destination: Destination::Worker,
                 credentials_mode: CredentialsMode::Include,
                 use_url_credentials: true,
-                origin: worker_url,
                 pipeline_id: pipeline_id,
                 referrer_url: referrer_url,
                 referrer_policy: referrer_policy,
+                origin,
                 .. RequestInit::default()
             };
 
             let (metadata, bytes) = match load_whole_resource(request,
                                                               &init.resource_threads.sender()) {
                 Err(_) => {
                     println!("error loading script {}", serialized_worker_url);
                     parent_sender.send(CommonScriptMsg::RunnableMsg(WorkerEvent,
--- a/servo/components/script/dom/element.rs
+++ b/servo/components/script/dom/element.rs
@@ -108,17 +108,17 @@ use style::properties::{Importance, Prop
 use style::properties::longhands::{self, background_image, border_spacing, font_family, font_size, overflow_x};
 use style::rule_tree::CascadeLevel;
 use style::selector_parser::{NonTSPseudoClass, PseudoElement, RestyleDamage, SelectorImpl, SelectorParser};
 use style::selector_parser::extended_filtering;
 use style::shared_lock::{SharedRwLock, Locked};
 use style::stylearc::Arc;
 use style::thread_state;
 use style::values::{CSSFloat, Either};
-use style::values::specified;
+use style::values::{specified, computed};
 use stylesheet_loader::StylesheetOwner;
 
 // TODO: Update focus state when the top-level browsing context gains or loses system focus,
 // and when the element enters or leaves a browsing context container.
 // https://html.spec.whatwg.org/multipage/#selector-focus
 
 #[dom_struct]
 pub struct Element {
@@ -571,17 +571,17 @@ impl LayoutElementHelpers for LayoutJS<E
             LengthOrPercentageOrAuto::Auto
         };
 
         // FIXME(emilio): Use from_computed value here and below.
         match width {
             LengthOrPercentageOrAuto::Auto => {}
             LengthOrPercentageOrAuto::Percentage(percentage) => {
                 let width_value =
-                    specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage));
+                    specified::LengthOrPercentageOrAuto::Percentage(computed::Percentage(percentage));
                 hints.push(from_declaration(
                     shared_lock,
                     PropertyDeclaration::Width(width_value)));
             }
             LengthOrPercentageOrAuto::Length(length) => {
                 let width_value = specified::LengthOrPercentageOrAuto::Length(
                     specified::NoCalcLength::Absolute(specified::AbsoluteLength::Px(length.to_f32_px())));
                 hints.push(from_declaration(
@@ -600,17 +600,17 @@ impl LayoutElementHelpers for LayoutJS<E
         } else {
             LengthOrPercentageOrAuto::Auto
         };
 
         match height {
             LengthOrPercentageOrAuto::Auto => {}
             LengthOrPercentageOrAuto::Percentage(percentage) => {
                 let height_value =
-                    specified::LengthOrPercentageOrAuto::Percentage(specified::Percentage(percentage));
+                    specified::LengthOrPercentageOrAuto::Percentage(computed::Percentage(percentage));
                 hints.push(from_declaration(
                     shared_lock,
                     PropertyDeclaration::Height(height_value)));
             }
             LengthOrPercentageOrAuto::Length(length) => {
                 let height_value = specified::LengthOrPercentageOrAuto::Length(
                     specified::NoCalcLength::Absolute(specified::AbsoluteLength::Px(length.to_f32_px())));
                 hints.push(from_declaration(
--- a/servo/components/script/dom/eventsource.rs
+++ b/servo/components/script/dom/eventsource.rs
@@ -382,17 +382,17 @@ impl EventSource {
             CorsSettings::UseCredentials
         } else {
             CorsSettings::Anonymous
         };
         // Step 8
         // TODO: Step 9 set request's client settings
         let mut request = RequestInit {
             url: url_record,
-            origin: global.get_url(),
+            origin: global.origin().immutable().clone(),
             pipeline_id: Some(global.pipeline_id()),
             // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
             use_url_credentials: true,
             mode: RequestMode::CorsMode,
             credentials_mode: if cors_attribute_state == CorsSettings::Anonymous {
                 CredentialsMode::CredentialsSameOrigin
             } else {
                 CredentialsMode::Include
--- a/servo/components/script/dom/htmlimageelement.rs
+++ b/servo/components/script/dom/htmlimageelement.rs
@@ -254,17 +254,17 @@ impl HTMLImageElement {
             wrapper: Some(window.get_runnable_wrapper()),
         };
         ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
             listener.notify_fetch(message.to().unwrap());
         });
 
         let request = RequestInit {
             url: img_url.clone(),
-            origin: document.url().clone(),
+            origin: document.origin().immutable().clone(),
             type_: RequestType::Image,
             pipeline_id: Some(document.global().pipeline_id()),
             .. RequestInit::default()
         };
 
         // This is a background load because the load blocker already fulfills the
         // purpose of delaying the document's load event.
         document.loader().fetch_async_background(request, action_sender);
--- a/servo/components/script/dom/htmlmediaelement.rs
+++ b/servo/components/script/dom/htmlmediaelement.rs
@@ -545,17 +545,17 @@ impl HTMLMediaElement {
             };
 
             let request = RequestInit {
                 url: url.clone(),
                 type_: ty,
                 destination: Destination::Media,
                 credentials_mode: CredentialsMode::Include,
                 use_url_credentials: true,
-                origin: document.url(),
+                origin: document.origin().immutable().clone(),
                 pipeline_id: Some(self.global().pipeline_id()),
                 referrer_url: Some(document.url()),
                 referrer_policy: document.get_referrer_policy(),
                 .. RequestInit::default()
             };
 
             document.fetch_async(LoadType::Media(url), request, action_sender);
         } else {
--- a/servo/components/script/dom/htmlscriptelement.rs
+++ b/servo/components/script/dom/htmlscriptelement.rs
@@ -248,17 +248,17 @@ fn fetch_a_classic_script(script: &HTMLS
             None => RequestMode::NoCors,
         },
         // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
         // Step 3-4
         credentials_mode: match cors_setting {
             Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
             _ => CredentialsMode::Include,
         },
-        origin: doc.url(),
+        origin: doc.origin().immutable().clone(),
         pipeline_id: Some(script.global().pipeline_id()),
         referrer_url: Some(doc.url()),
         referrer_policy: doc.get_referrer_policy(),
         integrity_metadata: integrity_metadata,
         .. RequestInit::default()
     };
 
     // TODO: Step 3, Add custom steps to perform fetch
--- a/servo/components/script/dom/serviceworkerglobalscope.rs
+++ b/servo/components/script/dom/serviceworkerglobalscope.rs
@@ -146,33 +146,34 @@ impl ServiceWorkerGlobalScope {
                             swmanager_sender: IpcSender<ServiceWorkerMsg>,
                             scope_url: ServoUrl) {
         let ScopeThings { script_url,
                           init,
                           worker_load_origin,
                           .. } = scope_things;
 
         let serialized_worker_url = script_url.to_string();
+        let origin = GlobalScope::current().expect("No current global object").origin().immutable().clone();
         thread::Builder::new().name(format!("ServiceWorker for {}", serialized_worker_url)).spawn(move || {
             thread_state::initialize(SCRIPT | IN_WORKER);
             let roots = RootCollection::new();
             let _stack_roots_tls = StackRootTLS::new(&roots);
 
             let WorkerScriptLoadOrigin { referrer_url, referrer_policy, pipeline_id } = worker_load_origin;
 
             let request = RequestInit {
                 url: script_url.clone(),
                 type_: RequestType::Script,
                 destination: Destination::ServiceWorker,
                 credentials_mode: CredentialsMode::Include,
                 use_url_credentials: true,
-                origin: script_url,
                 pipeline_id: pipeline_id,
                 referrer_url: referrer_url,
                 referrer_policy: referrer_policy,
+                origin,
                 .. RequestInit::default()
             };
 
             let (url, source) = match load_whole_resource(request,
                                                           &init.resource_threads.sender()) {
                 Err(_) => {
                     println!("error loading script {}", serialized_worker_url);
                     return;
--- a/servo/components/script/dom/workerglobalscope.rs
+++ b/servo/components/script/dom/workerglobalscope.rs
@@ -206,17 +206,17 @@ impl WorkerGlobalScopeMethods for Worker
         for url in urls {
             let global_scope = self.upcast::<GlobalScope>();
             let request = NetRequestInit {
                 url: url.clone(),
                 type_: RequestType::Script,
                 destination: Destination::Script,
                 credentials_mode: CredentialsMode::Include,
                 use_url_credentials: true,
-                origin: self.worker_url.clone(),
+                origin: global_scope.origin().immutable().clone(),
                 pipeline_id: Some(self.upcast::<GlobalScope>().pipeline_id()),
                 referrer_url: None,
                 referrer_policy: None,
                 .. NetRequestInit::default()
             };
             let (url, source) = match load_whole_resource(request,
                                                           &global_scope.resource_threads().sender()) {
                 Err(_) => return Err(Error::Network),
--- a/servo/components/script/dom/worklet.rs
+++ b/servo/components/script/dom/worklet.rs
@@ -569,27 +569,24 @@ impl WorkletThread {
         debug!("Fetching from {}.", script_url);
         // Step 1.
         // TODO: Settings object?
 
         // Step 2.
         // TODO: Fetch a module graph, not just a single script.
         // TODO: Fetch the script asynchronously?
         // TODO: Caching.
-        // TODO: Avoid re-parsing the origin as a URL.
         let resource_fetcher = self.global_init.resource_threads.sender();
-        let origin_url = ServoUrl::parse(&*origin.unicode_serialization())
-            .unwrap_or_else(|_| ServoUrl::parse("about:blank").unwrap());
         let request = RequestInit {
             url: script_url,
             type_: RequestType::Script,
             destination: Destination::Script,
             mode: RequestMode::CorsMode,
-            origin: origin_url,
             credentials_mode: credentials.into(),
+            origin,
             .. RequestInit::default()
         };
         let script = load_whole_resource(request, &resource_fetcher).ok()
             .and_then(|(_, bytes)| String::from_utf8(bytes).ok());
 
         // Step 4.
         // NOTE: the spec parses and executes the script in separate steps,
         // but our JS API doesn't separate these, so we do the steps out of order.
--- a/servo/components/script/dom/xmlhttprequest.rs
+++ b/servo/components/script/dom/xmlhttprequest.rs
@@ -589,17 +589,17 @@ impl XMLHttpRequestMethods for XMLHttpRe
             // XXXManishearth actually "subresource", but it doesn't exist
             // https://github.com/whatwg/xhr/issues/71
             destination: Destination::None,
             synchronous: self.sync.get(),
             mode: RequestMode::CorsMode,
             use_cors_preflight: has_handlers,
             credentials_mode: credentials_mode,
             use_url_credentials: use_url_credentials,
-            origin: self.global().get_url(),
+            origin: self.global().origin().immutable().clone(),
             referrer_url: self.referrer_url.clone(),
             referrer_policy: self.referrer_policy.clone(),
             pipeline_id: Some(self.global().pipeline_id()),
             .. RequestInit::default()
         };
 
         if bypass_cross_origin_check {
             request.mode = RequestMode::Navigate;
--- a/servo/components/script/fetch.rs
+++ b/servo/components/script/fetch.rs
@@ -51,20 +51,17 @@ fn request_init_from_request(request: Ne
         body: request.body.clone(),
         type_: request.type_,
         destination: request.destination,
         synchronous: request.synchronous,
         mode: request.mode,
         use_cors_preflight: request.use_cors_preflight,
         credentials_mode: request.credentials_mode,
         use_url_credentials: request.use_url_credentials,
-        // TODO: NetTraitsRequestInit and NetTraitsRequest have different "origin"
-        // ... NetTraitsRequestInit.origin: Url
-        // ... NetTraitsRequest.origin: RefCell<Origin>
-        origin: request.url(),
+        origin: GlobalScope::current().expect("No current global object").origin().immutable().clone(),
         referrer_url: from_referrer_to_referrer_url(&request),
         referrer_policy: request.referrer_policy,
         pipeline_id: request.pipeline_id,
         redirect_mode: request.redirect_mode,
         ..NetTraitsRequestInit::default()
     }
 }
 
--- a/servo/components/script/layout_image.rs
+++ b/servo/components/script/layout_image.rs
@@ -65,17 +65,17 @@ pub fn fetch_image_for_layout(url: Servo
         wrapper: Some(window.get_runnable_wrapper()),
     };
     ROUTER.add_route(action_receiver.to_opaque(), box move |message| {
         listener.notify_fetch(message.to().unwrap());
     });
 
     let request = FetchRequestInit {
         url: url,
-        origin: document.url().clone(),
+        origin: document.origin().immutable().clone(),
         type_: RequestType::Image,
         pipeline_id: Some(document.global().pipeline_id()),
         .. FetchRequestInit::default()
     };
 
     // Layout image loads do not delay the document load event.
     document.mut_loader().fetch_async_background(request, action_sender);
 }
--- a/servo/components/script/script_thread.rs
+++ b/servo/components/script/script_thread.rs
@@ -1923,16 +1923,22 @@ impl ScriptThread {
         let DOMManipulationTaskSource(ref dom_sender) = self.dom_manipulation_task_source;
         let UserInteractionTaskSource(ref user_sender) = self.user_interaction_task_source;
         let HistoryTraversalTaskSource(ref history_sender) = self.history_traversal_task_source;
 
         let (ipc_timer_event_chan, ipc_timer_event_port) = ipc::channel().unwrap();
         ROUTER.route_ipc_receiver_to_mpsc_sender(ipc_timer_event_port,
                                                  self.timer_event_chan.clone());
 
+        let origin = if final_url.as_str() == "about:blank" {
+            incomplete.origin.clone()
+        } else {
+            MutableOrigin::new(final_url.origin())
+        };
+
         // Create the window and document objects.
         let window = Window::new(self.js_runtime.clone(),
                                  MainThreadScriptChan(sender.clone()),
                                  DOMManipulationTaskSource(dom_sender.clone()),
                                  UserInteractionTaskSource(user_sender.clone()),
                                  self.networking_task_source.clone(),
                                  HistoryTraversalTaskSource(history_sender.clone()),
                                  self.file_reading_task_source.clone(),
@@ -1946,17 +1952,17 @@ impl ScriptThread {
                                  self.constellation_chan.clone(),
                                  self.control_chan.clone(),
                                  self.scheduler_chan.clone(),
                                  ipc_timer_event_chan,
                                  incomplete.layout_chan,
                                  incomplete.pipeline_id,
                                  incomplete.parent_info,
                                  incomplete.window_size,
-                                 incomplete.origin.clone(),
+                                 origin,
                                  self.webvr_thread.clone());
 
         // Initialize the browsing context for the window.
         let window_proxy = self.local_window_proxy(&window,
                                                    incomplete.browsing_context_id,
                                                    incomplete.top_level_browsing_context_id,
                                                    incomplete.parent_info);
         window.init_window_proxy(&window_proxy);
@@ -2278,23 +2284,23 @@ impl ScriptThread {
     fn pre_page_load(&self, incomplete: InProgressLoad, load_data: LoadData) {
         let id = incomplete.pipeline_id.clone();
         let mut req_init = RequestInit {
             url: load_data.url.clone(),
             method: load_data.method,
             destination: Destination::Document,
             credentials_mode: CredentialsMode::Include,
             use_url_credentials: true,
-            origin: load_data.url.clone(),
             pipeline_id: Some(id),
             referrer_url: load_data.referrer_url,
             referrer_policy: load_data.referrer_policy,
             headers: load_data.headers,
             body: load_data.data,
             redirect_mode: RedirectMode::Manual,
+            origin: incomplete.origin.immutable().clone(),
             .. RequestInit::default()
         };
 
         if req_init.url.scheme() == "javascript" {
             req_init.url = ServoUrl::parse("about:blank").unwrap();
         }
 
         let context = ParserContext::new(id, load_data.url);
--- a/servo/components/script/stylesheet_loader.rs
+++ b/servo/components/script/stylesheet_loader.rs
@@ -256,17 +256,17 @@ impl<'a> StylesheetLoader<'a> {
                 None => RequestMode::NoCors,
             },
             // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
             // Step 3-4
             credentials_mode: match cors_setting {
                 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
                 _ => CredentialsMode::Include,
             },
-            origin: document.url(),
+            origin: document.origin().immutable().clone(),
             pipeline_id: Some(self.elem.global().pipeline_id()),
             referrer_url: Some(document.url()),
             referrer_policy: referrer_policy,
             integrity_metadata: integrity_metadata,
             .. RequestInit::default()
         };
 
         document.fetch_async(LoadType::Stylesheet(url), request, action_sender);
--- a/servo/components/style/gecko/conversions.rs
+++ b/servo/components/style/gecko/conversions.rs
@@ -13,21 +13,20 @@ use gecko::values::{convert_rgba_to_nsco
 use gecko_bindings::bindings::{Gecko_CreateGradient, Gecko_SetGradientImageValue, Gecko_SetLayerImageImageValue};
 use gecko_bindings::bindings::{Gecko_InitializeImageCropRect, Gecko_SetImageElement};
 use gecko_bindings::structs::{nsCSSUnit, nsStyleCoord_CalcValue, nsStyleImage};
 use gecko_bindings::structs::{nsresult, SheetType};
 use gecko_bindings::sugar::ns_style_coord::{CoordDataValue, CoordData, CoordDataMut};
 use std::f32::consts::PI;
 use stylesheets::{Origin, RulesMutateError};
 use values::computed::{Angle, CalcLengthOrPercentage, Gradient, Image};
-use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto};
+use values::computed::{LengthOrPercentage, LengthOrPercentageOrAuto, Percentage};
 use values::generics::grid::TrackSize;
 use values::generics::image::{CompatMode, Image as GenericImage, GradientItem};
 use values::generics::rect::Rect;
-use values::specified::length::Percentage;
 use values::specified::url::SpecifiedUrl;
 
 impl From<CalcLengthOrPercentage> for nsStyleCoord_CalcValue {
     fn from(other: CalcLengthOrPercentage) -> nsStyleCoord_CalcValue {
         let has_percentage = other.percentage.is_some();
         nsStyleCoord_CalcValue {
             mLength: other.unclamped_length().0,
             mPercent: other.percentage.map_or(0., |p| p.0),
--- a/servo/components/style/gecko/values.rs
+++ b/servo/components/style/gecko/values.rs
@@ -13,23 +13,22 @@ use gecko_bindings::structs::{CounterSty
 use gecko_bindings::structs::{StyleGridTrackBreadth, StyleShapeRadius};
 use gecko_bindings::sugar::ns_style_coord::{CoordData, CoordDataMut, CoordDataValue};
 use media_queries::Device;
 use nsstring::{nsACString, nsCString};
 use std::cmp::max;
 use values::{Auto, Either, ExtremumLength, None_, Normal};
 use values::computed::{Angle, LengthOrPercentage, LengthOrPercentageOrAuto};
 use values::computed::{LengthOrPercentageOrNone, Number, NumberOrPercentage};
-use values::computed::{MaxLength, MozLength};
+use values::computed::{MaxLength, MozLength, Percentage};
 use values::computed::basic_shape::ShapeRadius as ComputedShapeRadius;
 use values::generics::CounterStyleOrNone;
 use values::generics::basic_shape::ShapeRadius;
 use values::generics::gecko::ScrollSnapPoint;
 use values::generics::grid::{TrackBreadth, TrackKeyword};
-use values::specified::Percentage;
 
 /// A trait that defines an interface to convert from and to `nsStyleCoord`s.
 pub trait GeckoStyleCoordConvertible : Sized {
     /// Convert this to a `nsStyleCoord`.
     fn to_gecko_style_coord<T: CoordDataMut>(&self, coord: &mut T);
     /// Given a `nsStyleCoord`, try to get a value of this type..
     fn from_gecko_style_coord<T: CoordData>(coord: &T) -> Option<Self>;
 }
--- a/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
+++ b/servo/components/style/gecko_bindings/sugar/ns_css_value.rs
@@ -9,18 +9,17 @@ use gecko_bindings::bindings;
 use gecko_bindings::structs;
 use gecko_bindings::structs::{nsCSSValue, nsCSSUnit};
 use gecko_bindings::structs::{nsCSSValue_Array, nsCSSValueList, nscolor};
 use gecko_string_cache::Atom;
 use std::marker::PhantomData;
 use std::mem;
 use std::ops::{Index, IndexMut};
 use std::slice;
-use values::computed::{Angle, LengthOrPercentage};
-use values::specified::length::Percentage;
+use values::computed::{Angle, LengthOrPercentage, Percentage};
 use values::specified::url::SpecifiedUrl;
 
 impl nsCSSValue {
     /// Create a CSSValue with null unit, useful to be used as a return value.
     #[inline]
     pub fn null() -> Self {
         unsafe { mem::zeroed() }
     }
--- a/servo/components/style/properties/declaration_block.rs
+++ b/servo/components/style/properties/declaration_block.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! A property declaration block.
 
 #![deny(missing_docs)]
 
 use context::QuirksMode;
 use cssparser::{DeclarationListParser, parse_important, ParserInput, CompactCowStr};
-use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter};
+use cssparser::{Parser, AtRuleParser, DeclarationParser, Delimiter, ParseError as CssParseError};
 use error_reporting::{ParseErrorReporter, ContextualParseError};
 use parser::{ParserContext, log_css_error};
 use properties::animated_properties::AnimationValue;
 use selectors::parser::SelectorParseError;
 use shared_lock::Locked;
 use smallvec::SmallVec;
 use std::fmt;
 use std::slice::Iter;
@@ -930,23 +930,38 @@ struct PropertyDeclarationParser<'a, 'b:
 
 /// Default methods reject all at rules.
 impl<'a, 'b, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Prelude = ();
     type AtRule = Importance;
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 }
 
+/// Based on NonMozillaVendorIdentifier from Gecko's CSS parser.
+fn is_non_mozilla_vendor_identifier(name: &str) -> bool {
+    (name.starts_with("-") && !name.starts_with("-moz-")) ||
+        name.starts_with("_")
+}
+
 impl<'a, 'b, 'i> DeclarationParser<'i> for PropertyDeclarationParser<'a, 'b> {
     type Declaration = Importance;
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
     fn parse_value<'t>(&mut self, name: CompactCowStr<'i>, input: &mut Parser<'i, 't>)
                        -> Result<Importance, ParseError<'i>> {
-        let id = PropertyId::parse(name)?;
+        let id = match PropertyId::parse(&name) {
+            Ok(id) => id,
+            Err(()) => {
+                return Err(if is_non_mozilla_vendor_identifier(&name) {
+                    PropertyDeclarationParseError::UnknownVendorProperty
+                } else {
+                    PropertyDeclarationParseError::UnknownProperty(name)
+                }.into());
+            }
+        };
         input.parse_until_before(Delimiter::Bang, |input| {
             PropertyDeclaration::parse_into(self.declarations, id, self.context, input)
                 .map_err(|e| e.into())
         })?;
         let importance = match input.try(parse_important) {
             Ok(()) => Importance::Important,
             Err(_) => Importance::Normal,
         };
@@ -971,16 +986,25 @@ pub fn parse_property_declaration_list(c
     let mut iter = DeclarationListParser::new(input, parser);
     while let Some(declaration) = iter.next() {
         match declaration {
             Ok(importance) => {
                 block.extend(iter.parser.declarations.drain(), importance);
             }
             Err(err) => {
                 iter.parser.declarations.clear();
+
+                // If the unrecognized property looks like a vendor-specific property,
+                // silently ignore it instead of polluting the error output.
+                if let CssParseError::Custom(SelectorParseError::Custom(
+                    StyleParseError::PropertyDeclaration(
+                        PropertyDeclarationParseError::UnknownVendorProperty))) = err.error {
+                    continue;
+                }
+
                 let pos = err.span.start;
                 let error = ContextualParseError::UnsupportedPropertyDeclaration(
                     iter.input.slice(err.span), err.error);
                 log_css_error(iter.input, pos, error, &context);
             }
         }
     }
     block
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -61,17 +61,17 @@ use rule_tree::StrongRuleNode;
 use std::fmt::{self, Debug};
 use std::mem::{forget, transmute, zeroed};
 use std::ptr;
 use stylearc::Arc;
 use std::cmp;
 use values::{Auto, CustomIdent, Either, KeyframesName};
 use values::computed::ToComputedValue;
 use values::computed::effects::{BoxShadow, Filter, SimpleShadow};
-use values::specified::length::Percentage;
+use values::computed::length::Percentage;
 use computed_values::border_style;
 
 pub mod style_structs {
     % for style_struct in data.style_structs:
     pub use super::${style_struct.gecko_struct_name} as ${style_struct.name};
     % endfor
 }
 
@@ -2866,17 +2866,17 @@ fn static_assert() {
                     } else if feature.0 == atom!("transform") {
                         self.gecko.mWillChangeBitField |= NS_STYLE_WILL_CHANGE_TRANSFORM as u8;
                     }
 
                     unsafe {
                         Gecko_AppendWillChange(&mut self.gecko, feature.0.as_ptr());
                     }
 
-                    if let Ok(prop_id) = PropertyId::parse(feature.0.to_string().into()) {
+                    if let Ok(prop_id) = PropertyId::parse(&feature.0.to_string()) {
                         match prop_id.as_shorthand() {
                             Ok(shorthand) => {
                                 for longhand in shorthand.longhands() {
                                     self.gecko.mWillChangeBitField |=
                                         will_change_bitfield_from_prop_flags(longhand);
                                 }
                             },
                             Err(longhand_or_custom) => {
--- a/servo/components/style/properties/helpers/animated_properties.mako.rs
+++ b/servo/components/style/properties/helpers/animated_properties.mako.rs
@@ -36,22 +36,21 @@ use values::{CSSFloat, CustomIdent, Eith
 use values::animated::ToAnimatedValue;
 use values::animated::effects::BoxShadowList as AnimatedBoxShadowList;
 use values::animated::effects::Filter as AnimatedFilter;
 use values::animated::effects::FilterList as AnimatedFilterList;
 use values::animated::effects::TextShadowList as AnimatedTextShadowList;
 use values::computed::{Angle, LengthOrPercentageOrAuto, LengthOrPercentageOrNone};
 use values::computed::{BorderCornerRadius, ClipRect};
 use values::computed::{CalcLengthOrPercentage, Color, Context, ComputedValueAsSpecified};
-use values::computed::{LengthOrPercentage, MaxLength, MozLength, ToComputedValue};
+use values::computed::{LengthOrPercentage, MaxLength, MozLength, Percentage, ToComputedValue};
 use values::generics::{SVGPaint, SVGPaintKind};
 use values::generics::border::BorderCornerRadius as GenericBorderCornerRadius;
 use values::generics::effects::Filter;
 use values::generics::position as generic_position;
-use values::specified::length::Percentage;
 
 
 /// A longhand property whose animation type is not "none".
 ///
 /// NOTE: This includes the 'display' property since it is animatable from SMIL even though it is
 /// not animatable from CSS animations or Web Animations. CSS transitions also does not allow
 /// animating 'display', but for CSS transitions we have the separate TransitionProperty type.
 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
@@ -1681,17 +1680,16 @@ fn add_weighted_transform_lists(from_lis
                 }
                 _ => {
                     // This should be unreachable due to the can_interpolate_list() call.
                     unreachable!();
                 }
             }
         }
     } else {
-        use values::specified::Percentage;
         let from_transform_list = TransformList(Some(from_list.to_vec()));
         let to_transform_list = TransformList(Some(to_list.to_vec()));
         result.push(
             TransformOperation::InterpolateMatrix { from_list: from_transform_list,
                                                     to_list: to_transform_list,
                                                     progress: Percentage(other_portion as f32) });
     }
 
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -273,17 +273,17 @@
     initial_value="computed::BorderImageSideWidth::one().into()",
     initial_specified_value="specified::BorderImageSideWidth::one().into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-width",
     animation_value_type="discrete",
     boxed=True)}
 
 ${helpers.predefined_type("border-image-slice", "BorderImageSlice",
     initial_value="computed::NumberOrPercentage::Percentage(computed::Percentage(1.)).into()",
-    initial_specified_value="specified::NumberOrPercentage::Percentage(specified::Percentage(1.)).into()",
+    initial_specified_value="specified::NumberOrPercentage::Percentage(specified::Percentage::new(1.)).into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-slice",
     animation_value_type="discrete",
     boxed=True)}
 
 #[cfg(feature = "gecko")]
 impl ::values::computed::BorderImageWidth {
     pub fn to_gecko_rect(&self, sides: &mut ::gecko_bindings::structs::nsStyleSides) {
         use gecko_bindings::sugar::ns_style_coord::{CoordDataMut, CoordDataValue};
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -731,28 +731,28 @@
 <%helpers:longhand name="transform" extra_prefixes="webkit"
                    animation_value_type="ComputedValue"
                    flags="CREATES_STACKING_CONTEXT FIXPOS_CB"
                    spec="https://drafts.csswg.org/css-transforms/#propdef-transform">
     use app_units::Au;
     use values::computed::{LengthOrPercentageOrNumber as ComputedLoPoNumber, LengthOrNumber as ComputedLoN};
     use values::computed::{LengthOrPercentage as ComputedLoP, Length as ComputedLength};
     use values::generics::transform::Matrix;
-    use values::specified::{Angle, Integer, Length, LengthOrPercentage, Percentage};
+    use values::specified::{Angle, Integer, Length, LengthOrPercentage};
     use values::specified::{LengthOrNumber, LengthOrPercentageOrNumber as LoPoNumber, Number};
     use style_traits::ToCss;
     use style_traits::values::Css;
 
     use std::fmt;
 
     pub mod computed_value {
         use app_units::Au;
         use values::CSSFloat;
         use values::computed;
-        use values::computed::{Length, LengthOrPercentage, Percentage};
+        use values::computed::{Length, LengthOrPercentage};
 
         #[derive(Clone, Copy, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub struct ComputedMatrix {
             pub m11: CSSFloat, pub m12: CSSFloat, pub m13: CSSFloat, pub m14: CSSFloat,
             pub m21: CSSFloat, pub m22: CSSFloat, pub m23: CSSFloat, pub m24: CSSFloat,
             pub m31: CSSFloat, pub m32: CSSFloat, pub m33: CSSFloat, pub m34: CSSFloat,
             pub m41: CSSFloat, pub m42: CSSFloat, pub m43: CSSFloat, pub m44: CSSFloat,
@@ -812,17 +812,17 @@
             //                                    Scale(...) ],
             //                       to_list: [ AccumulateMatrix { from_list: ...,
             //                                                     to_list: [ InterpolateMatrix,
             //                                                                 ... ],
             //                                                     count: ... } ],
             //                       progress: ... } ]
             InterpolateMatrix { from_list: T,
                                 to_list: T,
-                                progress: Percentage },
+                                progress: computed::Percentage },
             // For accumulate operation of mismatched transform lists.
             AccumulateMatrix { from_list: T,
                                to_list: T,
                                count: computed::Integer },
         }
 
         #[derive(Clone, Debug, PartialEq)]
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
@@ -899,17 +899,17 @@
         /// Part of CSS Transform Module Level 2 and defined at
         /// [§ 13.1. 3D Transform Function](https://drafts.csswg.org/css-transforms-2/#funcdef-perspective).
         ///
         /// The value must be greater than or equal to zero.
         Perspective(specified::Length),
         /// A intermediate type for interpolation of mismatched transform lists.
         InterpolateMatrix { from_list: SpecifiedValue,
                             to_list: SpecifiedValue,
-                            progress: Percentage },
+                            progress: computed::Percentage },
         /// A intermediate type for accumulation of mismatched transform lists.
         AccumulateMatrix { from_list: SpecifiedValue,
                            to_list: SpecifiedValue,
                            count: Integer },
     }
 
     impl ToCss for computed_value::T {
         fn to_css<W>(&self, _: &mut W) -> fmt::Result where W: fmt::Write {
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -561,18 +561,17 @@ macro_rules! impl_gecko_keyword_conversi
 <%helpers:longhand name="font-size" need_clone="True" animation_value_type="ComputedValue"
                    allow_quirks="True" spec="https://drafts.csswg.org/css-fonts/#propdef-font-size">
     use app_units::Au;
     use properties::longhands::system_font::SystemFont;
     use properties::style_structs::Font;
     use std::fmt;
     use style_traits::{HasViewportPercentage, ToCss};
     use values::FONT_MEDIUM_PX;
-    use values::specified::{AllowQuirks, FontRelativeLength, LengthOrPercentage};
-    use values::specified::{NoCalcLength, Percentage};
+    use values::specified::{AllowQuirks, FontRelativeLength, LengthOrPercentage, NoCalcLength};
     use values::specified::length::FontBaseSize;
 
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 SpecifiedValue::Length(ref lop) => lop.to_css(dest),
                 SpecifiedValue::Keyword(kw, _) => kw.to_css(dest),
                 SpecifiedValue::Smaller => dest.write_str("smaller"),
@@ -805,18 +804,18 @@ macro_rules! impl_gecko_keyword_conversi
                 }
                 SpecifiedValue::Length(LengthOrPercentage::Length(
                         NoCalcLength::ServoCharacterWidth(value))) => {
                     value.to_computed_value(base_size.resolve(context))
                 }
                 SpecifiedValue::Length(LengthOrPercentage::Length(ref l)) => {
                     l.to_computed_value(context)
                 }
-                SpecifiedValue::Length(LengthOrPercentage::Percentage(Percentage(value))) => {
-                    base_size.resolve(context).scale_by(value)
+                SpecifiedValue::Length(LengthOrPercentage::Percentage(pc)) => {
+                    base_size.resolve(context).scale_by(pc.0)
                 }
                 SpecifiedValue::Length(LengthOrPercentage::Calc(ref calc)) => {
                     let calc = calc.to_computed_value(context);
                     calc.to_used_value(Some(base_size.resolve(context))).unwrap()
                 }
                 SpecifiedValue::Keyword(ref key, fraction) => {
                     key.to_computed_value(context).scale_by(fraction)
                 }
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -15,17 +15,17 @@ use std::collections::HashSet;
 use std::fmt;
 use std::mem;
 use std::ops::Deref;
 use stylearc::{Arc, UniqueArc};
 
 use app_units::Au;
 #[cfg(feature = "servo")] use cssparser::RGBA;
 use cssparser::{Parser, TokenSerializationType, serialize_identifier};
-use cssparser::{ParserInput, CompactCowStr};
+use cssparser::ParserInput;
 #[cfg(feature = "servo")] use euclid::SideOffsets2D;
 use computed_values;
 use context::QuirksMode;
 use error_reporting::NullReporter;
 use font_metrics::FontMetricsProvider;
 #[cfg(feature = "gecko")] use gecko_bindings::bindings;
 #[cfg(feature = "gecko")] use gecko_bindings::structs::{self, nsCSSPropertyID};
 #[cfg(feature = "servo")] use logical_geometry::{LogicalMargin, PhysicalSide};
@@ -505,17 +505,17 @@ impl LonghandId {
     fn parse_value<'i, 't>(&self, context: &ParserContext, input: &mut Parser<'i, 't>)
                            -> Result<PropertyDeclaration, ParseError<'i>> {
         match *self {
             % for property in data.longhands:
                 LonghandId::${property.camel_case} => {
                     % if not property.derived_from:
                         longhands::${property.ident}::parse_declared(context, input)
                     % else:
-                        Err(PropertyDeclarationParseError::UnknownProperty.into())
+                        Err(PropertyDeclarationParseError::UnknownProperty("${property.ident}".into()).into())
                     % endif
                 }
             % endfor
         }
     }
 
     /// If this is a logical property, return the corresponding physical one in the given writing mode.
     /// Otherwise, return unchanged.
@@ -987,18 +987,18 @@ impl ToCss for PropertyId {
         }
     }
 }
 
 impl PropertyId {
     /// Returns a given property from the string `s`.
     ///
     /// Returns Err(()) for unknown non-custom properties
-    pub fn parse<'i>(property_name: CompactCowStr<'i>) -> Result<Self, ParseError<'i>> {
-        if let Ok(name) = ::custom_properties::parse_name(&property_name) {
+    pub fn parse(property_name: &str) -> Result<Self, ()> {
+        if let Ok(name) = ::custom_properties::parse_name(property_name) {
             return Ok(PropertyId::Custom(::custom_properties::Name::from(name)))
         }
 
         // FIXME(https://github.com/rust-lang/rust/issues/33156): remove this enum and use PropertyId
         // when stable Rust allows destructors in statics.
         pub enum StaticId {
             Longhand(LonghandId),
             Shorthand(ShorthandId),
@@ -1009,20 +1009,20 @@ impl PropertyId {
                     % for property in properties:
                         % for name in [property.name] + property.alias:
                             "${name}" => StaticId::${kind}(${kind}Id::${property.camel_case}),
                         % endfor
                     % endfor
                 % endfor
             }
         }
-        match static_id(&property_name) {
+        match static_id(property_name) {
             Some(&StaticId::Longhand(id)) => Ok(PropertyId::Longhand(id)),
             Some(&StaticId::Shorthand(id)) => Ok(PropertyId::Shorthand(id)),
-            None => Err(StyleParseError::UnknownProperty(property_name).into()),
+            None => Err(()),
         }
     }
 
     /// Returns a property id from Gecko's nsCSSPropertyID.
     #[cfg(feature = "gecko")]
     #[allow(non_upper_case_globals)]
     pub fn from_nscsspropertyid(id: nsCSSPropertyID) -> Result<Self, ()> {
         use gecko_bindings::structs::*;
@@ -1096,17 +1096,17 @@ impl PropertyId {
                 let mut s = String::new();
                 write!(&mut s, "--{}", name).unwrap();
                 s.into()
             }
         }
     }
 
     fn check_allowed_in(&self, rule_type: CssRuleType, stylesheet_origin: Origin)
-                        -> Result<(), PropertyDeclarationParseError> {
+                        -> Result<(), PropertyDeclarationParseError<'static>> {
         let id: NonCustomPropertyId;
         match *self {
             // Custom properties are allowed everywhere
             PropertyId::Custom(_) => return Ok(()),
 
             PropertyId::Shorthand(shorthand_id) => id = shorthand_id.into(),
             PropertyId::Longhand(longhand_id) => id = longhand_id.into(),
         }
@@ -1181,17 +1181,17 @@ impl PropertyId {
 
         if INTERNAL.contains(id) {
             if stylesheet_origin != Origin::UserAgent {
                 if EXPERIMENTAL.contains(id) {
                     if !passes_pref_check() {
                         return Err(PropertyDeclarationParseError::ExperimentalProperty);
                     }
                 } else {
-                    return Err(PropertyDeclarationParseError::UnknownProperty);
+                    return Err(PropertyDeclarationParseError::UnknownProperty(self.name().into()));
                 }
             }
         } else {
             if EXPERIMENTAL.contains(id) && !passes_pref_check() {
                 return Err(PropertyDeclarationParseError::ExperimentalProperty);
             }
         }
 
@@ -1457,19 +1457,19 @@ impl PropertyDeclaration {
     /// https://drafts.csswg.org/css-animations/#keyframes
     /// > The <declaration-list> inside of <keyframe-block> accepts any CSS property
     /// > except those defined in this specification,
     /// > but does accept the `animation-play-state` property and interprets it specially.
     ///
     /// This will not actually parse Importance values, and will always set things
     /// to Importance::Normal. Parsing Importance values is the job of PropertyDeclarationParser,
     /// we only set them here so that we don't have to reallocate
-    pub fn parse_into(declarations: &mut SourcePropertyDeclaration,
-                      id: PropertyId, context: &ParserContext, input: &mut Parser)
-                      -> Result<(), PropertyDeclarationParseError> {
+    pub fn parse_into<'i, 't>(declarations: &mut SourcePropertyDeclaration,
+                              id: PropertyId, context: &ParserContext, input: &mut Parser<'i, 't>)
+                              -> Result<(), PropertyDeclarationParseError<'i>> {
         assert!(declarations.is_empty());
         let rule_type = context.rule_type();
         debug_assert!(rule_type == CssRuleType::Keyframe ||
                       rule_type == CssRuleType::Page ||
                       rule_type == CssRuleType::Style,
                       "Declarations are only expected inside a keyframe, page, or style rule.");
         id.check_allowed_in(rule_type, context.stylesheet_origin)?;
         match id {
--- a/servo/components/style/stylesheets/keyframes_rule.rs
+++ b/servo/components/style/stylesheets/keyframes_rule.rs
@@ -12,16 +12,17 @@ use properties::{Importance, PropertyDec
 use properties::{PropertyDeclarationId, LonghandId, SourcePropertyDeclaration};
 use properties::LonghandIdSet;
 use properties::animated_properties::AnimatableLonghand;
 use properties::longhands::transition_timing_function::single_value::SpecifiedValue as SpecifiedTimingFunction;
 use selectors::parser::SelectorParseError;
 use shared_lock::{DeepCloneParams, DeepCloneWithLock, SharedRwLock, SharedRwLockReadGuard, Locked, ToCssWithGuard};
 use std::fmt;
 use style_traits::{PARSING_MODE_DEFAULT, ToCss, ParseError, StyleParseError};
+use style_traits::PropertyDeclarationParseError;
 use stylearc::Arc;
 use stylesheets::{CssRuleType, StylesheetContents};
 use stylesheets::rule_parser::VendorPrefix;
 use values::KeyframesName;
 
 /// A [`@keyframes`][keyframes] rule.
 ///
 /// [keyframes]: https://drafts.csswg.org/css-animations/#keyframes
@@ -527,17 +528,18 @@ impl<'a, 'b, 'i> AtRuleParser<'i> for Ke
 }
 
 impl<'a, 'b, 'i> DeclarationParser<'i> for KeyframeDeclarationParser<'a, 'b> {
     type Declaration = ();
     type Error = SelectorParseError<'i, StyleParseError<'i>>;
 
     fn parse_value<'t>(&mut self, name: CompactCowStr<'i>, input: &mut Parser<'i, 't>)
                        -> Result<(), ParseError<'i>> {
-        let id = PropertyId::parse(name.into())?;
+        let id = PropertyId::parse(&name)
+            .map_err(|()| PropertyDeclarationParseError::UnknownProperty(name))?;
         match PropertyDeclaration::parse_into(self.declarations, id, self.context, input) {
             Ok(()) => {
                 // In case there is still unparsed text in the declaration, we should roll back.
                 input.expect_exhausted().map_err(|e| e.into())
             }
             Err(_e) => Err(StyleParseError::UnspecifiedError.into())
         }
     }
--- a/servo/components/style/values/computed/length.rs
+++ b/servo/components/style/values/computed/length.rs
@@ -7,21 +7,62 @@
 use app_units::{Au, AU_PER_PX};
 use ordered_float::NotNaN;
 use std::fmt;
 use style_traits::ToCss;
 use style_traits::values::specified::AllowedLengthType;
 use super::{Number, ToComputedValue, Context};
 use values::{Auto, CSSFloat, Either, ExtremumLength, None_, Normal, specified};
 use values::specified::length::{AbsoluteLength, FontBaseSize, FontRelativeLength};
-use values::specified::length::{Percentage, ViewportPercentageLength};
+use values::specified::length::ViewportPercentageLength;
 
 pub use super::image::Image;
 pub use values::specified::{Angle, BorderStyle, Time, UrlOrNone};
 
+/// A computed `<percentage>` value.
+///
+/// FIXME(emilio): why is this in length.rs?
+#[derive(Clone, Copy, Debug, Default, PartialEq, HasViewportPercentage)]
+#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
+pub struct Percentage(pub CSSFloat);
+
+impl Percentage {
+    /// 0%
+    #[inline]
+    pub fn zero() -> Self {
+        Percentage(0.)
+    }
+
+    /// 100%
+    #[inline]
+    pub fn hundred() -> Self {
+        Percentage(1.)
+    }
+}
+
+impl ToCss for Percentage {
+    fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
+        write!(dest, "{}%", self.0 * 100.)
+    }
+}
+
+impl ToComputedValue for specified::Percentage {
+    type ComputedValue = Percentage;
+
+    #[inline]
+    fn to_computed_value(&self, _: &Context) -> Percentage {
+        Percentage(self.get())
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Percentage) -> Self {
+        specified::Percentage::new(computed.0)
+    }
+}
+
 impl ToComputedValue for specified::NoCalcLength {
     type ComputedValue = Au;
 
     #[inline]
     fn to_computed_value(&self, context: &Context) -> Au {
         match *self {
             specified::NoCalcLength::Absolute(length) =>
                 length.to_computed_value(context),
--- a/servo/components/style/values/computed/mod.rs
+++ b/servo/components/style/values/computed/mod.rs
@@ -33,21 +33,21 @@ pub use self::effects::{BoxShadow, Filte
 pub use self::flex::FlexBasis;
 pub use self::image::{Gradient, GradientItem, Image, ImageLayer, LineDirection, MozImageRect};
 #[cfg(feature = "gecko")]
 pub use self::gecko::ScrollSnapPoint;
 pub use self::rect::LengthOrNumberRect;
 pub use super::{Auto, Either, None_};
 #[cfg(feature = "gecko")]
 pub use super::specified::{AlignItems, AlignJustifyContent, AlignJustifySelf, JustifyItems};
-pub use super::specified::{BorderStyle, Percentage, UrlOrNone};
+pub use super::specified::{BorderStyle, UrlOrNone};
 pub use super::generics::grid::GridLine;
 pub use super::specified::url::SpecifiedUrl;
 pub use self::length::{CalcLengthOrPercentage, Length, LengthOrNone, LengthOrNumber, LengthOrPercentage};
-pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength};
+pub use self::length::{LengthOrPercentageOrAuto, LengthOrPercentageOrNone, MaxLength, MozLength, Percentage};
 pub use self::position::Position;
 pub use self::text::{InitialLetter, LetterSpacing, LineHeight, WordSpacing};
 pub use self::transform::{TimingFunction, TransformOrigin};
 
 pub mod background;
 pub mod basic_shape;
 pub mod border;
 pub mod color;
--- a/servo/components/style/values/computed/position.rs
+++ b/servo/components/style/values/computed/position.rs
@@ -4,19 +4,18 @@
 
 //! CSS handling for the computed value of
 //! [`position`][position] values.
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
 use std::fmt;
 use style_traits::ToCss;
-use values::computed::LengthOrPercentage;
+use values::computed::{LengthOrPercentage, Percentage};
 use values::generics::position::Position as GenericPosition;
-use values::specified::length::Percentage;
 
 /// The computed value of a CSS `<position>`
 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
 
 /// The computed value of a CSS horizontal position.
 pub type HorizontalPosition = LengthOrPercentage;
 
 /// The computed value of a CSS vertical position.
--- a/servo/components/style/values/computed/transform.rs
+++ b/servo/components/style/values/computed/transform.rs
@@ -1,19 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 //! Computed types for CSS values that are related to transformations.
 
 use properties::animated_properties::Animatable;
-use values::computed::{Length, LengthOrPercentage, Number};
+use values::computed::{Length, LengthOrPercentage, Number, Percentage};
 use values::generics::transform::TimingFunction as GenericTimingFunction;
 use values::generics::transform::TransformOrigin as GenericTransformOrigin;
-use values::specified::length::Percentage;
 
 /// The computed value of a CSS `<transform-origin>`
 pub type TransformOrigin = GenericTransformOrigin<LengthOrPercentage, LengthOrPercentage, Length>;
 
 /// A computed timing function.
 pub type TimingFunction = GenericTimingFunction<u32, Number>;
 
 impl TransformOrigin {
--- a/servo/components/style/values/generics/flex.rs
+++ b/servo/components/style/values/generics/flex.rs
@@ -1,15 +1,15 @@
 /* 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/. */
 
 //! Generic types for CSS values related to flexbox.
 
-use values::specified::Percentage;
+use values::computed::Percentage;
 
 /// A generic value for the `flex-basis` property.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, HasViewportPercentage, PartialEq, ToComputedValue, ToCss)]
 pub enum FlexBasis<LengthOrPercentage> {
     /// `auto`
     Auto,
     /// `content`
--- a/servo/components/style/values/specified/basic_shape.rs
+++ b/servo/components/style/values/specified/basic_shape.rs
@@ -7,24 +7,25 @@
 //!
 //! [basic-shape]: https://drafts.csswg.org/css-shapes/#typedef-basic-shape
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::borrow::Cow;
 use std::fmt;
 use style_traits::{ToCss, ParseError, StyleParseError};
+use values::computed::Percentage;
 use values::generics::basic_shape::{Circle as GenericCircle};
 use values::generics::basic_shape::{ClippingShape as GenericClippingShape, Ellipse as GenericEllipse};
 use values::generics::basic_shape::{FillRule, BasicShape as GenericBasicShape};
 use values::generics::basic_shape::{FloatAreaShape as GenericFloatAreaShape, InsetRect as GenericInsetRect};
 use values::generics::basic_shape::{GeometryBox, ShapeBox, ShapeSource};
 use values::generics::basic_shape::{Polygon as GenericPolygon, ShapeRadius as GenericShapeRadius};
 use values::generics::rect::Rect;
-use values::specified::{LengthOrPercentage, Percentage};
+use values::specified::LengthOrPercentage;
 use values::specified::border::BorderRadius;
 use values::specified::position::{HorizontalPosition, Position, PositionComponent, Side, VerticalPosition};
 use values::specified::url::SpecifiedUrl;
 
 /// A specified clipping shape.
 pub type ClippingShape = GenericClippingShape<BasicShape>;
 
 /// A specified float area shape.
--- a/servo/components/style/values/specified/calc.rs
+++ b/servo/components/style/values/specified/calc.rs
@@ -9,19 +9,19 @@
 use app_units::Au;
 use cssparser::{Parser, Token, BasicParseError};
 use parser::ParserContext;
 use std::ascii::AsciiExt;
 use std::fmt;
 use style_traits::{HasViewportPercentage, ToCss, ParseError, StyleParseError};
 use style_traits::values::specified::AllowedLengthType;
 use values::{CSSInteger, CSSFloat};
+use values::computed;
 use values::specified::{Angle, Time};
-use values::specified::length::{FontRelativeLength, NoCalcLength};
-use values::specified::length::{Percentage, ViewportPercentageLength};
+use values::specified::length::{FontRelativeLength, NoCalcLength, ViewportPercentageLength};
 
 /// A node inside a `Calc` expression's AST.
 #[derive(Clone, Debug)]
 pub enum CalcNode {
     /// `<length>`
     Length(NoCalcLength),
     /// `<angle>`
     Angle(Angle),
@@ -47,16 +47,18 @@ pub enum CalcNode {
 #[derive(Clone, Copy, PartialEq)]
 pub enum CalcUnit {
     /// `<number>`
     Number,
     /// `<integer>`
     Integer,
     /// `<length>`
     Length,
+    /// `<percentage>`
+    Percentage,
     /// `<length> | <percentage>`
     LengthOrPercentage,
     /// `<angle>`
     Angle,
     /// `<time>`
     Time,
 }
 
@@ -70,17 +72,17 @@ pub struct CalcLengthOrPercentage {
     pub vw: Option<CSSFloat>,
     pub vh: Option<CSSFloat>,
     pub vmin: Option<CSSFloat>,
     pub vmax: Option<CSSFloat>,
     pub em: Option<CSSFloat>,
     pub ex: Option<CSSFloat>,
     pub ch: Option<CSSFloat>,
     pub rem: Option<CSSFloat>,
-    pub percentage: Option<Percentage>,
+    pub percentage: Option<computed::Percentage>,
     #[cfg(feature = "gecko")]
     pub mozmm: Option<CSSFloat>,
 }
 
 impl HasViewportPercentage for CalcLengthOrPercentage {
     fn has_viewport_percentage(&self) -> bool {
         self.vw.is_some() || self.vh.is_some() ||
         self.vmin.is_some() || self.vmax.is_some()
@@ -141,19 +143,18 @@ impl CalcNode {
     /// `<length>`, `<angle>`, `<time>`, `<percentage>`, according to
     /// `expected_unit`.
     ///
     /// May return a "complex" `CalcNode`, in the presence of a parenthesized
     /// expression, for example.
     fn parse_one<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
-        expected_unit: CalcUnit)
-        -> Result<Self, ParseError<'i>>
-    {
+        expected_unit: CalcUnit
+    ) -> Result<Self, ParseError<'i>> {
         match (input.next()?, expected_unit) {
             (Token::Number { value, .. }, _) => Ok(CalcNode::Number(value)),
             (Token::Dimension { value, ref unit, .. }, CalcUnit::Length) |
             (Token::Dimension { value, ref unit, .. }, CalcUnit::LengthOrPercentage) => {
                 NoCalcLength::parse_dimension(context, value, unit)
                     .map(CalcNode::Length)
                     .map_err(|()| StyleParseError::UnspecifiedError.into())
             }
@@ -162,17 +163,18 @@ impl CalcNode {
                     .map(CalcNode::Angle)
                     .map_err(|()| StyleParseError::UnspecifiedError.into())
             }
             (Token::Dimension { value, ref unit, .. }, CalcUnit::Time) => {
                 Time::parse_dimension(value, unit, /* from_calc = */ true)
                     .map(CalcNode::Time)
                     .map_err(|()| StyleParseError::UnspecifiedError.into())
             }
-            (Token::Percentage { unit_value, .. }, CalcUnit::LengthOrPercentage) => {
+            (Token::Percentage { unit_value, .. }, CalcUnit::LengthOrPercentage) |
+            (Token::Percentage { unit_value, .. }, CalcUnit::Percentage) => {
                 Ok(CalcNode::Percentage(unit_value))
             }
             (Token::ParenthesisBlock, _) => {
                 input.parse_nested_block(|i| {
                     CalcNode::parse(context, i, expected_unit)
                 })
             }
             (Token::Function(ref name), _) if name.eq_ignore_ascii_case("calc") => {
@@ -278,29 +280,67 @@ impl CalcNode {
         let mut ret = CalcLengthOrPercentage {
             clamping_mode: clamping_mode,
             .. Default::default()
         };
         self.add_length_or_percentage_to(&mut ret, 1.0)?;
         Ok(ret)
     }
 
+    /// Tries to simplify this expression into a `<percentage>` value.
+    fn to_percentage(&self) -> Result<CSSFloat, ()> {
+        Ok(match *self {
+            CalcNode::Percentage(percentage) => percentage,
+            CalcNode::Sub(ref a, ref b) => {
+                a.to_percentage()? - b.to_percentage()?
+            }
+            CalcNode::Sum(ref a, ref b) => {
+                a.to_percentage()? + b.to_percentage()?
+            }
+            CalcNode::Mul(ref a, ref b) => {
+                match a.to_percentage() {
+                    Ok(lhs) => {
+                        let rhs = b.to_number()?;
+                        lhs * rhs
+                    }
+                    Err(..) => {
+                        let lhs = a.to_number()?;
+                        let rhs = b.to_percentage()?;
+                        lhs * rhs
+                    }
+                }
+            }
+            CalcNode::Div(ref a, ref b) => {
+                let lhs = a.to_percentage()?;
+                let rhs = b.to_number()?;
+                if rhs == 0. {
+                    return Err(())
+                }
+                lhs / rhs
+            }
+            CalcNode::Number(..) |
+            CalcNode::Length(..) |
+            CalcNode::Angle(..) |
+            CalcNode::Time(..) => return Err(()),
+        })
+    }
+
     /// Puts this `<length>` or `<percentage>` into `ret`, or error.
     ///
     /// `factor` is the sign or multiplicative factor to account for the sign
     /// (this allows adding and substracting into the return value).
     fn add_length_or_percentage_to(
         &self,
         ret: &mut CalcLengthOrPercentage,
         factor: CSSFloat)
         -> Result<(), ()>
     {
         match *self {
             CalcNode::Percentage(pct) => {
-                ret.percentage = Some(Percentage(
+                ret.percentage = Some(computed::Percentage(
                     ret.percentage.map_or(0., |p| p.0) + pct * factor,
                 ));
             }
             CalcNode::Length(ref l) => {
                 match *l {
                     NoCalcLength::Absolute(abs) => {
                         ret.absolute = Some(
                             ret.absolute.unwrap_or(Au(0)) +
@@ -490,74 +530,78 @@ impl CalcNode {
             CalcNode::Angle(..) |
             CalcNode::Time(..) => return Err(()),
         })
     }
 
     /// Convenience parsing function for integers.
     pub fn parse_integer<'i, 't>(
         context: &ParserContext,
-        input: &mut Parser<'i, 't>)
-        -> Result<CSSInteger, ParseError<'i>>
-    {
+        input: &mut Parser<'i, 't>
+    ) -> Result<CSSInteger, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::Integer)?
             .to_number()
             .map(|n| n as CSSInteger)
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 
     /// Convenience parsing function for `<length> | <percentage>`.
     pub fn parse_length_or_percentage<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
-        clamping_mode: AllowedLengthType)
-        -> Result<CalcLengthOrPercentage, ParseError<'i>>
-    {
+        clamping_mode: AllowedLengthType
+    ) -> Result<CalcLengthOrPercentage, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::LengthOrPercentage)?
             .to_length_or_percentage(clamping_mode)
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 
+    /// Convenience parsing function for percentages.
+    pub fn parse_percentage<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<CSSFloat, ParseError<'i>> {
+        Self::parse(context, input, CalcUnit::Percentage)?
+            .to_percentage()
+            .map_err(|()| StyleParseError::UnspecifiedError.into())
+    }
+
     /// Convenience parsing function for `<length>`.
     pub fn parse_length<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>,
-        clamping_mode: AllowedLengthType)
-        -> Result<CalcLengthOrPercentage, ParseError<'i>>
-    {
+        clamping_mode: AllowedLengthType
+    ) -> Result<CalcLengthOrPercentage, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::Length)?
             .to_length_or_percentage(clamping_mode)
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 
     /// Convenience parsing function for `<number>`.
     pub fn parse_number<'i, 't>(
         context: &ParserContext,
-        input: &mut Parser<'i, 't>)
-        -> Result<CSSFloat, ParseError<'i>>
-    {
+        input: &mut Parser<'i, 't>
+    ) -> Result<CSSFloat, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::Number)?
             .to_number()
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 
     /// Convenience parsing function for `<angle>`.
     pub fn parse_angle<'i, 't>(
         context: &ParserContext,
-        input: &mut Parser<'i, 't>)
-        -> Result<Angle, ParseError<'i>>
-    {
+        input: &mut Parser<'i, 't>
+    ) -> Result<Angle, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::Angle)?
             .to_angle()
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 
     /// Convenience parsing function for `<time>`.
     pub fn parse_time<'i, 't>(
         context: &ParserContext,
-        input: &mut Parser<'i, 't>)
-        -> Result<Time, ParseError<'i>>
-    {
+        input: &mut Parser<'i, 't>
+    ) -> Result<Time, ParseError<'i>> {
         Self::parse(context, input, CalcUnit::Time)?
             .to_time()
             .map_err(|()| StyleParseError::UnspecifiedError.into())
     }
 }
--- a/servo/components/style/values/specified/effects.rs
+++ b/servo/components/style/values/specified/effects.rs
@@ -1,26 +1,26 @@
 /* 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/. */
 
 //! Specified types for CSS values related to effects.
 
-use cssparser::{BasicParseError, Parser, Token};
+use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use style_traits::{ParseError, StyleParseError};
 #[cfg(not(feature = "gecko"))]
 use values::Impossible;
 use values::computed::{Context, Number as ComputedNumber, ToComputedValue};
 use values::computed::effects::BoxShadow as ComputedBoxShadow;
 use values::computed::effects::SimpleShadow as ComputedSimpleShadow;
 use values::generics::effects::BoxShadow as GenericBoxShadow;
 use values::generics::effects::Filter as GenericFilter;
 use values::generics::effects::SimpleShadow as GenericSimpleShadow;
-use values::specified::{Angle, Percentage};
+use values::specified::{Angle, NumberOrPercentage};
 use values::specified::color::Color;
 use values::specified::length::Length;
 #[cfg(feature = "gecko")]
 use values::specified::url::SpecifiedUrl;
 
 /// A specified value for a single shadow of the `box-shadow` property.
 pub type BoxShadow = GenericBoxShadow<Option<Color>, Length, Option<Length>>;
 
@@ -28,25 +28,46 @@ pub type BoxShadow = GenericBoxShadow<Op
 #[cfg(feature = "gecko")]
 pub type Filter = GenericFilter<Angle, Factor, Length, SimpleShadow>;
 
 /// A specified value for a single `filter`.
 #[cfg(not(feature = "gecko"))]
 pub type Filter = GenericFilter<Angle, Factor, Length, Impossible>;
 
 /// A value for the `<factor>` parts in `Filter`.
-///
-/// FIXME: Should be `NumberOrPercentage`, but Gecko doesn't support that yet.
+#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
-#[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
-pub enum Factor {
-    /// Literal number.
-    Number(ComputedNumber),
-    /// Literal percentage.
-    Percentage(Percentage),
+pub struct Factor(NumberOrPercentage);
+
+impl Parse for Factor {
+    #[inline]
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
+        NumberOrPercentage::parse_non_negative(context, input).map(Factor)
+    }
+}
+
+impl ToComputedValue for Factor {
+    type ComputedValue = ComputedNumber;
+
+    #[inline]
+    fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
+        use values::computed::NumberOrPercentage;
+        match self.0.to_computed_value(context) {
+            NumberOrPercentage::Number(n) => n,
+            NumberOrPercentage::Percentage(p) => p.0,
+        }
+    }
+
+    #[inline]
+    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
+        Factor(NumberOrPercentage::Number(ToComputedValue::from_computed_value(computed)))
+    }
 }
 
 /// A specified value for the `drop-shadow()` filter.
 pub type SimpleShadow = GenericSimpleShadow<Option<Color>, Length, Option<Length>>;
 
 impl Parse for BoxShadow {
     fn parse<'i, 't>(
         context: &ParserContext,
@@ -151,53 +172,16 @@ impl Parse for Filter {
                 "saturate" => Ok(GenericFilter::Saturate(Factor::parse(context, i)?)),
                 "sepia" => Ok(GenericFilter::Sepia(Factor::parse(context, i)?)),
                 "drop-shadow" => Ok(GenericFilter::DropShadow(Parse::parse(context, i)?)),
             }
         })
     }
 }
 
-impl Parse for Factor {
-    #[inline]
-    fn parse<'i, 't>(
-        _context: &ParserContext,
-        input: &mut Parser<'i, 't>
-    ) -> Result<Self, ParseError<'i>> {
-        match input.next()? {
-            Token::Number { value, .. } if value.is_sign_positive() => {
-                Ok(Factor::Number(value))
-            },
-            Token::Percentage { unit_value, .. } if unit_value.is_sign_positive() => {
-                Ok(Factor::Percentage(Percentage(unit_value)))
-            },
-            other => Err(BasicParseError::UnexpectedToken(other).into()),
-        }
-    }
-}
-
-impl ToComputedValue for Factor {
-    /// This should actually be `ComputedNumberOrPercentage`, but layout uses
-    /// `computed::effects::Filter` directly in `StackingContext`.
-    type ComputedValue = ComputedNumber;
-
-    #[inline]
-    fn to_computed_value(&self, _context: &Context) -> Self::ComputedValue {
-        match *self {
-            Factor::Number(number) => number,
-            Factor::Percentage(percentage) => percentage.0,
-        }
-    }
-
-    #[inline]
-    fn from_computed_value(computed: &Self::ComputedValue) -> Self {
-        Factor::Number(*computed)
-    }
-}
-
 impl Parse for SimpleShadow {
     #[inline]
     fn parse<'i, 't>(
         context: &ParserContext,
         input: &mut Parser<'i, 't>
     ) -> Result<Self, ParseError<'i>> {
         let color = input.try(|i| Color::parse(context, i)).ok();
         let horizontal = Length::parse(context, input)?;
--- a/servo/components/style/values/specified/image.rs
+++ b/servo/components/style/values/specified/image.rs
@@ -325,20 +325,20 @@ impl Gradient {
                     Ok(Self::new(x, y))
                 })
             }
         }
 
         impl<S: Side> From<Component<S>> for NumberOrPercentage {
             fn from(component: Component<S>) -> Self {
                 match component {
-                    Component::Center => NumberOrPercentage::Percentage(Percentage(0.5)),
+                    Component::Center => NumberOrPercentage::Percentage(Percentage::new(0.5)),
                     Component::Number(number) => number,
                     Component::Side(side) => {
-                        let p = Percentage(if side.is_start() { 0. } else { 1. });
+                        let p = if side.is_start() { Percentage::zero() } else { Percentage::hundred() };
                         NumberOrPercentage::Percentage(p)
                     },
                 }
             }
         }
 
         impl<S: Side> From<Component<S>> for PositionComponent<S> {
             fn from(component: Component<S>) -> Self {
@@ -358,17 +358,17 @@ impl Gradient {
                 }
             }
         }
 
         impl<S: Copy + Side> Component<S> {
             fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
                 match (NumberOrPercentage::from(*self), NumberOrPercentage::from(*other)) {
                     (NumberOrPercentage::Percentage(a), NumberOrPercentage::Percentage(b)) => {
-                        a.0.partial_cmp(&b.0)
+                        a.get().partial_cmp(&b.get())
                     },
                     (NumberOrPercentage::Number(a), NumberOrPercentage::Number(b)) => {
                         a.value.partial_cmp(&b.value)
                     },
                     (_, _) => {
                         None
                     }
                 }
@@ -439,51 +439,51 @@ impl Gradient {
         let mut items = input.try(|i| {
             i.expect_comma()?;
             i.parse_comma_separated(|i| {
                 let function = i.expect_function()?;
                 let (color, mut p) = i.parse_nested_block(|i| {
                     let p = match_ignore_ascii_case! { &function,
                         "color-stop" => {
                             let p = match NumberOrPercentage::parse(context, i)? {
-                                NumberOrPercentage::Number(number) => number.value,
-                                NumberOrPercentage::Percentage(p) => p.0,
+                                NumberOrPercentage::Number(number) => Percentage::new(number.value),
+                                NumberOrPercentage::Percentage(p) => p,
                             };
                             i.expect_comma()?;
                             p
                         },
-                        "from" => 0.,
-                        "to" => 1.,
+                        "from" => Percentage::zero(),
+                        "to" => Percentage::hundred(),
                         _ => return Err(StyleParseError::UnexpectedFunction(function.clone()).into()),
                     };
                     let color = Color::parse(context, i)?;
                     if color == Color::CurrentColor {
                         return Err(StyleParseError::UnspecifiedError.into());
                     }
                     Ok((color.into(), p))
                 })?;
                 if reverse_stops {
-                    p = 1. - p;
+                    p.reverse();
                 }
                 Ok(GenericGradientItem::ColorStop(GenericColorStop {
                     color: color,
-                    position: Some(LengthOrPercentage::Percentage(Percentage(p))),
+                    position: Some(p.into()),
                 }))
             })
         }).unwrap_or(vec![]);
 
         if items.is_empty() {
             items = vec![
                 GenericGradientItem::ColorStop(GenericColorStop {
                     color: Color::transparent().into(),
-                    position: Some(Percentage(0.).into()),
+                    position: Some(Percentage::zero().into()),
                 }),
                 GenericGradientItem::ColorStop(GenericColorStop {
                     color: Color::transparent().into(),
-                    position: Some(Percentage(1.).into()),
+                    position: Some(Percentage::hundred().into()),
                 }),
             ];
         } else if items.len() == 1 {
             let first = items[0].clone();
             items.push(first);
         } else {
             items.sort_by(|a, b| {
                 match (a, b) {
--- a/servo/components/style/values/specified/length.rs
+++ b/servo/components/style/values/specified/length.rs
@@ -15,17 +15,17 @@ use std::{cmp, fmt, mem};
 use std::ascii::AsciiExt;
 use std::ops::Mul;
 use style_traits::{HasViewportPercentage, ToCss, ParseError, StyleParseError};
 use style_traits::values::specified::{AllowedLengthType, AllowedNumericType};
 use stylesheets::CssRuleType;
 use super::{AllowQuirks, Number, ToComputedValue};
 use values::{Auto, CSSFloat, Either, FONT_MEDIUM_PX, None_, Normal};
 use values::ExtremumLength;
-use values::computed::{ComputedValueAsSpecified, Context};
+use values::computed::{self, Context};
 use values::specified::calc::CalcNode;
 
 pub use values::specified::calc::CalcLengthOrPercentage;
 pub use super::image::{ColorStop, EndingShape as GradientEndingShape, Gradient};
 pub use super::image::{GradientKind, Image};
 
 /// Number of app units per pixel
 pub const AU_PER_PX: CSSFloat = 60.;
@@ -696,90 +696,139 @@ impl<T: Parse> Either<Length, T> {
         if let Ok(v) = input.try(|input| T::parse(context, input)) {
             return Ok(Either::Second(v));
         }
         Length::parse_internal(context, input, AllowedLengthType::NonNegative, AllowQuirks::No).map(Either::First)
     }
 }
 
 /// A percentage value.
-///
-/// [0 .. 100%] maps to [0.0 .. 1.0]
-///
-/// FIXME(emilio): There's no standard property that requires a `<percentage>`
-/// without requiring also a `<length>`. If such a property existed, we'd need
-/// to add special handling for `calc()` and percentages in here in the same way
-/// as for `Angle` and `Time`, but the lack of this this is otherwise
-/// undistinguishable (we handle it correctly from `CalcLengthOrPercentage`).
-///
-/// As of today, only `-moz-image-rect` supports percentages without length.
-/// This is not a regression, and that's a non-standard extension anyway, so I'm
-/// not implementing it for now.
-#[derive(Clone, Copy, Debug, Default, HasViewportPercentage, PartialEq)]
-#[cfg_attr(feature = "servo", derive(Deserialize, HeapSizeOf, Serialize))]
-pub struct Percentage(pub CSSFloat);
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+#[cfg_attr(feature = "servo", derive(HeapSizeOf))]
+pub struct Percentage {
+    /// The percentage value as a float.
+    ///
+    /// [0 .. 100%] maps to [0.0 .. 1.0]
+    value: CSSFloat,
+    /// If this percentage came from a calc() expression, this tells how
+    /// clamping should be done on the value.
+    calc_clamping_mode: Option<AllowedNumericType>,
+}
+
+no_viewport_percentage!(Percentage);
 
 impl ToCss for Percentage {
     fn to_css<W>(&self, dest: &mut W) -> fmt::Result
         where W: fmt::Write,
     {
-        write!(dest, "{}%", self.0 * 100.)
+        if self.calc_clamping_mode.is_some() {
+            dest.write_str("calc(")?;
+        }
+
+        write!(dest, "{}%", self.value * 100.)?;
+
+        if self.calc_clamping_mode.is_some() {
+            dest.write_str(")")?;
+        }
+        Ok(())
     }
 }
 
 impl Percentage {
+    /// Create a percentage from a numeric value.
+    pub fn new(value: CSSFloat) -> Self {
+        Self {
+            value,
+            calc_clamping_mode: None,
+        }
+    }
+
+    /// Get the underlying value for this float.
+    pub fn get(&self) -> CSSFloat {
+        self.calc_clamping_mode.map_or(self.value, |mode| mode.clamp(self.value))
+    }
+
+    /// Reverse this percentage, preserving calc-ness.
+    ///
+    /// For example: If it was 20%, convert it into 80%.
+    pub fn reverse(&mut self) {
+        let new_value = 1. - self.value;
+        self.value = new_value;
+    }
+
+
     /// Parse a specific kind of percentage.
-    pub fn parse_with_clamping_mode<'i, 't>(context: &ParserContext,
-                                            input: &mut Parser<'i, 't>,
-                                            num_context: AllowedNumericType)
-                                            -> Result<Self, ParseError<'i>> {
+    pub fn parse_with_clamping_mode<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+        num_context: AllowedNumericType,
+    ) -> Result<Self, ParseError<'i>> {
         match input.next()? {
             Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
-                Ok(Percentage(unit_value))
+                Ok(Percentage::new(unit_value))
+            }
+            Token::Function(ref name) if name.eq_ignore_ascii_case("calc") => {
+                let result = input.parse_nested_block(|i| {
+                    CalcNode::parse_percentage(context, i)
+                })?;
+
+                // TODO(emilio): -moz-image-rect is the only thing that uses
+                // the clamping mode... I guess we could disallow it...
+                Ok(Percentage {
+                    value: result,
+                    calc_clamping_mode: Some(num_context),
+                })
             }
             t => Err(BasicParseError::UnexpectedToken(t).into())
         }
     }
 
     /// Parses a percentage token, but rejects it if it's negative.
     pub fn parse_non_negative<'i, 't>(context: &ParserContext,
                                       input: &mut Parser<'i, 't>)
                                       -> Result<Self, ParseError<'i>> {
         Self::parse_with_clamping_mode(context, input, AllowedNumericType::NonNegative)
     }
 
     /// 0%
     #[inline]
     pub fn zero() -> Self {
-        Percentage(0.)
+        Percentage {
+            value: 0.,
+            calc_clamping_mode: None,
+        }
     }
 
     /// 100%
     #[inline]
     pub fn hundred() -> Self {
-        Percentage(1.)
+        Percentage {
+            value: 1.,
+            calc_clamping_mode: None,
+        }
     }
 }
 
 impl Parse for Percentage {
     #[inline]
-    fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
+    fn parse<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>
+    ) -> Result<Self, ParseError<'i>> {
         Self::parse_with_clamping_mode(context, input, AllowedNumericType::All)
     }
 }
 
-impl ComputedValueAsSpecified for Percentage {}
-
 /// A length or a percentage value.
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum LengthOrPercentage {
     Length(NoCalcLength),
-    Percentage(Percentage),
+    Percentage(computed::Percentage),
     Calc(Box<CalcLengthOrPercentage>),
 }
 
 impl From<Length> for LengthOrPercentage {
     fn from(len: Length) -> LengthOrPercentage {
         match len {
             Length::NoCalc(l) => LengthOrPercentage::Length(l),
             Length::Calc(l) => LengthOrPercentage::Calc(l),
@@ -792,16 +841,30 @@ impl From<NoCalcLength> for LengthOrPerc
     fn from(len: NoCalcLength) -> Self {
         LengthOrPercentage::Length(len)
     }
 }
 
 impl From<Percentage> for LengthOrPercentage {
     #[inline]
     fn from(pc: Percentage) -> Self {
+        if pc.calc_clamping_mode.is_some() {
+            LengthOrPercentage::Calc(Box::new(CalcLengthOrPercentage {
+                percentage: Some(computed::Percentage(pc.get())),
+                .. Default::default()
+            }))
+        } else {
+            LengthOrPercentage::Percentage(computed::Percentage(pc.get()))
+        }
+    }
+}
+
+impl From<computed::Percentage> for LengthOrPercentage {
+    #[inline]
+    fn from(pc: computed::Percentage) -> Self {
         LengthOrPercentage::Percentage(pc)
     }
 }
 
 impl LengthOrPercentage {
     #[inline]
     /// Returns a `zero` length.
     pub fn zero() -> LengthOrPercentage {
@@ -815,17 +878,17 @@ impl LengthOrPercentage {
                               -> Result<LengthOrPercentage, ParseError<'i>>
     {
         let token = input.next()?;
         match token {
             Token::Dimension { value, ref unit, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentage::Length)
             }
             Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
-                return Ok(LengthOrPercentage::Percentage(Percentage(unit_value)))
+                return Ok(LengthOrPercentage::Percentage(computed::Percentage(unit_value)))
             }
             Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 if value != 0. &&
                    !context.parsing_mode.allows_unitless_lengths() &&
                    !allow_quirks.allowed(context.quirks_mode) {
                     Err(())
                 } else {
                     return Ok(LengthOrPercentage::Length(NoCalcLength::from_px(value)))
@@ -920,48 +983,48 @@ impl LengthOrPercentage {
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `auto` keyword.
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 pub enum LengthOrPercentageOrAuto {
     Length(NoCalcLength),
-    Percentage(Percentage),
+    Percentage(computed::Percentage),
     Auto,
     Calc(Box<CalcLengthOrPercentage>),
 }
 
 impl From<NoCalcLength> for LengthOrPercentageOrAuto {
     #[inline]
     fn from(len: NoCalcLength) -> Self {
         LengthOrPercentageOrAuto::Length(len)
     }
 }
 
-impl From<Percentage> for LengthOrPercentageOrAuto {
+impl From<computed::Percentage> for LengthOrPercentageOrAuto {
     #[inline]
-    fn from(pc: Percentage) -> Self {
+    fn from(pc: computed::Percentage) -> Self {
         LengthOrPercentageOrAuto::Percentage(pc)
     }
 }
 
 impl LengthOrPercentageOrAuto {
     fn parse_internal<'i, 't>(context: &ParserContext,
                               input: &mut Parser<'i, 't>,
                               num_context: AllowedLengthType,
                               allow_quirks: AllowQuirks)
                               -> Result<Self, ParseError<'i>> {
         let token = input.next()?;
         match token {
             Token::Dimension { value, ref unit, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentageOrAuto::Length)
             }
             Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
-                Ok(LengthOrPercentageOrAuto::Percentage(Percentage(unit_value)))
+                Ok(LengthOrPercentageOrAuto::Percentage(computed::Percentage(unit_value)))
             }
             Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 if value != 0. &&
                    !context.parsing_mode.allows_unitless_lengths() &&
                    !allow_quirks.allowed(context.quirks_mode) {
                     return Err(StyleParseError::UnspecifiedError.into())
                 }
                 Ok(LengthOrPercentageOrAuto::Length(
@@ -1004,17 +1067,17 @@ impl LengthOrPercentageOrAuto {
 
     /// Returns a value representing a `0` length.
     pub fn zero() -> Self {
         LengthOrPercentageOrAuto::Length(NoCalcLength::zero())
     }
 
     /// Returns a value representing `0%`.
     pub fn zero_percent() -> Self {
-        LengthOrPercentageOrAuto::Percentage(Percentage::zero())
+        LengthOrPercentageOrAuto::Percentage(computed::Percentage::zero())
     }
 }
 
 impl Parse for LengthOrPercentageOrAuto {
     #[inline]
     fn parse<'i, 't>(context: &ParserContext, input: &mut Parser<'i, 't>) -> Result<Self, ParseError<'i>> {
         Self::parse_quirky(context, input, AllowQuirks::No)
     }
@@ -1032,17 +1095,17 @@ impl LengthOrPercentageOrAuto {
 }
 
 /// Either a `<length>`, a `<percentage>`, or the `none` keyword.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
 #[allow(missing_docs)]
 pub enum LengthOrPercentageOrNone {
     Length(NoCalcLength),
-    Percentage(Percentage),
+    Percentage(computed::Percentage),
     Calc(Box<CalcLengthOrPercentage>),
     None,
 }
 
 impl LengthOrPercentageOrNone {
     fn parse_internal<'i, 't>(context: &ParserContext,
                               input: &mut Parser<'i, 't>,
                               num_context: AllowedLengthType,
@@ -1050,17 +1113,17 @@ impl LengthOrPercentageOrNone {
                               -> Result<LengthOrPercentageOrNone, ParseError<'i>>
     {
         let token = input.next()?;
         match token {
             Token::Dimension { value, ref unit, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 NoCalcLength::parse_dimension(context, value, unit).map(LengthOrPercentageOrNone::Length)
             }
             Token::Percentage { unit_value, .. } if num_context.is_ok(context.parsing_mode, unit_value) => {
-                Ok(LengthOrPercentageOrNone::Percentage(Percentage(unit_value)))
+                Ok(LengthOrPercentageOrNone::Percentage(computed::Percentage(unit_value)))
             }
             Token::Number { value, .. } if num_context.is_ok(context.parsing_mode, value) => {
                 if value != 0. && !context.parsing_mode.allows_unitless_lengths() &&
                    !allow_quirks.allowed(context.quirks_mode) {
                     return Err(StyleParseError::UnspecifiedError.into())
                 }
                 Ok(LengthOrPercentageOrNone::Length(
                     NoCalcLength::Absolute(AbsoluteLength::Px(value))
--- a/servo/components/style/values/specified/mod.rs
+++ b/servo/components/style/values/specified/mod.rs
@@ -499,33 +499,37 @@ impl ToCss for Number {
         self.value.to_css(dest)?;
         if self.calc_clamping_mode.is_some() {
             dest.write_str(")")?;
         }
         Ok(())
     }
 }
 
-/// <number-percentage>
+/// <number> | <percentage>
+///
 /// Accepts only non-negative numbers.
+///
+/// FIXME(emilio): Should probably use Either.
 #[allow(missing_docs)]
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Copy, Debug, PartialEq, ToCss)]
 pub enum NumberOrPercentage {
     Percentage(Percentage),
     Number(Number),
 }
 
 no_viewport_percentage!(NumberOrPercentage);
 
 impl NumberOrPercentage {
-    fn parse_with_clamping_mode<'i, 't>(context: &ParserContext,
-                                        input: &mut Parser<'i, 't>,
-                                        type_: AllowedNumericType)
-                                        -> Result<Self, ParseError<'i>> {
+    fn parse_with_clamping_mode<'i, 't>(
+        context: &ParserContext,
+        input: &mut Parser<'i, 't>,
+        type_: AllowedNumericType
+    ) -> Result<Self, ParseError<'i>> {
         if let Ok(per) = input.try(|i| Percentage::parse_with_clamping_mode(context, i, type_)) {
             return Ok(NumberOrPercentage::Percentage(per));
         }
 
         parse_number_with_clamping_mode(context, input, type_).map(NumberOrPercentage::Number)
     }
 
     /// Parse a non-negative number or percentage.
--- a/servo/components/style/values/specified/position.rs
+++ b/servo/components/style/values/specified/position.rs
@@ -7,19 +7,19 @@
 //!
 //! [position]: https://drafts.csswg.org/css-backgrounds-3/#position
 
 use cssparser::Parser;
 use parser::{Parse, ParserContext};
 use std::fmt;
 use style_traits::{HasViewportPercentage, ToCss, ParseError};
 use values::computed::{CalcLengthOrPercentage, LengthOrPercentage as ComputedLengthOrPercentage};
-use values::computed::{Context, ToComputedValue};
+use values::computed::{Context, Percentage, ToComputedValue};
 use values::generics::position::Position as GenericPosition;
-use values::specified::{AllowQuirks, LengthOrPercentage, Percentage};
+use values::specified::{AllowQuirks, LengthOrPercentage};
 use values::specified::transform::OriginComponent;
 
 /// The specified value of a CSS `<position>`
 pub type Position = GenericPosition<HorizontalPosition, VerticalPosition>;
 
 /// The specified value of a horizontal position.
 pub type HorizontalPosition = PositionComponent<X>;
 
@@ -190,17 +190,17 @@ impl<S: Parse> PositionComponent<S> {
         let lop = input.try(|i| LengthOrPercentage::parse_quirky(context, i, allow_quirks)).ok();
         Ok(PositionComponent::Side(keyword, lop))
     }
 }
 
 impl<S> PositionComponent<S> {
     /// `0%`
     pub fn zero() -> Self {
-        PositionComponent::Length(LengthOrPercentage::Percentage(Percentage(0.)))
+        PositionComponent::Length(LengthOrPercentage::Percentage(Percentage::zero()))
     }
 }
 
 impl<S: Side> ToComputedValue for PositionComponent<S> {
     type ComputedValue = ComputedLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         match *self {
@@ -209,17 +209,18 @@ impl<S: Side> ToComputedValue for Positi
             },
             PositionComponent::Side(ref keyword, None) => {
                 let p = Percentage(if keyword.is_start() { 0. } else { 1. });
                 ComputedLengthOrPercentage::Percentage(p)
             },
             PositionComponent::Side(ref keyword, Some(ref length)) if !keyword.is_start() => {
                 match length.to_computed_value(context) {
                     ComputedLengthOrPercentage::Length(length) => {
-                        ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage::new(-length, Some(Percentage(1.0))))
+                        ComputedLengthOrPercentage::Calc(
+                            CalcLengthOrPercentage::new(-length, Some(Percentage::hundred())))
                     },
                     ComputedLengthOrPercentage::Percentage(p) => {
                         ComputedLengthOrPercentage::Percentage(Percentage(1.0 - p.0))
                     },
                     ComputedLengthOrPercentage::Calc(calc) => {
                         let p = Percentage(1. - calc.percentage.map_or(0., |p| p.0));
                         ComputedLengthOrPercentage::Calc(CalcLengthOrPercentage::new(-calc.unclamped_length(), Some(p)))
                     },
--- a/servo/components/style/values/specified/transform.rs
+++ b/servo/components/style/values/specified/transform.rs
@@ -4,22 +4,23 @@
 
 //! Specified types for CSS values that are related to transformations.
 
 use cssparser::Parser;
 use euclid::Point2D;
 use parser::{Parse, ParserContext};
 use selectors::parser::SelectorParseError;
 use style_traits::{ParseError, StyleParseError};
-use values::computed::{LengthOrPercentage as ComputedLengthOrPercentage, Context, ToComputedValue};
+use values::computed::{Context, LengthOrPercentage as ComputedLengthOrPercentage};
+use values::computed::{Percentage as ComputedPercentage, ToComputedValue};
 use values::computed::transform::TimingFunction as ComputedTimingFunction;
 use values::generics::transform::{StepPosition, TimingFunction as GenericTimingFunction};
 use values::generics::transform::{TimingKeyword, TransformOrigin as GenericTransformOrigin};
 use values::specified::{Integer, Number};
-use values::specified::length::{Length, LengthOrPercentage, Percentage};
+use values::specified::length::{Length, LengthOrPercentage};
 use values::specified::position::{Side, X, Y};
 
 /// The specified value of a CSS `<transform-origin>`
 pub type TransformOrigin = GenericTransformOrigin<OriginComponent<X>, OriginComponent<Y>, Length>;
 
 /// The specified value of a component of a CSS `<transform-origin>`.
 #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
 #[derive(Clone, Debug, HasViewportPercentage, PartialEq, ToCss)]
@@ -102,37 +103,37 @@ impl<S> Parse for OriginComponent<S>
 impl<S> ToComputedValue for OriginComponent<S>
     where S: Side,
 {
     type ComputedValue = ComputedLengthOrPercentage;
 
     fn to_computed_value(&self, context: &Context) -> Self::ComputedValue {
         match *self {
             OriginComponent::Center => {
-                ComputedLengthOrPercentage::Percentage(Percentage(0.5))
+                ComputedLengthOrPercentage::Percentage(ComputedPercentage(0.5))
             },
             OriginComponent::Length(ref length) => {
                 length.to_computed_value(context)
             },
             OriginComponent::Side(ref keyword) => {
-                let p = Percentage(if keyword.is_start() { 0. } else { 1. });
+                let p = ComputedPercentage(if keyword.is_start() { 0. } else { 1. });
                 ComputedLengthOrPercentage::Percentage(p)
             },
         }
     }
 
     fn from_computed_value(computed: &Self::ComputedValue) -> Self {
         OriginComponent::Length(ToComputedValue::from_computed_value(computed))
     }
 }
 
 impl<S> OriginComponent<S> {
     /// `0%`
     pub fn zero() -> Self {
-        OriginComponent::Length(LengthOrPercentage::Percentage(Percentage(0.)))
+        OriginComponent::Length(LengthOrPercentage::Percentage(ComputedPercentage::zero()))
     }
 }
 
 #[cfg(feature = "gecko")]
 #[inline]
 fn allow_frames_timing() -> bool {
     use gecko_bindings::bindings;
     unsafe { bindings::Gecko_IsFramesTimingEnabled() }
--- a/servo/components/style_traits/lib.rs
+++ b/servo/components/style_traits/lib.rs
@@ -86,17 +86,17 @@ pub enum StyleParseError<'i> {
     BadStringInDeclarationValueBlock(CompactCowStr<'i>),
     /// Unexpected closing parenthesis in a DVB.
     UnbalancedCloseParenthesisInDeclarationValueBlock,
     /// Unexpected closing bracket in a DVB.
     UnbalancedCloseSquareBracketInDeclarationValueBlock,
     /// Unexpected closing curly bracket in a DVB.
     UnbalancedCloseCurlyBracketInDeclarationValueBlock,
     /// A property declaration parsing error.
-    PropertyDeclaration(PropertyDeclarationParseError),
+    PropertyDeclaration(PropertyDeclarationParseError<'i>),
     /// A property declaration value had input remaining after successfully parsing.
     PropertyDeclarationValueNotExhausted,
     /// An unexpected dimension token was encountered.
     UnexpectedDimension(CompactCowStr<'i>),
     /// A media query using a ranged expression with no value was encountered.
     RangedExpressionWithNoValue,
     /// A function was encountered that was not expected.
     UnexpectedFunction(CompactCowStr<'i>),
@@ -107,25 +107,25 @@ pub enum StyleParseError<'i> {
     /// Unexpected @charset rule encountered.
     UnexpectedCharsetRule,
     /// Unsupported @ rule
     UnsupportedAtRule(CompactCowStr<'i>),
     /// A placeholder for many sources of errors that require more specific variants.
     UnspecifiedError,
     /// An unexpected token was found within a namespace rule.
     UnexpectedTokenWithinNamespace(Token<'i>),
-    /// An unknown CSS property was encountered.
-    UnknownProperty(CompactCowStr<'i>),
 }
 
 /// The result of parsing a property declaration.
 #[derive(Eq, PartialEq, Clone, Debug)]
-pub enum PropertyDeclarationParseError {
+pub enum PropertyDeclarationParseError<'i> {
     /// The property declaration was for an unknown property.
-    UnknownProperty,
+    UnknownProperty(CompactCowStr<'i>),
+    /// An unknown vendor-specific identifier was encountered.
+    UnknownVendorProperty,
     /// The property declaration was for a disabled experimental property.
     ExperimentalProperty,
     /// The property declaration contained an invalid value.
     InvalidValue(String),
     /// The declaration contained an animation property, and we were parsing
     /// this as a keyframe block (so that property should be ignored).
     ///
     /// See: https://drafts.csswg.org/css-animations/#keyframes
@@ -135,18 +135,18 @@ pub enum PropertyDeclarationParseError {
 }
 
 impl<'a> From<StyleParseError<'a>> for ParseError<'a> {
     fn from(this: StyleParseError<'a>) -> Self {
         cssparser::ParseError::Custom(SelectorParseError::Custom(this))
     }
 }
 
-impl<'a> From<PropertyDeclarationParseError> for ParseError<'a> {
-    fn from(this: PropertyDeclarationParseError) -> Self {
+impl<'a> From<PropertyDeclarationParseError<'a>> for ParseError<'a> {
+    fn from(this: PropertyDeclarationParseError<'a>) -> Self {
         cssparser::ParseError::Custom(SelectorParseError::Custom(StyleParseError::PropertyDeclaration(this)))
     }
 }
 
 bitflags! {
     /// The mode to use when parsing values.
     pub flags ParsingMode: u8 {
         /// In CSS, lengths must have units, except for zero values, where the unit can be omitted.
--- a/servo/ports/cef/window.rs
+++ b/servo/ports/cef/window.rs
@@ -19,16 +19,17 @@ use wrappers::CefWrap;
 
 use compositing::compositor_thread::EventLoopWaker;
 use compositing::windowing::{WindowEvent, WindowMethods};
 use euclid::{Point2D, TypedPoint2D, TypedRect, Size2D, TypedSize2D, ScaleFactor};
 use gleam::gl;
 use msg::constellation_msg::{Key, KeyModifiers};
 use net_traits::net_error_list::NetError;
 use script_traits::{DevicePixel, LoadData};
+use servo::ipc_channel::ipc::IpcSender;
 use servo_geometry::DeviceIndependentPixel;
 use std::cell::RefCell;
 use std::ffi::CString;
 use std::os::raw::{c_char, c_void};
 use std::ptr;
 use std::rc::Rc;
 use std::sync::mpsc::{Sender, channel};
 use servo_url::ServoUrl;
@@ -484,18 +485,20 @@ impl WindowMethods for Window {
                        .get_client()
                        .get_render_handler()
                        .on_cursor_change(browser.clone(), cursor_handle,
                                          self.cursor_type_for_cursor(cursor), &info);
             }
         }
     }
 
-    fn allow_navigation(&self, _: ServoUrl) -> bool {
-        true
+    fn allow_navigation(&self, _: ServoUrl, response_chan: IpcSender<bool>) {
+        if let Err(e) = response_chan.send(true) {
+            warn!("Failed to send allow_navigation() response: {}", e);
+        };
     }
 
     fn supports_clipboard(&self) -> bool {
         false
     }
 }
 
 #[cfg(target_os="macos")]
--- a/servo/ports/geckolib/error_reporter.rs
+++ b/servo/ports/geckolib/error_reporter.rs
@@ -228,17 +228,18 @@ impl<'a> ErrorHelpers<'a> for Contextual
 
             (_, CssParseError::Custom(SelectorParseError::UnexpectedIdent(ident))) =>
                 ErrorString::Ident(ident),
 
             (_, CssParseError::Custom(SelectorParseError::ExpectedNamespace(namespace))) =>
                 ErrorString::Ident(namespace),
 
             (_, CssParseError::Custom(SelectorParseError::Custom(
-                StyleParseError::UnknownProperty(property)))) =>
+                StyleParseError::PropertyDeclaration(
+                    PropertyDeclarationParseError::UnknownProperty(property))))) =>
                 ErrorString::Ident(property),
 
             (_, CssParseError::Custom(SelectorParseError::Custom(
                 StyleParseError::UnexpectedTokenWithinNamespace(token)))) =>
                 ErrorString::UnexpectedToken(token),
 
             (s, _)  => ErrorString::Snippet(s)
         }
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -2478,18 +2478,19 @@ pub extern "C" fn Servo_DeclarationBlock
 #[no_mangle]
 pub extern "C" fn Servo_DeclarationBlock_SetPercentValue(declarations:
                                                          RawServoDeclarationBlockBorrowed,
                                                          property: nsCSSPropertyID,
                                                          value: f32) {
     use style::properties::{PropertyDeclaration, LonghandId};
     use style::properties::longhands::height::SpecifiedValue as Height;
     use style::properties::longhands::width::SpecifiedValue as Width;
+    use style::values::computed::Percentage;
     use style::values::specified::MozLength;
-    use style::values::specified::length::{LengthOrPercentage, Percentage};
+    use style::values::specified::length::LengthOrPercentage;
 
     let long = get_longhand_from_id!(property);
     let pc = Percentage(value);
 
     let prop = match_wrap_declared! { long,
         Height => Height(MozLength::LengthOrPercentageOrAuto(pc.into())),
         Width => Width(MozLength::LengthOrPercentageOrAuto(pc.into())),
         MarginTop => pc.into(),
--- a/servo/ports/glutin/Cargo.toml
+++ b/servo/ports/glutin/Cargo.toml
@@ -8,16 +8,17 @@ license = "MPL-2.0"
 name = "glutin_app"
 path = "lib.rs"
 
 [dependencies]
 bitflags = "0.7"
 compositing = {path = "../../components/compositing"}
 euclid = "0.15"
 gleam = "0.4"
+libservo = {path = "../../components/servo"}
 log = "0.3.5"
 msg = {path = "../../components/msg"}
 net_traits = {path = "../../components/net_traits"}
 script_traits = {path = "../../components/script_traits"}
 servo-glutin = "0.11"
 servo_geometry = {path = "../../components/geometry"}
 servo_config = {path = "../../components/config"}
 servo_url = {path = "../../components/url"}
--- a/servo/ports/glutin/lib.rs
+++ b/servo/ports/glutin/lib.rs
@@ -12,16 +12,17 @@ extern crate compositing;
 extern crate euclid;
 extern crate gleam;
 extern crate glutin;
 #[macro_use] extern crate log;
 extern crate msg;
 extern crate net_traits;
 #[cfg(any(target_os = "linux", target_os = "macos"))] extern crate osmesa_sys;
 extern crate script_traits;
+extern crate servo;
 extern crate servo_config;
 extern crate servo_geometry;
 extern crate servo_url;
 extern crate style_traits;
 extern crate webrender_api;
 
 #[cfg(target_os = "windows")] extern crate winapi;
 #[cfg(target_os = "windows")] extern crate user32;
--- a/servo/ports/glutin/window.rs
+++ b/servo/ports/glutin/window.rs
@@ -20,16 +20,17 @@ use glutin::TouchPhase;
 #[cfg(target_os = "macos")]
 use glutin::os::macos::{ActivationPolicy, WindowBuilderExt};
 use msg::constellation_msg::{self, Key};
 use msg::constellation_msg::{ALT, CONTROL, KeyState, NONE, SHIFT, SUPER, TraversalDirection};
 use net_traits::net_error_list::NetError;
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use osmesa_sys;
 use script_traits::{DevicePixel, LoadData, TouchEventType, TouchpadPressurePhase};
+use servo::ipc_channel::ipc::IpcSender;
 use servo_config::opts;
 use servo_config::prefs::PREFS;
 use servo_config::resource_files;
 use servo_geometry::DeviceIndependentPixel;
 use servo_url::ServoUrl;
 use std::cell::{Cell, RefCell};
 #[cfg(any(target_os = "linux", target_os = "macos"))]
 use std::ffi::CString;
@@ -1295,18 +1296,20 @@ impl WindowMethods for Window {
             }
 
             _ => {
                 self.platform_handle_key(key, mods);
             }
         }
     }
 
-    fn allow_navigation(&self, _: ServoUrl) -> bool {
-        true
+    fn allow_navigation(&self, _: ServoUrl, response_chan: IpcSender<bool>) {
+        if let Err(e) = response_chan.send(true) {
+            warn!("Failed to send allow_navigation() response: {}", e);
+        };
     }
 
     fn supports_clipboard(&self) -> bool {
         true
     }
 }
 
 fn glutin_phase_to_touch_event_type(phase: TouchPhase) -> TouchEventType {
--- a/servo/python/servo/bootstrap_commands.py
+++ b/servo/python/servo/bootstrap_commands.py
@@ -130,21 +130,18 @@ class MachCommands(CommandBase):
                 print("Use |bootstrap-rust --force| to download again.")
                 continue
 
             if self.use_stable_rust():
                 std_url = ("https://static-rust-lang-org.s3.amazonaws.com/dist/rust-std-%s-%s.tar.gz"
                            % (version, target_triple))
                 tgz_file = install_dir + ('rust-std-%s-%s.tar.gz' % (version, target_triple))
             else:
-                tarball = "%s/rust-std-nightly-%s.tar.gz" % (version, target_triple)
-                base_url = "https://s3.amazonaws.com/rust-lang-ci/rustc-builds"
-                if not self.config["build"]["llvm-assertions"]:
-                    base_url += "-alt"
-                std_url = base_url + "/" + tarball
+                std_url = ("https://s3.amazonaws.com/rust-lang-ci/rustc-builds/%s/rust-std-nightly-%s.tar.gz"
+                           % (version, target_triple))
                 tgz_file = install_dir + ('rust-std-nightly-%s.tar.gz' % target_triple)
 
             download_file("Host rust library for target %s" % target_triple, std_url, tgz_file)
             print("Extracting Rust stdlib for target %s..." % target_triple)
             extract(tgz_file, install_dir)
             shutil.copytree(path.join(install_dir,
                                       "rust-std%s%s-%s" % (nightly_suffix, stable_version, target_triple),
                                       "rust-std-%s" % target_triple, "lib", "rustlib", target_triple),
--- a/servo/rust-commit-hash
+++ b/servo/rust-commit-hash
@@ -1,1 +1,1 @@
-f85579d4a2c342654f9b158fafd565eb159fdb59
+ab91c70cc69450bc69b76df0fd2faa82bda42d6b
--- a/servo/tests/unit/net/http_loader.rs
+++ b/servo/tests/unit/net/http_loader.rs
@@ -25,24 +25,28 @@ use msg::constellation_msg::TEST_PIPELIN
 use net::cookie::Cookie;
 use net::cookie_storage::CookieStorage;
 use net::resource_thread::AuthCacheEntry;
 use net::test::replace_host_table;
 use net_traits::{CookieSource, NetworkError};
 use net_traits::request::{Request, RequestInit, RequestMode, CredentialsMode, Destination};
 use net_traits::response::ResponseBody;
 use new_fetch_context;
-use servo_url::ServoUrl;
+use servo_url::{ServoUrl, ImmutableOrigin};
 use std::collections::HashMap;
 use std::io::{Read, Write};
 use std::str::FromStr;
 use std::sync::{Arc, Mutex, RwLock, mpsc};
 use std::sync::atomic::{AtomicBool, Ordering};
 use std::sync::mpsc::Receiver;
 
+fn mock_origin() -> ImmutableOrigin {
+    ServoUrl::parse("http://servo.org").unwrap().origin()
+}
+
 fn read_response(reader: &mut Read) -> String {
     let mut buf = vec![0; 1024];
     match reader.read(&mut buf) {
         Ok(len) if len > 0 => {
             unsafe { buf.set_len(len); }
             String::from_utf8(buf).unwrap()
         },
         Ok(_) => "".to_owned(),
@@ -133,63 +137,63 @@ fn test_check_default_headers_loaded_in_
 
     *expected_headers.lock().unwrap() = Some(headers.clone());
 
     // Testing for method.GET
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: url.clone().origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     // Testing for method.POST
     let mut post_headers = headers.clone();
     post_headers.set(ContentLength(0 as u64));
     let url_str = url.as_str();
     // request gets header "Origin: http://example.com" but expected_headers has
     // "Origin: http://example.com/" which do not match for equality so strip trailing '/'
     post_headers.set(Origin::from_str(&url_str[..url_str.len()-1]).unwrap());
     *expected_headers.lock().unwrap() = Some(post_headers);
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Post,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: url.clone().origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let _ = server.close();
 }
 
 #[test]
 fn test_load_when_request_is_not_get_or_head_and_there_is_no_body_content_length_should_be_set_to_0() {
     let handler = move |request: HyperRequest, _: HyperResponse| {
         assert_eq!(request.headers.get::<ContentLength>(), Some(&ContentLength(0)));
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Post,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let _ = server.close();
 }
 
 #[test]
 fn test_request_and_response_data_with_network_messages() {
     let handler = move |_: HyperRequest, mut response: HyperResponse| {
         response.headers_mut().set(Host { hostname: "foo.bar".to_owned(), port: None });
@@ -200,23 +204,23 @@ fn test_request_and_response_data_with_n
     let mut request_headers = Headers::new();
     request_headers.set(Host { hostname: "bar.foo".to_owned(), port: None });
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         headers: request_headers,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let (devtools_chan, devtools_port) = mpsc::channel();
     let response = fetch(&mut request, Some(devtools_chan));
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let _ = server.close();
 
     // notification received from devtools
     let devhttprequest = expect_devtools_http_request(&devtools_port);
     let devhttpresponse = expect_devtools_http_response(&devtools_port);
 
     //Creating default headers for request
@@ -287,23 +291,23 @@ fn test_request_and_response_message_fro
         response.send(b"Yay!").unwrap();
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: None,
         .. RequestInit::default()
     });
     let (devtools_chan, devtools_port) = mpsc::channel();
     let response = fetch(&mut request, Some(devtools_chan));
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let _ = server.close();
 
     // notification received from devtools
     assert!(devtools_port.try_recv().is_err());
 }
 
 #[test]
@@ -322,17 +326,16 @@ fn test_redirected_request_to_devtools()
         response.send(b"").unwrap();
     };
     let (mut pre_server, pre_url) = make_server(pre_handler);
 
     let mut request = Request::from_init(RequestInit {
         url: pre_url.clone(),
         method: Method::Post,
         destination: Destination::Document,
-        origin: pre_url.clone(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let (devtools_chan, devtools_port) = mpsc::channel();
     fetch(&mut request, Some(devtools_chan));
 
     let _ = pre_server.close();
     let _ = post_server.close();
@@ -370,17 +373,17 @@ fn test_load_when_redirecting_from_a_pos
         response.send(b"").unwrap();
     };
     let (mut pre_server, pre_url) = make_server(pre_handler);
 
     let mut request = Request::from_init(RequestInit {
         url: pre_url.clone(),
         method: Method::Post,
         destination: Destination::Document,
-        origin: pre_url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = pre_server.close();
     let _ = post_server.close();
 
@@ -398,26 +401,27 @@ fn test_load_should_decode_the_response_
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
-    assert_eq!(*response.body.lock().unwrap(),
+    let internal_response = response.internal_response.unwrap();
+    assert!(internal_response.status.unwrap().is_success());
+    assert_eq!(*internal_response.body.lock().unwrap(),
                ResponseBody::Done(b"Yay!".to_vec()));
 }
 
 #[test]
 fn test_load_should_decode_the_response_as_gzip_when_response_headers_have_content_encoding_gzip() {
     let handler = move |_: HyperRequest, mut response: HyperResponse| {
         response.headers_mut().set(ContentEncoding(vec![Encoding::Gzip]));
         let mut e = GzEncoder::new(Vec::new(), Compression::Default);
@@ -427,26 +431,27 @@ fn test_load_should_decode_the_response_
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
-    assert_eq!(*response.body.lock().unwrap(),
+    let internal_response = response.internal_response.unwrap();
+    assert!(internal_response.status.unwrap().is_success());
+    assert_eq!(*internal_response.body.lock().unwrap(),
                ResponseBody::Done(b"Yay!".to_vec()));
 }
 
 #[test]
 fn test_load_doesnt_send_request_body_on_any_redirect() {
     let post_handler = move |mut request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.method, Method::Get);
         let data = read_response(&mut request);
@@ -465,17 +470,17 @@ fn test_load_doesnt_send_request_body_on
     };
     let (mut pre_server, pre_url) = make_server(pre_handler);
 
     let mut request = Request::from_init(RequestInit {
         url: pre_url.clone(),
         body: Some(b"Body on POST!".to_vec()),
         method: Method::Post,
         destination: Destination::Document,
-        origin: pre_url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = pre_server.close();
     let _ = post_server.close();
 
@@ -490,26 +495,26 @@ fn test_load_doesnt_add_host_to_sts_list
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let context = new_fetch_context(None);
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
     assert_eq!(context.state.hsts_list.read().unwrap().is_host_secure(url.host_str().unwrap()), false);
 }
 
 #[test]
 fn test_load_sets_cookies_in_the_resource_manager_when_it_get_set_cookie_header_in_response() {
     let handler = move |_: HyperRequest, mut response: HyperResponse| {
         response.headers_mut().set(SetCookie(vec!["mozillaIs=theBest".to_owned()]));
         response.send(b"Yay!").unwrap();
@@ -520,26 +525,26 @@ fn test_load_sets_cookies_in_the_resourc
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), Some("mozillaIs=theBest"));
 }
 
 #[test]
 fn test_load_sets_requests_cookies_header_for_url_by_getting_cookies_from_the_resource_manager() {
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<CookieHeader>(),
@@ -560,26 +565,26 @@ fn test_load_sets_requests_cookies_heade
         cookie_jar.push(cookie, &url, CookieSource::HTTP);
     }
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_sends_cookie_if_nonhttp() {
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<CookieHeader>(),
                    Some(&CookieHeader(vec!["mozillaIs=theBest".to_owned()])));
         response.send(b"Yay!").unwrap();
@@ -598,26 +603,26 @@ fn test_load_sends_cookie_if_nonhttp() {
         cookie_jar.push(cookie, &url, CookieSource::HTTP);
     }
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_cookie_set_with_httponly_should_not_be_available_using_getcookiesforurl() {
     let handler = move |_: HyperRequest, mut response: HyperResponse| {
         let pair = vec!["mozillaIs=theBest; HttpOnly".to_owned()];
         response.headers_mut().set(SetCookie(pair));
         response.send(b"Yay!").unwrap();
@@ -628,26 +633,26 @@ fn test_cookie_set_with_httponly_should_
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), Some("mozillaIs=theBest"));
     let mut cookie_jar = context.state.cookie_jar.write().unwrap();
     assert!(cookie_jar.cookies_for_url(&url, CookieSource::NonHTTP).is_none());
 }
 
 #[test]
 fn test_when_cookie_received_marked_secure_is_ignored_for_http() {
@@ -662,26 +667,26 @@ fn test_when_cookie_received_marked_secu
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     assert_cookie_for_domain(&context.state.cookie_jar, url.as_str(), None);
 }
 
 #[test]
 fn test_load_sets_content_length_to_length_of_request_body() {
     let content = b"This is a request body";
     let content_length = ContentLength(content.len() as u64);
@@ -691,25 +696,25 @@ fn test_load_sets_content_length_to_leng
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Post,
         body: Some(content.to_vec()),
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_uses_explicit_accept_from_headers_in_load_data() {
     let accept = Accept(vec![qitem(Mime(TopLevel::Text, SubLevel::Html, vec![]))]);
     let expected_accept = accept.clone();
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<Accept>(), Some(&expected_accept));
@@ -719,25 +724,25 @@ fn test_load_uses_explicit_accept_from_h
 
     let mut accept_headers = Headers::new();
     accept_headers.set(accept);
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         headers: accept_headers,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_sets_default_accept_to_html_xhtml_xml_and_then_anything_else() {
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<Accept>(), Some(&Accept(vec![
             qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])),
             qitem(Mime(TopLevel::Application, SubLevel::Ext("xhtml+xml".to_owned()), vec![])),
@@ -747,25 +752,25 @@ fn test_load_sets_default_accept_to_html
         response.send(b"Yay!").unwrap();
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_uses_explicit_accept_encoding_from_load_data_headers() {
     let accept_encoding = AcceptEncoding(vec![qitem(Encoding::Chunked)]);
     let expected_accept_encoding = accept_encoding.clone();
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<AcceptEncoding>(), Some(&expected_accept_encoding));
@@ -775,25 +780,25 @@ fn test_load_uses_explicit_accept_encodi
 
     let mut accept_encoding_headers = Headers::new();
     accept_encoding_headers.set(accept_encoding);
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         headers: accept_encoding_headers,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_sets_default_accept_encoding_to_gzip_and_deflate() {
     let handler = move |request: HyperRequest, response: HyperResponse| {
         assert_eq!(request.headers.get::<AcceptEncoding>(), Some(&AcceptEncoding(vec![
             qitem(Encoding::Gzip),
             qitem(Encoding::Deflate),
@@ -802,25 +807,25 @@ fn test_load_sets_default_accept_encodin
         response.send(b"Yay!").unwrap();
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_load_errors_when_there_a_redirect_loop() {
     let url_b_for_a = Arc::new(Mutex::new(None::<ServoUrl>));
     let url_b_for_a_clone = url_b_for_a.clone();
     let handler_a = move |_: HyperRequest, mut response: HyperResponse| {
         response.headers_mut().set(Location(url_b_for_a_clone.lock().unwrap().as_ref().unwrap().to_string()));
@@ -838,17 +843,17 @@ fn test_load_errors_when_there_a_redirec
     let (mut server_b, url_b) = make_server(handler_b);
 
     *url_b_for_a.lock().unwrap() = Some(url_b.clone());
 
     let mut request = Request::from_init(RequestInit {
         url: url_a.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url_a.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server_a.close();
     let _ = server_b.close();
 
@@ -881,17 +886,17 @@ fn test_load_succeeds_with_a_redirect_lo
     let (mut server_b, url_b) = make_server(handler_b);
 
     *url_b_for_a.lock().unwrap() = Some(url_b.clone());
 
     let mut request = Request::from_init(RequestInit {
         url: url_a.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url_a.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server_a.close();
     let _ = server_b.close();
 
@@ -917,28 +922,28 @@ fn test_load_follows_a_redirect() {
         response.send(b"").unwrap();
     };
     let (mut pre_server, pre_url) = make_server(pre_handler);
 
     let mut request = Request::from_init(RequestInit {
         url: pre_url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: pre_url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = pre_server.close();
     let _ = post_server.close();
 
-    let response = response.to_actual();
-    assert!(response.status.unwrap().is_success());
-    assert_eq!(*response.body.lock().unwrap(),
+    let internal_response = response.internal_response.unwrap();
+    assert!(internal_response.status.unwrap().is_success());
+    assert_eq!(*internal_response.body.lock().unwrap(),
                ResponseBody::Done(b"Yay!".to_vec()));
 }
 
 #[test]
 fn  test_redirect_from_x_to_y_provides_y_cookies_from_y() {
     let shared_url_y = Arc::new(Mutex::new(None::<ServoUrl>));
     let shared_url_y_clone = shared_url_y.clone();
     let handler = move |request: HyperRequest, mut response: HyperResponse| {
@@ -994,28 +999,28 @@ fn  test_redirect_from_x_to_y_provides_y
         ).unwrap();
         cookie_jar.push(cookie_y, &url_y, CookieSource::HTTP);
     }
 
     let mut request = Request::from_init(RequestInit {
         url: url_x.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url_x.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    let response = response.to_actual();
-    assert!(response.status.unwrap().is_success());
-    assert_eq!(*response.body.lock().unwrap(),
+    let internal_response = response.internal_response.unwrap();
+    assert!(internal_response.status.unwrap().is_success());
+    assert_eq!(*internal_response.body.lock().unwrap(),
                ResponseBody::Done(b"Yay!".to_vec()));
 }
 
 #[test]
 fn test_redirect_from_x_to_x_provides_x_with_cookie_from_first_response() {
     let handler = move |request: HyperRequest, mut response: HyperResponse| {
         let path = match request.uri {
             ::hyper::uri::RequestUri::AbsolutePath(path) => path,
@@ -1038,28 +1043,28 @@ fn test_redirect_from_x_to_x_provides_x_
     let (mut server, url) = make_server(handler);
 
     let url = url.join("/initial/").unwrap();
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    let response = response.to_actual();
-    assert!(response.status.unwrap().is_success());
-    assert_eq!(*response.body.lock().unwrap(),
+    let internal_response = response.internal_response.unwrap();
+    assert!(internal_response.status.unwrap().is_success());
+    assert_eq!(*internal_response.body.lock().unwrap(),
                ResponseBody::Done(b"Yay!".to_vec()));
 }
 
 #[test]
 fn test_if_auth_creds_not_in_url_but_in_cache_it_sets_it() {
     let handler = move |request: HyperRequest, response: HyperResponse| {
         let expected = Authorization(Basic {
             username: "username".to_owned(),
@@ -1070,17 +1075,17 @@ fn test_if_auth_creds_not_in_url_but_in_
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
     let context = new_fetch_context(None);
 
     let auth_entry = AuthCacheEntry {
         user_name: "username".to_owned(),
@@ -1088,43 +1093,43 @@ fn test_if_auth_creds_not_in_url_but_in_
     };
 
     context.state.auth_cache.write().unwrap().entries.insert(url.origin().clone().ascii_serialization(), auth_entry);
 
     let response = fetch_with_context(&mut request, &context);
 
     let _ = server.close();
 
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 }
 
 #[test]
 fn test_auth_ui_needs_www_auth() {
     let handler = move |_: HyperRequest, mut response: HyperResponse| {
         *response.status_mut() = StatusCode::Unauthorized;
         response.send(b"").unwrap();
     };
     let (mut server, url) = make_server(handler);
 
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         body: None,
         destination: Destination::Document,
-        origin: url.clone(),
+        origin: mock_origin(),
         pipeline_id: Some(TEST_PIPELINE_ID),
         credentials_mode: CredentialsMode::Include,
         .. RequestInit::default()
     });
 
     let response = fetch(&mut request, None);
 
     let _ = server.close();
 
-    assert_eq!(response.status.unwrap(), StatusCode::Unauthorized);
+    assert_eq!(response.internal_response.unwrap().status.unwrap(), StatusCode::Unauthorized);
 }
 
 #[test]
 fn test_origin_set() {
     let origin_header = Arc::new(Mutex::new(None));
     let origin_header_clone = origin_header.clone();
     let handler = move |request: HyperRequest, mut resp: HyperResponse| {
         let origin_header_clone = origin_header.clone();
@@ -1137,45 +1142,45 @@ fn test_origin_set() {
     let (mut server, url) = make_server(handler);
 
     let mut origin = Origin::new(url.scheme(), url.host_str().unwrap(), url.port());
     *origin_header_clone.lock().unwrap() = Some(origin.clone());
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Post,
         body: None,
-        origin: url.clone(),
+        origin: url.clone().origin(),
         .. RequestInit::default()
     });
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let origin_url = ServoUrl::parse("http://example.com").unwrap();
     origin = Origin::new(origin_url.scheme(), origin_url.host_str().unwrap(), origin_url.port());
     // Test Origin header is set on Get request with CORS mode
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Get,
         mode: RequestMode::CorsMode,
         body: None,
-        origin: origin_url.clone(),
+        origin: origin_url.clone().origin(),
         .. RequestInit::default()
     });
 
     *origin_header_clone.lock().unwrap() = Some(origin.clone());
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     // Test Origin header is not set on method Head
     let mut request = Request::from_init(RequestInit {
         url: url.clone(),
         method: Method::Head,
         body: None,
-        origin: url.clone(),
+        origin: url.clone().origin(),
         .. RequestInit::default()
     });
 
     *origin_header_clone.lock().unwrap() = None;
     let response = fetch(&mut request, None);
-    assert!(response.status.unwrap().is_success());
+    assert!(response.internal_response.unwrap().status.unwrap().is_success());
 
     let _ = server.close();
 }
--- a/servo/tests/unit/style/animated_properties.rs
+++ b/servo/tests/unit/style/animated_properties.rs
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use cssparser::RGBA;
 use style::properties::animated_properties::{Animatable, IntermediateRGBA};
 use style::properties::longhands::transform::computed_value::ComputedOperation as TransformOperation;
 use style::properties::longhands::transform::computed_value::T as TransformList;
 use style::values::animated::ToAnimatedValue;
-use style::values::specified::length::Percentage;
+use style::values::computed::length::Percentage;
 
 fn interpolate_rgba(from: RGBA, to: RGBA, progress: f64) -> RGBA {
     let from = from.to_animated_value();
     let to = to.to_animated_value();
     RGBA::from_animated_value(from.interpolate(&to, progress).unwrap())
 }
 
 // Color
--- a/servo/tests/unit/style/attr.rs
+++ b/servo/tests/unit/style/attr.rs
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use app_units::Au;
 use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_length};
 use style::values::computed::CalcLengthOrPercentage;
-use style::values::specified::length::Percentage;
+use style::values::computed::length::Percentage;
 
 #[test]
 fn test_length_calc() {
     let calc = CalcLengthOrPercentage::new(Au(10), Some(Percentage(0.2)));
     assert_eq!(calc.to_used_value(Some(Au(10))), Some(Au(12)));
     assert_eq!(calc.to_used_value(Some(Au(0))), Some(Au(10)));
     assert_eq!(calc.to_used_value(None), None);
 
--- a/servo/tests/unit/style/properties/serialization.rs
+++ b/servo/tests/unit/style/properties/serialization.rs
@@ -353,26 +353,26 @@ mod shorthand_serialization {
 
         use style::values::specified::BorderCornerRadius;
         use style::values::specified::length::Percentage;
 
         #[test]
         fn border_radius_should_serialize_correctly() {
             let mut properties = Vec::new();
             properties.push(PropertyDeclaration::BorderTopLeftRadius(Box::new(BorderCornerRadius::new(
-                Percentage(0.01).into(), Percentage(0.05).into()
+                Percentage::new(0.01).into(), Percentage::new(0.05).into()
             ))));
             properties.push(PropertyDeclaration::BorderTopRightRadius(Box::new(BorderCornerRadius::new(
-                Percentage(0.02).into(), Percentage(0.06).into()
+                Percentage::new(0.02).into(), Percentage::new(0.06).into()
             ))));
             properties.push(PropertyDeclaration::BorderBottomRightRadius(Box::new(BorderCornerRadius::new(
-                Percentage(0.03).into(), Percentage(0.07).into()
+                Percentage::new(0.03).into(), Percentage::new(0.07).into()
             ))));
             properties.push(PropertyDeclaration::BorderBottomLeftRadius(Box::new(BorderCornerRadius::new(
-                Percentage(0.04).into(), Percentage(0.08).into()
+                Percentage::new(0.04).into(), Percentage::new(0.08).into()
             ))));
 
             let serialization = shorthand_properties_to_string(properties);
             assert_eq!(serialization, "border-radius: 1% 2% 3% 4% / 5% 6% 7% 8%;");
         }
     }
 
 
@@ -558,17 +558,17 @@ mod shorthand_serialization {
     fn flex_should_serialize_all_available_properties() {
         use style::values::specified::{Number, Percentage};
 
         let mut properties = Vec::new();
 
         let grow = Number::new(2f32);
         let shrink = Number::new(3f32);
         let basis =
-            FlexBasis::Length(Percentage(0.5f32).into());
+            FlexBasis::Length(Percentage::new(0.5f32).into());
 
         properties.push(PropertyDeclaration::FlexGrow(grow));
         properties.push(PropertyDeclaration::FlexShrink(shrink));
         properties.push(PropertyDeclaration::FlexBasis(basis));
 
         let serialization = shorthand_properties_to_string(properties);
         assert_eq!(serialization, "flex: 2 3 50%;");
     }
--- a/servo/tests/unit/style/stylesheets.rs
+++ b/servo/tests/unit/style/stylesheets.rs
@@ -21,17 +21,18 @@ use style::properties::{CSSWideKeyword, 
 use style::properties::longhands;
 use style::properties::longhands::animation_play_state;
 use style::shared_lock::SharedRwLock;
 use style::stylearc::Arc;
 use style::stylesheets::{Origin, Namespaces};
 use style::stylesheets::{Stylesheet, StylesheetContents, NamespaceRule, CssRule, CssRules, StyleRule, KeyframesRule};
 use style::stylesheets::keyframes_rule::{Keyframe, KeyframeSelector, KeyframePercentage};
 use style::values::{KeyframesName, CustomIdent};
-use style::values::specified::{LengthOrPercentageOrAuto, Percentage, PositionComponent};
+use style::values::computed::Percentage;
+use style::values::specified::{LengthOrPercentageOrAuto, PositionComponent};
 
 pub fn block_from<I>(iterable: I) -> PropertyDeclarationBlock
 where I: IntoIterator<Item=(PropertyDeclaration, Importance)> {
     let mut block = PropertyDeclarationBlock::new();
     for (d, i) in iterable {
         block.push(d, i)
     }
     block
@@ -310,21 +311,48 @@ fn test_report_error_stylesheet() {
     let media = Arc::new(lock.wrap(MediaList::empty()));
     Stylesheet::from_str(css, url.clone(), Origin::UserAgent, media, lock,
                          None, &error_reporter, QuirksMode::NoQuirks, 5u64);
 
     let mut errors = errors.lock().unwrap();
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'invalid: true;', \
-                Custom(UnknownProperty(\"invalid\"))", error.message);
+                Custom(PropertyDeclaration(UnknownProperty(\"invalid\")))", error.message);
     assert_eq!(9, error.line);
     assert_eq!(8, error.column);
 
     let error = errors.pop().unwrap();
     assert_eq!("Unsupported property declaration: 'display: invalid;', \
                 Custom(PropertyDeclaration(InvalidValue(\"display\")))", error.message);
     assert_eq!(8, error.line);
     assert_eq!(8, error.column);
 
     // testing for the url
     assert_eq!(url, error.url);
 }
+
+#[test]
+fn test_no_report_unrecognized_vendor_properties() {
+    let css = r"
+    div {
+        -o-background-color: red;
+        _background-color: red;
+        -moz-background-color: red;
+    }
+    ";
+    let url = ServoUrl::parse("about::test").unwrap();
+    let error_reporter = CSSInvalidErrorReporterTest::new();
+
+    let errors = error_reporter.errors.clone();
+
+    let lock = SharedRwLock::new();
+    let media = Arc::new(lock.wrap(MediaList::empty()));
+    Stylesheet::from_str(css, url, Origin::UserAgent, media, lock,
+                         None, &error_reporter, QuirksMode::NoQuirks, 0u64);
+
+    let mut errors = errors.lock().unwrap();
+    let error = errors.pop().unwrap();
+    assert_eq!("Unsupported property declaration: '-moz-background-color: red;', \
+                Custom(PropertyDeclaration(UnknownProperty(\"-moz-background-color\")))",
+               error.message);
+    assert!(errors.is_empty());
+}
--- a/taskcluster/ci/source-test/mozlint.yml
+++ b/taskcluster/ci/source-test/mozlint.yml
@@ -8,19 +8,17 @@ mozlint-eslint:
     worker-type: aws-provisioner-v1/gecko-t-linux-xlarge
     worker:
         docker-image: {in-tree: "lint"}
         max-run-time: 1800
     run:
         using: run-task
         command: >
             cd /home/worker/checkouts/gecko/ &&
-            /build/tooltool.py fetch -m tools/lint/eslint/manifest.tt &&
-            tar xvfz eslint.tar.gz &&
-            rm eslint.tar.gz &&
+            cp -r /build/node_modules_eslint node_modules &&
             ln -s ../tools/lint/eslint/eslint-plugin-mozilla node_modules &&
             ln -s ../tools/lint/eslint/eslint-plugin-spidermonkey-js node_modules &&
             ./mach lint -l eslint -f treeherder --quiet
     when:
         files-changed:
             # Files that are likely audited.
             - '**/*.js'
             - '**/*.jsm'
--- a/taskcluster/docker/lint/Dockerfile
+++ b/taskcluster/docker/lint/Dockerfile
@@ -12,16 +12,18 @@ ADD topsrcdir/python/mozbuild/mozbuild/a
 ADD topsrcdir/testing/mozharness/external_tools/robustcheckout.py /usr/local/mercurial/robustcheckout.py
 
 # %include taskcluster/docker/recipes/install-node.sh
 ADD topsrcdir/taskcluster/docker/recipes/install-node.sh /build/install-node.sh
 
 # %include taskcluster/docker/recipes/install-mercurial.sh
 ADD topsrcdir/taskcluster/docker/recipes/install-mercurial.sh /build/install-mercurial.sh
 ADD system-setup.sh /tmp/system-setup.sh
+# %include tools/lint/eslint/manifest.tt
+ADD topsrcdir/tools/lint/eslint/manifest.tt /tmp/eslint.tt
 # %include tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
 ADD topsrcdir/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt /tmp/eslint-plugin-mozilla.tt
 # %include tools/lint/flake8_/flake8_requirements.txt
 ADD topsrcdir/tools/lint/flake8_/flake8_requirements.txt /tmp/flake8_requirements.txt
 # %include tools/lint/tox/tox_requirements.txt
 ADD topsrcdir/tools/lint/tox/tox_requirements.txt /tmp/tox_requirements.txt
 RUN bash /tmp/system-setup.sh
 
--- a/taskcluster/docker/lint/system-setup.sh
+++ b/taskcluster/docker/lint/system-setup.sh
@@ -42,16 +42,18 @@ cd /build
 ###
 # ESLint Setup
 ###
 
 # install node
 
 . install-node.sh
 
+/build/tooltool.py fetch -m /tmp/eslint.tt
+mv /build/node_modules /build/node_modules_eslint
 /build/tooltool.py fetch -m /tmp/eslint-plugin-mozilla.tt
 mv /build/node_modules /build/node_modules_eslint-plugin-mozilla
 
 ###
 # Flake8 Setup
 ###
 
 cd /setup
--- a/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_navigation.py
@@ -379,17 +379,16 @@ class TestBackForwardNavigation(BaseNavi
     def test_file_url(self):
         test_pages = [
             {"url": self.test_page_remote},
             {"url": self.test_page_file_url},
             {"url": self.test_page_remote},
         ]
         self.run_bfcache_test(test_pages)
 
-    @skip("Causes crashes for JS GC (bug 1344863) and a11y (bug 1344868)")
     def test_frameset(self):
         test_pages = [
             {"url": self.marionette.absolute_url("frameset.html")},
             {"url": self.test_page_remote},
             {"url": self.marionette.absolute_url("frameset.html")},
         ]
         self.run_bfcache_test(test_pages)
 
--- a/toolkit/components/extensions/ExtensionTabs.jsm
+++ b/toolkit/components/extensions/ExtensionTabs.jsm
@@ -261,16 +261,27 @@ class TabBase {
    */
   get favIconUrl() {
     if (this.hasTabPermission) {
       return this._favIconUrl;
     }
   }
 
   /**
+   * @property {integer} lastAccessed
+   *        Returns the last time the tab was accessed as the number of
+   *        milliseconds since epoch.
+   *        @readonly
+   *        @abstract
+   */
+  get lastAccessed() {
+    throw new Error("Not implemented");
+  }
+
+  /**
    * @property {boolean} audible
    *        Returns true if the tab is currently playing audio, false otherwise.
    *        @readonly
    *        @abstract
    */
   get audible() {
     throw new Error("Not implemented");
   }
@@ -476,16 +487,17 @@ class TabBase {
       windowId: this.windowId,
       highlighted: this.selected,
       active: this.selected,
       pinned: this.pinned,
       status: this.status,
       incognito: this.incognito,
       width: this.width,
       height: this.height,
+      lastAccessed: this.lastAccessed,
       audible: this.audible,
       mutedInfo: this.mutedInfo,
     };
 
     // If the tab has not been fully layed-out yet, fallback to the geometry
     // from a different tab (usually the currently active tab).
     if (fallbackTab && (!result.width || !result.height)) {
       result.width = fallbackTab.width;
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -122,9 +122,9 @@ skip-if = os == 'android'
 [test_ext_webrequest_upload.html]
 skip-if = os == 'android' # Currently fails in emulator tests
 [test_ext_webrequest_permission.html]
 [test_ext_webrequest_websocket.html]
 [test_ext_webnavigation.html]
 [test_ext_webnavigation_filters.html]
 [test_ext_window_postMessage.html]
 [test_ext_subframes_privileges.html]
-[test_ext_xhr_capabilities.html]
\ No newline at end of file
+[test_ext_xhr_capabilities.html]
--- a/toolkit/components/places/Bookmarks.jsm
+++ b/toolkit/components/places/Bookmarks.jsm
@@ -2181,17 +2181,19 @@ async function(db, folderGuids, options)
          JOIN descendants ON parent = did
        )
        SELECT b.id AS _id, b.parent AS _parentId, b.position AS 'index',
               b.type, url, b.guid, p.guid AS parentGuid, b.dateAdded,
               b.lastModified, IFNULL(b.title, "") AS title,
               p.parent AS _grandParentId, NULL AS _childCount,
               b.syncStatus AS _syncStatus
        FROM descendants
-       JOIN moz_bookmarks b ON did = b.id
+       /* The usage of CROSS JOIN is not random, it tells the optimizer
+          to retain the original rows order, so the hierarchy is respected */
+       CROSS JOIN moz_bookmarks b ON did = b.id
        JOIN moz_bookmarks p ON p.id = b.parent
        LEFT JOIN moz_places h ON b.fk = h.id`, { folderGuid });
 
     itemsRemoved = itemsRemoved.concat(rowsToItemsArray(rows));
 
     await db.executeCached(
       `WITH RECURSIVE
        descendants(did) AS (
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -153,62 +153,16 @@ hasRecentCorruptDB()
       NS_ENSURE_TRUE(lastMod > 0, false);
       return (PR_Now() - lastMod) > RECENT_BACKUP_TIME_MICROSEC;
     }
   }
   return false;
 }
 
 /**
- * Updates sqlite_stat1 table through ANALYZE.
- * Since also nsPlacesExpiration.js executes ANALYZE, the analyzed tables
- * must be the same in both components.  So ensure they are in sync.
- *
- * @param aDBConn
- *        The database connection.
- */
-nsresult
-updateSQLiteStatistics(mozIStorageConnection* aDBConn)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  nsCOMPtr<mozIStorageAsyncStatement> analyzePlacesStmt;
-  aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "ANALYZE moz_places"
-  ), getter_AddRefs(analyzePlacesStmt));
-  NS_ENSURE_STATE(analyzePlacesStmt);
-  nsCOMPtr<mozIStorageAsyncStatement> analyzeBookmarksStmt;
-  aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "ANALYZE moz_bookmarks"
-  ), getter_AddRefs(analyzeBookmarksStmt));
-  NS_ENSURE_STATE(analyzeBookmarksStmt);
-  nsCOMPtr<mozIStorageAsyncStatement> analyzeVisitsStmt;
-  aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "ANALYZE moz_historyvisits"
-  ), getter_AddRefs(analyzeVisitsStmt));
-  NS_ENSURE_STATE(analyzeVisitsStmt);
-  nsCOMPtr<mozIStorageAsyncStatement> analyzeInputStmt;
-  aDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
-    "ANALYZE moz_inputhistory"
-  ), getter_AddRefs(analyzeInputStmt));
-  NS_ENSURE_STATE(analyzeInputStmt);
-
-  mozIStorageBaseStatement *stmts[] = {
-    analyzePlacesStmt,
-    analyzeBookmarksStmt,
-    analyzeVisitsStmt,
-    analyzeInputStmt
-  };
-
-  nsCOMPtr<mozIStoragePendingStatement> ps;
-  (void)aDBConn->ExecuteAsync(stmts, ArrayLength(stmts), nullptr,
-                              getter_AddRefs(ps));
-  return NS_OK;
-}
-
-/**
  * Sets the connection journal mode to one of the JOURNAL_* types.
  *
  * @param aDBConn
  *        The database connection.
  * @param aJournalMode
  *        One of the JOURNAL_* types.
  * @returns the current journal mode.
  * @note this may return a different journal mode than the required one, since
@@ -639,21 +593,16 @@ Database::EnsureConnection()
       // Bail out if we couldn't fix the database.
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     if (databaseMigrated) {
       mDatabaseStatus = nsINavHistoryService::DATABASE_STATUS_UPGRADED;
     }
 
-    if (mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_UPGRADED ||
-        mDatabaseStatus == nsINavHistoryService::DATABASE_STATUS_CREATE) {
-      MOZ_ALWAYS_SUCCEEDS(updateSQLiteStatistics(mMainConn));
-    }
-
     // Initialize here all the items that are not part of the on-disk database,
     // like views, temp triggers or temp tables.  The database should not be
     // considered corrupt if any of the following fails.
 
     rv = InitTempEntities();
     NS_ENSURE_SUCCESS(rv, rv);
 
     initSucceeded = true;
@@ -2621,16 +2570,23 @@ Database::Shutdown()
     new FinalizeStatementCacheProxy<mozIStorageStatement>(
           mAsyncThreadStatements,
           NS_ISUPPORTS_CAST(nsIObserver*, this)
         );
   DispatchToAsyncThread(event);
 
   mClosed = true;
 
+  // Execute PRAGMA optimized as last step, this will ensure proper database
+  // performance across restarts.
+  nsCOMPtr<mozIStoragePendingStatement> ps;
+  MOZ_ALWAYS_SUCCEEDS(mMainConn->ExecuteSimpleSQLAsync(NS_LITERAL_CSTRING(
+    "PRAGMA optimize(0x02)"
+  ), nullptr, getter_AddRefs(ps)));
+
   (void)mMainConn->AsyncClose(connectionShutdown);
   mMainConn = nullptr;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIObserver
 
 NS_IMETHODIMP
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -109,20 +109,16 @@ const URIENTRY_AVG_SIZE = 600;
 // stand-by or mobile devices batteries.
 const IDLE_TIMEOUT_SECONDS = 5 * 60;
 
 // If a clear history ran just before we shutdown, we will skip most of the
 // expiration at shutdown.  This is maximum number of seconds from last
 // clearHistory to decide to skip expiration at shutdown.
 const SHUTDOWN_WITH_RECENT_CLEARHISTORY_TIMEOUT_SECONDS = 10;
 
-// If the pages delta from the last ANALYZE is over this threashold, the tables
-// should be analyzed again.
-const ANALYZE_PAGES_THRESHOLD = 100;
-
 // If the number of pages over history limit is greater than this threshold,
 // expiration will be more aggressive, to bring back history to a saner size.
 const OVERLIMIT_PAGES_THRESHOLD = 1000;
 
 const MSECS_PER_DAY = 86400000;
 const ANNOS_EXPIRE_POLICIES = [
   { bind: "expire_days",
     type: Ci.nsIAnnotationService.EXPIRE_DAYS,
@@ -153,22 +149,21 @@ const STATUS = {
   DIRTY: 1,
   UNKNOWN: 2,
 };
 
 // Represents actions on which a query will run.
 const ACTION = {
   TIMED:           1 << 0, // happens every this._interval
   TIMED_OVERLIMIT: 1 << 1, // like TIMED but only when history is over limits
-  TIMED_ANALYZE:   1 << 2, // happens when ANALYZE statistics should be updated
-  CLEAR_HISTORY:   1 << 3, // happens when history is cleared
-  SHUTDOWN_DIRTY:  1 << 4, // happens at shutdown for DIRTY state
-  IDLE_DIRTY:      1 << 5, // happens on idle for DIRTY state
-  IDLE_DAILY:      1 << 6, // happens once a day on idle
-  DEBUG:           1 << 7, // happens on TOPIC_DEBUG_START_EXPIRATION
+  CLEAR_HISTORY:   1 << 2, // happens when history is cleared
+  SHUTDOWN_DIRTY:  1 << 3, // happens at shutdown for DIRTY state
+  IDLE_DIRTY:      1 << 4, // happens on idle for DIRTY state
+  IDLE_DAILY:      1 << 5, // happens once a day on idle
+  DEBUG:           1 << 6, // happens on TOPIC_DEBUG_START_EXPIRATION
 };
 
 // The queries we use to expire.
 const EXPIRATION_QUERIES = {
 
   // Some visits can be expired more often than others, cause they are less
   // useful to the user and can pollute awesomebar results:
   // 1. urls over 255 chars
@@ -409,42 +404,16 @@ const EXPIRATION_QUERIES = {
   },
 
   // Empty the notifications table.
   QUERY_DELETE_NOTIFICATIONS: {
     sql: "DELETE FROM expiration_notify",
     actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.SHUTDOWN_DIRTY |
              ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY | ACTION.DEBUG
   },
-
-  // The following queries are used to adjust the sqlite_stat1 table to help the
-  // query planner create better queries.  These should always be run LAST, and
-  // are therefore at the end of the object.
-  // Since also nsNavHistory.cpp executes ANALYZE, the analyzed tables
-  // must be the same in both components.  So ensure they are in sync.
-
-  QUERY_ANALYZE_MOZ_PLACES: {
-    sql: "ANALYZE moz_places",
-    actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
-             ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
-  },
-  QUERY_ANALYZE_MOZ_BOOKMARKS: {
-    sql: "ANALYZE moz_bookmarks",
-    actions: ACTION.TIMED_ANALYZE | ACTION.IDLE_DAILY | ACTION.DEBUG
-  },
-  QUERY_ANALYZE_MOZ_HISTORYVISITS: {
-    sql: "ANALYZE moz_historyvisits",
-    actions: ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
-             ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
-  },
-  QUERY_ANALYZE_MOZ_INPUTHISTORY: {
-    sql: "ANALYZE moz_inputhistory",
-    actions: ACTION.TIMED | ACTION.TIMED_OVERLIMIT | ACTION.TIMED_ANALYZE |
-             ACTION.CLEAR_HISTORY | ACTION.IDLE_DAILY | ACTION.DEBUG
-  },
 };
 
 /**
  * Sends a bookmarks notification through the given observers.
  *
  * @param observers
  *        array of nsINavBookmarkObserver objects.
  * @param notification
@@ -621,27 +590,20 @@ nsPlacesExpiration.prototype = {
   onDeleteURI() {},
   onPageChanged() {},
   onDeleteVisits() {},
 
   // nsITimerCallback
 
   notify: function PEX_timerCallback() {
     // Check if we are over history capacity, if so visits must be expired.
-    this._getPagesStats((aPagesCount, aStatsCount) => {
+    this._getPagesStats((aPagesCount) => {
       let overLimitPages = aPagesCount - this._urisLimit;
       this._overLimit = overLimitPages > 0;
-
       let action = this._overLimit ? ACTION.TIMED_OVERLIMIT : ACTION.TIMED;
-      // If the number of pages changed significantly from the last ANALYZE
-      // update SQLite statistics.
-      if (Math.abs(aPagesCount - aStatsCount) >= ANALYZE_PAGES_THRESHOLD) {
-        action = action | ACTION.TIMED_ANALYZE;
-      }
-
       // Adapt expiration aggressivity to the number of pages over the limit.
       let limit = overLimitPages > OVERLIMIT_PAGES_THRESHOLD ? LIMIT.LARGE
                                                              : LIMIT.SMALL;
 
       this._expireWithActionAndLimit(action, limit);
     });
   },
 
@@ -880,37 +842,33 @@ nsPlacesExpiration.prototype = {
     }
   },
 
   /**
    * Evaluates the real number of pages in the database and the value currently
    * used by the SQLite query planner.
    *
    * @param aCallback
-   *        invoked on success, function (aPagesCount, aStatsCount).
+   *        invoked on success, function (aPagesCount).
    */
   _getPagesStats: function PEX__getPagesStats(aCallback) {
     if (!this._cachedStatements["LIMIT_COUNT"]) {
       this._cachedStatements["LIMIT_COUNT"] = this._db.createAsyncStatement(
-        `SELECT (SELECT COUNT(*) FROM moz_places),
-                (SELECT SUBSTR(stat,1,LENGTH(stat)-2) FROM sqlite_stat1
-                 WHERE idx = 'moz_places_url_uniqueindex')`
+        `SELECT COUNT(*) FROM moz_places`
       );
     }
     this._cachedStatements["LIMIT_COUNT"].executeAsync({
       _pagesCount: 0,
-      _statsCount: 0,
       handleResult(aResults) {
         let row = aResults.getNextRow();
         this._pagesCount = row.getResultByIndex(0);
-        this._statsCount = row.getResultByIndex(1);
       },
       handleCompletion(aReason) {
         if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
-          aCallback(this._pagesCount, this._statsCount);
+          aCallback(this._pagesCount);
         }
       },
       handleError(aError) {
         Cu.reportError("Async statement execution returned with '" +
                        aError.result + "', '" + aError.message + "'");
       }
     });
   },
--- a/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
+++ b/toolkit/components/places/tests/browser/browser_visituri_privatebrowsing_perwindowpb.js
@@ -1,73 +1,61 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function test() {
-  // initialization
-  waitForExplicitFinish();
-  let windowsToClose = [];
-  let initialURL =
-    "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
-  let finalURL =
-    "http://example.com/tests/toolkit/components/places/tests/browser/final.html";
-  let observer = null;
-  let enumerator = null;
-  let currentObserver = null;
-  let uri = null;
+const initialURL =
+  "http://example.com/tests/toolkit/components/places/tests/browser/begin.html";
+const finalURL =
+ "http://example.com/tests/toolkit/components/places/tests/browser/final.html";
+
+var observer;
+var visitSavedPromise;
 
-  function doTest(aIsPrivateMode, aWindow, aTestURI, aCallback) {
+add_task(async function setup() {
+  visitSavedPromise = new Promise(resolve => {
     observer = {
-      observe(aSubject, aTopic, aData) {
+      observe(subject, topic, data) {
         // The uri-visit-saved topic should only work when on normal mode.
-        if (aTopic == "uri-visit-saved") {
-          // Remove the observers set on per window private mode and normal
-          // mode.
-          enumerator = aWindow.Services.obs.enumerateObservers("uri-visit-saved");
-          while (enumerator.hasMoreElements()) {
-            currentObserver = enumerator.getNext();
-            aWindow.Services.obs.removeObserver(currentObserver, "uri-visit-saved");
-          }
+        if (topic == "uri-visit-saved") {
+          Services.obs.removeObserver(observer, "uri-visit-saved");
 
           // The expected visit should be the finalURL because private mode
           // should not register a visit with the initialURL.
-          uri = aSubject.QueryInterface(Ci.nsIURI);
-          is(uri.spec, finalURL, "Check received expected visit");
+          let uri = subject.QueryInterface(Ci.nsIURI);
+          resolve(uri.spec);
         }
       }
     };
-
-    aWindow.Services.obs.addObserver(observer, "uri-visit-saved");
-
-    BrowserTestUtils.browserLoaded(aWindow.gBrowser.selectedBrowser).then(aCallback);
-    aWindow.gBrowser.selectedBrowser.loadURI(aTestURI);
-  }
-
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      windowsToClose.push(aWin);
-      // execute should only be called when need, like when you are opening
-      // web pages on the test. If calling executeSoon() is not necesary, then
-      // call whenNewWindowLoaded() instead of testOnWindow() on your test.
-      executeSoon(() => aCallback(aWin));
-    });
-  }
-
-   // This function is called after calling finish() on the test.
-  registerCleanupFunction(function() {
-    windowsToClose.forEach(function(aWin) {
-      aWin.close();
-    });
   });
 
-  // test first when on private mode
-  testOnWindow({private: true}, function(aWin) {
-    doTest(true, aWin, initialURL, function() {
-      // then test when not on private mode
-      testOnWindow({}, function(aWin2) {
-        doTest(false, aWin2, finalURL, function() {
-          PlacesTestUtils.clearHistory().then(finish);
-        });
-      });
-    });
+  Services.obs.addObserver(observer, "uri-visit-saved");
+
+  registerCleanupFunction(async function() {
+    await PlacesTestUtils.clearHistory()
   });
+});
+
+// Note: The private window test must be the first one to run, since we'll listen
+// to the first uri-visit-saved notification, and we expect this test to not
+// fire any, so we'll just find the non-private window test notification.
+add_task(async function test_private_browsing_window() {
+  await testLoadInWindow({private: true}, initialURL);
+});
+
+add_task(async function test_normal_window() {
+  await testLoadInWindow({private: false}, finalURL);
+
+  let url = await visitSavedPromise;
+  Assert.equal(url, finalURL, "Check received expected visit");
+});
+
+async function testLoadInWindow(options, url) {
+  let win = await BrowserTestUtils.openNewBrowserWindow(options);
+
+  registerCleanupFunction(async function() {
+    await BrowserTestUtils.closeWindow(win);
+  });
+
+  let loadedPromise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
+  await BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, url);
+  await loadedPromise;
 }
deleted file mode 100644
--- a/toolkit/components/places/tests/expiration/test_analyze_runs.js
+++ /dev/null
@@ -1,111 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Constants
-
-const TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING = "autocomplete-will-enter-text";
-
-// Helpers
-
-/**
- * Ensures that we have no data in the tables created by ANALYZE.
- */
-function clearAnalyzeData() {
-  let db = DBConn();
-  if (!db.tableExists("sqlite_stat1")) {
-    return;
-  }
-  db.executeSimpleSQL("DELETE FROM sqlite_stat1");
-}
-
-/**
- * Checks that we ran ANALYZE on the specified table.
- *
- * @param aTableName
- *        The table to check if ANALYZE was ran.
- * @param aRan
- *        True if it was expected to run, false otherwise
- */
-function do_check_analyze_ran(aTableName, aRan) {
-  let db = DBConn();
-  do_check_true(db.tableExists("sqlite_stat1"));
-  let stmt = db.createStatement("SELECT idx FROM sqlite_stat1 WHERE tbl = :table");
-  stmt.params.table = aTableName;
-  try {
-    if (aRan) {
-      do_check_true(stmt.executeStep());
-      do_check_neq(stmt.row.idx, null);
-    } else {
-      do_check_false(stmt.executeStep());
-    }
-  } finally {
-    stmt.finalize();
-  }
-}
-
-// Tests
-
-add_task(async function init_tests() {
-  const TEST_URI = NetUtil.newURI("http://mozilla.org/");
-  const TEST_TITLE = "This is a test";
-
-  await PlacesUtils.bookmarks.insert({
-    parentGuid: PlacesUtils.bookmarks.unfiledGuid,
-    title: TEST_TITLE,
-    url: TEST_URI
-  });
-  await PlacesTestUtils.addVisits(TEST_URI);
-  let thing = {
-    QueryInterface: XPCOMUtils.generateQI([Ci.nsIAutoCompleteInput,
-                                           Ci.nsIAutoCompletePopup,
-                                           Ci.nsIAutoCompleteController]),
-    get popup() { return thing; },
-    get controller() { return thing; },
-    popupOpen: true,
-    selectedIndex: 0,
-    getValueAt() { return TEST_URI.spec; },
-    searchString: TEST_TITLE,
-  };
-  Services.obs.notifyObservers(thing, TOPIC_AUTOCOMPLETE_FEEDBACK_INCOMING);
-});
-
-add_task(async function test_timed() {
-  clearAnalyzeData();
-
-  // Set a low interval and wait for the timed expiration to start.
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  setInterval(3);
-  await promise;
-  setInterval(3600);
-
-  do_check_analyze_ran("moz_places", false);
-  do_check_analyze_ran("moz_bookmarks", false);
-  do_check_analyze_ran("moz_historyvisits", false);
-  do_check_analyze_ran("moz_inputhistory", true);
-});
-
-add_task(async function test_debug() {
-  clearAnalyzeData();
-
-  await promiseForceExpirationStep(1);
-
-  do_check_analyze_ran("moz_places", true);
-  do_check_analyze_ran("moz_bookmarks", true);
-  do_check_analyze_ran("moz_historyvisits", true);
-  do_check_analyze_ran("moz_inputhistory", true);
-});
-
-add_task(async function test_clear_history() {
-  clearAnalyzeData();
-
-  let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-  let listener = Cc["@mozilla.org/places/expiration;1"]
-                 .getService(Ci.nsINavHistoryObserver);
-  listener.onClearHistory();
-  await promise;
-
-  do_check_analyze_ran("moz_places", true);
-  do_check_analyze_ran("moz_bookmarks", false);
-  do_check_analyze_ran("moz_historyvisits", true);
-  do_check_analyze_ran("moz_inputhistory", true);
-});
deleted file mode 100644
--- a/toolkit/components/places/tests/expiration/test_outdated_analyze.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Test that expiration executes ANALYZE when statistics are outdated.
-
-const TEST_URL = "http://www.mozilla.org/";
-
-XPCOMUtils.defineLazyServiceGetter(this, "gHistory",
-                                   "@mozilla.org/browser/history;1",
-                                   "mozIAsyncHistory");
-
-/**
- * Object that represents a mozIVisitInfo object.
- *
- * @param [optional] aTransitionType
- *        The transition type of the visit.  Defaults to TRANSITION_LINK if not
- *        provided.
- * @param [optional] aVisitTime
- *        The time of the visit.  Defaults to now if not provided.
- */
-function VisitInfo(aTransitionType, aVisitTime) {
-  this.transitionType =
-    aTransitionType === undefined ? TRANSITION_LINK : aTransitionType;
-  this.visitDate = aVisitTime || Date.now() * 1000;
-}
-
-function run_test() {
-  do_test_pending();
-
-  // Init expiration before "importing".
-  force_expiration_start();
-
-  // Add a bunch of pages (at laast IMPORT_PAGES_THRESHOLD pages).
-  let places = [];
-  for (let i = 0; i < 100; i++) {
-    places.push({
-      uri: NetUtil.newURI(TEST_URL + i),
-      title: "Title" + i,
-      visits: [new VisitInfo]
-    });
-  }
-  gHistory.updatePlaces(places);
-
-  // Set interval to a small value to expire on it.
-  setInterval(1); // 1s
-
-  Services.obs.addObserver(function observeExpiration(aSubject, aTopic, aData) {
-    Services.obs.removeObserver(observeExpiration,
-                                PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-
-    // Check that statistica are up-to-date.
-    let stmt = DBConn().createAsyncStatement(
-      "SELECT (SELECT COUNT(*) FROM moz_places) - "
-      + "(SELECT SUBSTR(stat,1,LENGTH(stat)-2) FROM sqlite_stat1 "
-      + "WHERE idx = 'moz_places_url_hashindex')"
-    );
-    stmt.executeAsync({
-      handleResult(aResultSet) {
-        let row = aResultSet.getNextRow();
-        this._difference = row.getResultByIndex(0);
-      },
-      handleError(aError) {
-        do_throw("Unexpected error (" + aError.result + "): " + aError.message);
-      },
-      handleCompletion(aReason) {
-        do_check_true(this._difference === 0);
-        do_test_finished();
-      }
-    });
-    stmt.finalize();
-  }, PlacesUtils.TOPIC_EXPIRATION_FINISHED);
-}
--- a/toolkit/components/places/tests/expiration/xpcshell.ini
+++ b/toolkit/components/places/tests/expiration/xpcshell.ini
@@ -1,18 +1,16 @@
 [DEFAULT]
 head = head_expiration.js
 skip-if = toolkit == 'android'
 
-[test_analyze_runs.js]
 [test_annos_expire_history.js]
 [test_annos_expire_never.js]
 [test_annos_expire_policy.js]
 [test_annos_expire_session.js]
 [test_clearHistory.js]
 [test_debug_expiration.js]
 [test_idle_daily.js]
 [test_notifications.js]
 [test_notifications_onDeleteURI.js]
 [test_notifications_onDeleteVisits.js]
-[test_outdated_analyze.js]
 [test_pref_interval.js]
 [test_pref_maxpages.js]
deleted file mode 100644
--- a/toolkit/components/places/tests/unit/test_analyze.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Tests sqlite_sta1 table exists, it should be created by analyze.
-// Since the bookmark roots are created when the DB is created (bug 704855),
-// the table will contain data.
-
-function run_test() {
-  do_test_pending();
-
-  let stmt = DBConn().createAsyncStatement(
-    "SELECT ROWID FROM sqlite_stat1"
-  );
-  stmt.executeAsync({
-    _gotResult: false,
-    handleResult(aResultSet) {
-      this._gotResult = true;
-    },
-    handleError(aError) {
-      do_throw("Unexpected error (" + aError.result + "): " + aError.message);
-    },
-    handleCompletion(aReason) {
-      do_check_true(this._gotResult);
-       do_test_finished();
-    }
-  });
-  stmt.finalize();
-}
--- a/toolkit/components/places/tests/unit/xpcshell.ini
+++ b/toolkit/components/places/tests/unit/xpcshell.ini
@@ -51,17 +51,16 @@ skip-if = os == "linux"
 [test_463863.js]
 [test_485442_crash_bug_nsNavHistoryQuery_GetUri.js]
 [test_486978_sort_by_date_queries.js]
 [test_536081.js]
 [test_1085291.js]
 [test_1105208.js]
 [test_1105866.js]
 [test_adaptive_bug527311.js]
-[test_analyze.js]
 [test_annotations.js]
 [test_asyncExecuteLegacyQueries.js]
 [test_async_in_batchmode.js]
 [test_async_transactions.js]
 skip-if = (os == "win" && os_version == "5.1") # Bug 1158887
 [test_autocomplete_stopSearch_no_throw.js]
 [test_bookmark_catobs.js]
 [test_bookmarks_json.js]
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -96,16 +96,20 @@ EXTRA_JS_MODULES += [
     'ThirdPartyCookieProbe.jsm',
     'UITelemetry.jsm',
 ]
 
 TESTING_JS_MODULES += [
   'tests/unit/TelemetryArchiveTesting.jsm',
 ]
 
+PYTHON_UNITTEST_MANIFESTS += [
+    'tests/python/python.ini',
+]
+
 GENERATED_FILES = [
     'TelemetryEventData.h',
     'TelemetryEventEnums.h',
     'TelemetryHistogramData.inc',
     'TelemetryHistogramEnums.h',
     'TelemetryProcessData.h',
     'TelemetryProcessEnums.h',
     'TelemetryScalarData.h',
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/python.ini
@@ -0,0 +1,1 @@
+[test_histogramtools_non_strict.py]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/python/test_histogramtools_non_strict.py
@@ -0,0 +1,78 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.import json
+
+import json
+import mozunit
+import sys
+import unittest
+from os import path
+
+TELEMETRY_ROOT_PATH = path.abspath(path.join(path.dirname(__file__), path.pardir, path.pardir))
+sys.path.append(TELEMETRY_ROOT_PATH)
+import histogram_tools   # noqa: E402
+
+
+def load_histogram(histograms):