Merge autoland to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 27 Jul 2017 16:09:49 -0700
changeset 371443 a4afa89bfdd10a903cfa9aa5a5bd1624dc85548c
parent 371407 0aab101edb50b91aa1c16b372fab382a60137482 (current diff)
parent 371442 0c61f00309fc3899344c1e682d02440db243a9b0 (diff)
child 371444 556f19ef392ac2d9aac579864e2179d6c1d464e8
child 371516 81a0e07cdc2c94c84751c404ea0bec40914dd0c6
child 371595 c8e2129968bfbf2afb293e72852c4b08cadcbda4
push id32246
push userkwierso@gmail.com
push dateThu, 27 Jul 2017 23:10:07 +0000
treeherdermozilla-central@a4afa89bfdd1 [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 m-c a=merge MozReview-Commit-ID: 4MCb0ASsMxe
browser/themes/linux/Toolbar-small.png
toolkit/components/telemetry/TelemetryUtils.jsm
toolkit/components/telemetry/moz.build
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1513,16 +1513,18 @@ pref("browser.translation.engine", "bing
 // Determines if Telemetry pings can be archived locally.
 pref("toolkit.telemetry.archive.enabled", true);
 // Enables sending the shutdown ping when Firefox shuts down.
 pref("toolkit.telemetry.shutdownPingSender.enabled", true);
 // Enables sending the shutdown ping using the pingsender from the first session.
 pref("toolkit.telemetry.shutdownPingSender.enabledFirstSession", false);
 // Enables sending the 'new-profile' ping on new profiles.
 pref("toolkit.telemetry.newProfilePing.enabled", true);
+// Enables sending 'update' pings on Firefox updates.
+pref("toolkit.telemetry.updatePing.enabled", true);
 
 // Telemetry experiments settings.
 pref("experiments.enabled", true);
 pref("experiments.manifest.fetchIntervalSeconds", 86400);
 pref("experiments.manifest.uri", "https://telemetry-experiment.cdn.mozilla.net/manifest/v1/firefox/%VERSION%/%CHANNEL%");
 // Whether experiments are supported by the current application profile.
 pref("experiments.supported", true);
 
--- a/browser/components/places/content/bookmarkProperties.js
+++ b/browser/components/places/content/bookmarkProperties.js
@@ -545,18 +545,19 @@ var BookmarkPropertiesPanel = {
 
   /**
    * Returns a childItems-transactions array representing the URIList with
    * which the dialog has been opened.
    */
   _getTransactionsForURIList: function BPP__getTransactionsForURIList() {
     var transactions = [];
     for (let uri of this._URIs) {
-      // uri should be an object in the form { url, title }. Though add-ons
+      // uri should be an object in the form { uri, title }. Though add-ons
       // could still use the legacy form, where it's an nsIURI.
+      // TODO: Remove This from v57 on.
       let [_uri, _title] = uri instanceof Ci.nsIURI ?
         [uri, this._getURITitleFromHistory(uri)] : [uri.uri, uri.title];
 
       let createTxn =
         new PlacesCreateBookmarkTransaction(_uri, -1,
                                             PlacesUtils.bookmarks.DEFAULT_INDEX,
                                             _title);
       transactions.push(createTxn);
@@ -659,20 +660,22 @@ var BookmarkPropertiesPanel = {
     } else if (this._itemType == LIVEMARK_CONTAINER) {
       info.feedUrl = this._feedURI;
       if (this._siteURI)
         info.siteUrl = this._siteURI;
 
       itemGuid = await PlacesTransactions.NewLivemark(info).transact();
     } else if (this._itemType == BOOKMARK_FOLDER) {
       itemGuid = await PlacesTransactions.NewFolder(info).transact();
-      for (let uri of this._URIs) {
-        let placeInfo = await PlacesUtils.history.fetch(uri);
-        let title = placeInfo ? placeInfo.title : "";
-        await PlacesTransactions.transact({ parentGuid: itemGuid, uri, title });
+      // URIs is an array of objects in the form { uri, title }.  It is still
+      // named URIs because for backwards compatibility it could also be an
+      // array of nsIURIs. TODO: Fix the property names from v57.
+      for (let { uri: url, title } of this._URIs) {
+        await PlacesTransactions.NewBookmark({ parentGuid: itemGuid, url, title })
+                                .transact();
       }
     } else {
       throw new Error(`unexpected value for _itemType:  ${this._itemType}`);
     }
 
     this._itemGuid = itemGuid;
     this._itemId = await PlacesUtils.promiseItemId(itemGuid);
     return Object.freeze({
--- a/browser/components/places/tests/browser/browser.ini
+++ b/browser/components/places/tests/browser/browser.ini
@@ -24,16 +24,17 @@ subsuite = clipboard
 [browser_555547.js]
 [browser_addBookmarkForFrame.js]
 [browser_bookmarklet_windowOpen.js]
 support-files =
   pageopeningwindow.html
 [browser_bookmarkProperties_addFolderDefaultButton.js]
 [browser_bookmarkProperties_addKeywordForThisSearch.js]
 [browser_bookmarkProperties_addLivemark.js]
+[browser_bookmarkProperties_bookmarkAllTabs.js]
 [browser_bookmarkProperties_editTagContainer.js]
 [browser_bookmarkProperties_readOnlyRoot.js]
 [browser_bookmarksProperties.js]
 [browser_drag_bookmarks_on_toolbar.js]
 [browser_forgetthissite_single.js]
 [browser_history_sidebar_search.js]
 [browser_library_batch_delete.js]
 [browser_library_commands.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/browser/browser_bookmarkProperties_bookmarkAllTabs.js
@@ -0,0 +1,44 @@
+"use strict"
+
+const TEST_URLS = [
+  "about:robots",
+  "about:mozilla",
+];
+
+add_task(async function() {
+  let tabs = [];
+  for (let url of TEST_URLS) {
+    tabs.push(await BrowserTestUtils.openNewForegroundTab(gBrowser, url));
+  }
+  registerCleanupFunction(async function() {
+    for (let tab of tabs) {
+      await BrowserTestUtils.removeTab(tab)
+    }
+  });
+
+  await withBookmarksDialog(true,
+    function open() {
+      document.getElementById("Browser:BookmarkAllTabs").doCommand();
+    },
+    async dialog => {
+      let acceptBtn = dialog.document.documentElement.getButton("accept");
+      ok(!acceptBtn.disabled, "Accept button is enabled");
+
+      let namepicker = dialog.document.getElementById("editBMPanel_namePicker");
+      Assert.ok(!namepicker.readOnly, "Name field is writable");
+      let folderName = dialog.document.getElementById("stringBundle").getString("bookmarkAllTabsDefault");
+      Assert.equal(namepicker.value, folderName, "Name field is correct.");
+
+      let promiseTitleChange = promiseBookmarksNotification(
+        "onItemChanged", (id, prop, isAnno, val) => prop == "title" && val == "folder");
+      fillBookmarkTextField("editBMPanel_namePicker", "folder", dialog);
+      await promiseTitleChange;
+    },
+    dialog => {
+      let savedItemId = dialog.gEditItemOverlay.itemId;
+      ok(savedItemId > 0, "Found the itemId");
+      return PlacesTestUtils.waitForNotification("onItemRemoved",
+                                                 id => id === savedItemId);
+    }
+  );
+});
--- a/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
+++ b/browser/components/places/tests/browser/browser_markPageAsFollowedLink.js
@@ -45,23 +45,19 @@ add_task(async function test() {
 
   // Wait for the right frame visit to be registered.
   info("Waiting right frame visit");
   await deferredRightFrameVisit.promise;
 
   await BrowserTestUtils.removeTab(tab);
 });
 
-async function getTransitionForUrl(url) {
-  // Ensure all the transactions completed.
-  await PlacesTestUtils.promiseAsyncUpdates();
-  let db = await PlacesUtils.promiseDBConnection();
-  let rows = await db.execute(`
-    SELECT visit_type
-    FROM moz_historyvisits
-    JOIN moz_places h ON place_id = h.id
-    WHERE url_hash = hash(:url) AND url = :url`,
-    { url });
-  if (rows.length) {
-    return rows[0].getResultByName("visit_type");
-  }
-  return null;
+function getTransitionForUrl(url) {
+  return PlacesUtils.withConnectionWrapper("browser_markPageAsFollowedLink", async db => {
+    let rows = await db.execute(`
+      SELECT visit_type
+      FROM moz_historyvisits
+      JOIN moz_places h ON place_id = h.id
+      WHERE url_hash = hash(:url) AND url = :url
+      `, { url });
+    return rows.length ? rows[0].getResultByName("visit_type") : null;
+  });
 }
--- a/browser/components/preferences/in-content-new/findInPage.js
+++ b/browser/components/preferences/in-content-new/findInPage.js
@@ -220,16 +220,21 @@ var gSearchResultsPane = {
    *    to search for filted query in
    */
   searchFunction(event) {
     this.query = event.target.value.trim().toLowerCase();
     this.getFindSelection(window).removeAllRanges();
     this.removeAllSearchTooltips();
     this.removeAllSearchMenuitemIndicators();
 
+    // Clear telemetry request if user types very frequently.
+    if (this.telemetryTimer) {
+      clearTimeout(this.telemetryTimer);
+    }
+
     let srHeader = document.getElementById("header-searchResults");
 
     if (this.query) {
       // Showing the Search Results Tag
       gotoPref("paneSearchResults");
 
       let resultsFound = false;
 
@@ -269,16 +274,23 @@ var gSearchResultsPane = {
         let helpUrl = Services.urlFormatter.formatURLPref("app.support.baseURL") + "preferences";
         let brandName = document.getElementById("bundleBrand").getString("brandShortName");
         // eslint-disable-next-line no-unsanitized/property
         document.getElementById("need-help").innerHTML =
           strings.getFormattedString("searchResults.needHelp2", [helpUrl, brandName]);
       } else {
         // Creating tooltips for all the instances found
         this.listSearchTooltips.forEach((anchorNode) => this.createSearchTooltip(anchorNode, this.query));
+
+        // Implant search telemetry probe after user stops typing for a while
+        if (this.query.length >= 2) {
+          this.telemetryTimer = setTimeout(() => {
+            Services.telemetry.keyedScalarAdd("preferences.search_query", this.query, 1);
+          }, 1000);
+        }
       }
     } else {
       document.getElementById("sorry-message").textContent = "";
       // Going back to General when cleared
       gotoPref("paneGeneral");
     }
   },
 
--- a/browser/extensions/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -112,18 +112,18 @@ FormAutofillParent.prototype = {
 
       case "nsPref:changed": {
         // Observe pref changes and update _active cache if status is changed.
         this._updateStatus();
         break;
       }
 
       case "formautofill-storage-changed": {
-        // Early exit if the action is not "add" nor "remove"
-        if (data != "add" && data != "remove") {
+        // Early exit if only metadata is changed
+        if (data == "notifyUsed") {
           break;
         }
 
         this._updateSavedFieldNames();
         break;
       }
 
       default: {
--- a/browser/extensions/formautofill/test/unit/test_activeStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_activeStatus.js
@@ -42,32 +42,28 @@ add_task(async function test_activeStatu
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 
   // _active != _computeStatus() => Need to trigger _onStatusChanged
   formAutofillParent._computeStatus.returns(false);
   formAutofillParent._onStatusChanged.reset();
   formAutofillParent.observe(null, "nsPref:changed", "extensions.formautofill.addresses.enabled");
   do_check_eq(formAutofillParent._onStatusChanged.called, true);
 
-  // profile added => Need to trigger _onStatusChanged
+  // profile changed => Need to trigger _onStatusChanged
+  ["add", "update", "remove", "reconcile", "merge"].forEach(event => {
+    formAutofillParent._computeStatus.returns(!formAutofillParent._active);
+    formAutofillParent._onStatusChanged.reset();
+    formAutofillParent.observe(null, "formautofill-storage-changed", event);
+    do_check_eq(formAutofillParent._onStatusChanged.called, true);
+  });
+
+  // profile metadata updated => No need to trigger _onStatusChanged
   formAutofillParent._computeStatus.returns(!formAutofillParent._active);
   formAutofillParent._onStatusChanged.reset();
-  formAutofillParent.observe(null, "formautofill-storage-changed", "add");
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-
-  // profile removed => Need to trigger _onStatusChanged
-  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
-  formAutofillParent._onStatusChanged.reset();
-  formAutofillParent.observe(null, "formautofill-storage-changed", "remove");
-  do_check_eq(formAutofillParent._onStatusChanged.called, true);
-
-  // profile updated => no need to trigger _onStatusChanged
-  formAutofillParent._computeStatus.returns(!formAutofillParent._active);
-  formAutofillParent._onStatusChanged.reset();
-  formAutofillParent.observe(null, "formautofill-storage-changed", "update");
+  formAutofillParent.observe(null, "formautofill-storage-changed", "notifyUsed");
   do_check_eq(formAutofillParent._onStatusChanged.called, false);
 });
 
 add_task(async function test_activeStatus_computeStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -19,28 +19,25 @@ add_task(async function test_profileSave
 });
 
 add_task(async function test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
   await formAutofillParent.init();
 
-  // profile added => Need to trigger updateValidFields
-  formAutofillParent.observe(null, "formautofill-storage-changed", "add");
-  do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
+  // profile changed => Need to trigger updateValidFields
+  ["add", "update", "remove", "reconcile", "merge"].forEach(event => {
+    formAutofillParent.observe(null, "formautofill-storage-changed", "add");
+    do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
+  });
 
-  // profile removed => Need to trigger updateValidFields
+  // profile metadata updated => no need to trigger updateValidFields
   formAutofillParent._updateSavedFieldNames.reset();
-  formAutofillParent.observe(null, "formautofill-storage-changed", "remove");
-  do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
-
-  // profile updated => no need to trigger updateValidFields
-  formAutofillParent._updateSavedFieldNames.reset();
-  formAutofillParent.observe(null, "formautofill-storage-changed", "update");
+  formAutofillParent.observe(null, "formautofill-storage-changed", "notifyUsed");
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, false);
 });
 
 add_task(async function test_profileSavedFieldNames_update() {
   let formAutofillParent = new FormAutofillParent();
   await formAutofillParent.init();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
--- a/browser/themes/shared/compacttheme.inc.css
+++ b/browser/themes/shared/compacttheme.inc.css
@@ -14,16 +14,19 @@
 %endif
   --toolbarbutton-text-shadow: none;
   --backbutton-urlbar-overlap: 0px;
   /* 18px icon + 2 * 5px padding + 1 * 1px border */
   --forwardbutton-width: 29px;
 }
 
 :root:-moz-lwtheme-brighttext {
+
+%ifndef MOZ_PHOTON_THEME
+
   /* Chrome */
   --chrome-background-color: #272b35;
   --chrome-color: #F5F7FA;
   --chrome-secondary-background-color: #393F4C;
   --chrome-navigator-toolbox-separator-color: rgba(0,0,0,.2);
   --chrome-nav-bar-separator-color: rgba(0,0,0,.2);
   --chrome-nav-buttons-background: #252C33;
   --chrome-nav-buttons-hover-background: #1B2127;
@@ -32,22 +35,50 @@
   --chrome-selection-background-color: #5675B9;
 
   /* Tabs */
   --tab-background-color: #272b35;
   --tab-hover-background-color: #07090a;
   --tab-selection-color: #f5f7fa;
   --tab-selection-background-color: #5675B9;
   --tab-selection-box-shadow: none;
-%ifndef MOZ_PHOTON_THEME
+
   --pinned-tab-glow: radial-gradient(22px at center calc(100% - 2px), rgba(76,158,217,0.9) 13%, rgba(0,0,0,0.4) 16%, transparent 70%);
+
+
+%else
+
+  /* Chrome */
+  --chrome-background-color: hsl(240, 5%, 5%);
+  --chrome-color: #F5F7FA;
+  --chrome-secondary-background-color: hsl(240, 1%, 20%);
+  --chrome-navigator-toolbox-separator-color: hsla(240, 5%, 5%, .1);
+  --chrome-nav-bar-separator-color: rgba(0,0,0,.2);
+  --chrome-nav-buttons-background: hsla(240, 5%, 5%, .1);
+  --chrome-nav-buttons-hover-background: hsla(240, 5%, 5%, .15);
+  --chrome-nav-bar-controls-border-color: hsla(240, 5%, 5%, .3);
+  --chrome-selection-color: #fff;
+  --chrome-selection-background-color: #5675B9;
+
+  /* Tabs */
+  --tab-hover-background-color: hsla(240, 9%, 98%, .1);
+  --tab-selection-color: #f5f7fa;
+  --tab-selection-background-color: hsl(240, 1%, 20%);
+  --tab-selection-box-shadow: none;
+
+  --toolbarbutton-icon-fill-inverted: rgba(249, 249, 250, .7);
+
 %endif
 
   /* Url and search bars */
+%ifndef MOZ_PHOTON_THEME
   --url-and-searchbar-background-color: #171B1F;
+%else
+  --url-and-searchbar-background-color: hsla(0, 0%, 100%, .1);
+%endif
   --urlbar-separator-color: #5F6670;
   --urlbar-dropmarker-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
   --urlbar-dropmarker-region: rect(0px, 11px, 14px, 0px);
   --urlbar-dropmarker-hover-region: rect(0, 22px, 14px, 11px);
   --urlbar-dropmarker-active-region: rect(0px, 33px, 14px, 22px);
   --urlbar-dropmarker-2x-url: url("chrome://browser/skin/compacttheme/urlbar-history-dropmarker.svg");
   --urlbar-dropmarker-2x-region: rect(0px, 11px, 14px, 0px);
   --urlbar-dropmarker-hover-2x-region: rect(0, 22px, 14px, 11px);
--- a/build/gyp.mozbuild
+++ b/build/gyp.mozbuild
@@ -15,17 +15,17 @@ gyp_vars.update({
     'build_with_mozilla': 1,
     'build_with_chromium': 0,
     # 10.9 once we move to TC cross-compiles - bug 1270217
     'mac_sdk_min': '10.7',
     'mac_deployment_target': '10.7',
     'use_official_google_api_keys': 0,
     'have_clock_monotonic': 1 if CONFIG['HAVE_CLOCK_MONOTONIC'] else 0,
     'have_ethtool_cmd_speed_hi': 1 if CONFIG['MOZ_WEBRTC_HAVE_ETHTOOL_SPEED_HI'] else 0,
-    'include_alsa_audio': 1 if CONFIG['MOZ_ALSA'] else 0,
+    'include_alsa_audio': 0,
     'include_pulse_audio': 1 if CONFIG['MOZ_PULSEAUDIO'] else 0,
     # basic stuff for everything
     'include_internal_video_render': 0,
     'clang': 1 if CONFIG['CLANG_CXX'] else 0,
     'clang_use_chrome_plugins': 0,
     'enable_protobuf': 0,
     'include_tests': 0,
     'enable_android_opensl': 1,
--- a/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_scrubber_movable.js
@@ -45,16 +45,21 @@ add_task(function* () {
   info("Try to drag the scrubber handle and check that the scrubber moves");
   let onDataChanged = timeline.once("timeline-data-changed");
   EventUtils.synthesizeMouse(scrubberHandleEl, 1, 20, {type: "mousedown"}, win);
   EventUtils.synthesizeMouse(timeHeaderEl, 0, 0, {type: "mousemove"}, win);
   EventUtils.synthesizeMouse(timeHeaderEl, 0, 0, {type: "mouseup"}, win);
   yield onDataChanged;
 
   checkScrubberIsAt(scrubberEl, timeHeaderEl, 0);
+
+  // Wait for promise of setCurrentTimes if setCurrentTimes is running.
+  if (panel.setCurrentTimeAllPromise) {
+    yield panel.setCurrentTimeAllPromise;
+  }
 });
 
 function* synthesizeInHeaderAndWaitForChange(timeline, x, y, type) {
   let onDataChanged = timeline.once("timeline-data-changed");
   EventUtils.synthesizeMouse(timeline.timeHeaderEl, x, y, {type}, timeline.win);
   yield onDataChanged;
 }
 
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -241,16 +241,27 @@ void
 TextComposition::DispatchCompositionEvent(
                    WidgetCompositionEvent* aCompositionEvent,
                    nsEventStatus* aStatus,
                    EventDispatchingCallback* aCallBack,
                    bool aIsSynthesized)
 {
   mWasCompositionStringEmpty = mString.IsEmpty();
 
+  // If this instance has requested to commit or cancel composition but
+  // is not synthesizing commit event, that means that the IME commits or
+  // cancels the composition asynchronously.  Typically, iBus behaves so.
+  // Then, synthesized events which were dispatched immediately after
+  // the request has already committed our editor's composition string and
+  // told it to web apps.  Therefore, we should ignore the delayed events.
+  if (mRequestedToCommitOrCancel && !aIsSynthesized) {
+    *aStatus = nsEventStatus_eConsumeNoDefault;
+    return;
+  }
+
   // If the content is a container of TabParent, composition should be in the
   // remote process.
   if (mTabParent) {
     Unused << mTabParent->SendCompositionEvent(*aCompositionEvent);
     aCompositionEvent->StopPropagation();
     if (aCompositionEvent->CausesDOMTextEvent()) {
       mLastData = aCompositionEvent->mData;
       mLastRanges = aCompositionEvent->mRanges;
@@ -289,27 +300,16 @@ TextComposition::DispatchCompositionEven
     aCompositionEvent->mRanges = nullptr;
   }
 
   if (!IsValidStateForComposition(aCompositionEvent->mWidget)) {
     *aStatus = nsEventStatus_eConsumeNoDefault;
     return;
   }
 
-  // If this instance has requested to commit or cancel composition but
-  // is not synthesizing commit event, that means that the IME commits or
-  // cancels the composition asynchronously.  Typically, iBus behaves so.
-  // Then, synthesized events which were dispatched immediately after
-  // the request has already committed our editor's composition string and
-  // told it to web apps.  Therefore, we should ignore the delayed events.
-  if (mRequestedToCommitOrCancel && !aIsSynthesized) {
-    *aStatus = nsEventStatus_eConsumeNoDefault;
-    return;
-  }
-
   // IME may commit composition with empty string for a commit request or
   // with non-empty string for a cancel request.  We should prevent such
   // unexpected result.  E.g., web apps may be confused if they implement
   // autocomplete which attempts to commit composition forcibly when the user
   // selects one of suggestions but composition string is cleared by IME.
   // Note that most Chinese IMEs don't expose actual composition string to us.
   // They typically tell us an IDEOGRAPHIC SPACE or empty string as composition
   // string.  Therefore, we should hack it only when:
--- a/dom/gamepad/GamepadManager.cpp
+++ b/dom/gamepad/GamepadManager.cpp
@@ -636,47 +636,54 @@ GamepadManager::SetGamepadByEvent(const 
   return ret;
 }
 
 already_AddRefed<Promise>
 GamepadManager::VibrateHaptic(uint32_t aControllerIdx, uint32_t aHapticIndex,
                               double aIntensity, double aDuration,
                               nsIGlobalObject* aGlobal, ErrorResult& aRv)
 {
+  const char* kGamepadHapticEnabledPref = "dom.gamepad.haptic_feedback.enabled";
   RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-
-  if (aControllerIdx >= VR_GAMEPAD_IDX_OFFSET) {
-    if (gfx::VRManagerChild::IsCreated()) {
-      const uint32_t index = aControllerIdx - VR_GAMEPAD_IDX_OFFSET;
-      gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
-      vm->AddPromise(mPromiseID, promise);
-      vm->SendVibrateHaptic(index, aHapticIndex,
-                            aIntensity, aDuration,
-                            mPromiseID);
-    }
-  } else {
-    for (const auto& channelChild: mChannelChildren) {
-      channelChild->AddPromise(mPromiseID, promise);
-      channelChild->SendVibrateHaptic(aControllerIdx, aHapticIndex,
-                                      aIntensity, aDuration,
-                                      mPromiseID);
+  if (Preferences::GetBool(kGamepadHapticEnabledPref)) {
+    if (aControllerIdx >= VR_GAMEPAD_IDX_OFFSET) {
+      if (gfx::VRManagerChild::IsCreated()) {
+        const uint32_t index = aControllerIdx - VR_GAMEPAD_IDX_OFFSET;
+        gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
+        vm->AddPromise(mPromiseID, promise);
+        vm->SendVibrateHaptic(index, aHapticIndex,
+                              aIntensity, aDuration,
+                              mPromiseID);
+      }
+    } else {
+      for (const auto& channelChild: mChannelChildren) {
+        channelChild->AddPromise(mPromiseID, promise);
+        channelChild->SendVibrateHaptic(aControllerIdx, aHapticIndex,
+                                        aIntensity, aDuration,
+                                        mPromiseID);
+      }
     }
   }
 
   ++mPromiseID;
   return promise.forget();
 }
 
 void
 GamepadManager::StopHaptics()
 {
+  const char* kGamepadHapticEnabledPref = "dom.gamepad.haptic_feedback.enabled";
+  if (!Preferences::GetBool(kGamepadHapticEnabledPref)) {
+    return;
+  }
+
   for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
     const uint32_t gamepadIndex = iter.UserData()->HashKey();
     if (gamepadIndex >= VR_GAMEPAD_IDX_OFFSET) {
       if (gfx::VRManagerChild::IsCreated()) {
         const uint32_t index = gamepadIndex - VR_GAMEPAD_IDX_OFFSET;
         gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
         vm->SendStopVibrateHaptic(index);
       }
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -603,17 +603,18 @@ RemoteDataDecoder::ReturnDecodedData()
 {
   AssertOnTaskQueue();
   MOZ_ASSERT(!mShutdown);
 
   // We only want to clear mDecodedData when we have resolved the promises.
   if (!mDecodePromise.IsEmpty()) {
     mDecodePromise.Resolve(mDecodedData, __func__);
     mDecodedData.Clear();
-  } else if (!mDrainPromise.IsEmpty()) {
+  } else if (!mDrainPromise.IsEmpty() &&
+             (!mDecodedData.IsEmpty() || mDrainStatus == DrainStatus::DRAINED)) {
     mDrainPromise.Resolve(mDecodedData, __func__);
     mDecodedData.Clear();
   }
 }
 
 void
 RemoteDataDecoder::DrainComplete()
 {
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1716,17 +1716,17 @@ needs-focus != 703186-1.html 703186-2.ht
 == 714519-2-q.html 714519-2-ref.html
 fuzzy-if(true,1,21) fuzzy-if(d2d,68,173) fuzzy-if(cocoaWidget,1,170) == 718521.html 718521-ref.html # bug 773482
 == 720987.html 720987-ref.html
 == 722888-1.html 722888-1-ref.html
 fuzzy(2,40000) == 722923-1.html 722923-1-ref.html
 == 723484-1.html 723484-1-ref.html
 random-if(Android) == 728983-1.html 728983-1-ref.html
 == 729143-1.html 729143-1-ref.html
-fails-if(styloVsGecko||stylo) == 731521-1.html 731521-1-ref.html
+== 731521-1.html 731521-1-ref.html
 needs-focus == 731726-1.html 731726-1-ref.html
 == 735481-1.html 735481-1-ref.html
 fuzzy-if(cocoaWidget,1,300000) fuzzy-if(skiaContent,2,300000) == 745934-1.html 745934-1-ref.html
 == 748692-1a.html 748692-1-ref.html
 == 748692-1b.html 748692-1-ref.html
 == 748803-1.html 748803-1-ref.html
 == 750551-1.html 750551-1-ref.html
 fuzzy-if(skiaContent,1,1) == 751012-1a.html 751012-1-ref.html
--- a/layout/style/RuleNodeCacheConditions.cpp
+++ b/layout/style/RuleNodeCacheConditions.cpp
@@ -21,17 +21,17 @@ bool
 RuleNodeCacheConditions::Matches(nsStyleContext* aStyleContext) const
 {
   MOZ_ASSERT(Cacheable());
   if ((mBits & eHaveFontSize) &&
       mFontSize != aStyleContext->StyleFont()->mFont.size) {
     return false;
   }
   if ((mBits & eHaveWritingMode) &&
-      (GetWritingMode() != WritingMode(aStyleContext).GetBits())) {
+      (mWritingMode != WritingMode(aStyleContext).GetBits())) {
     return false;
   }
   return true;
 }
 
 #ifdef DEBUG
 void
 RuleNodeCacheConditions::List() const
@@ -41,13 +41,13 @@ RuleNodeCacheConditions::List() const
   if (mBits & eHaveFontSize) {
     printf("FontSize(%d)", mFontSize);
     first = false;
   }
   if (mBits & eHaveWritingMode) {
     if (!first) {
       printf(", ");
     }
-    printf("WritingMode(0x%x)", GetWritingMode());
+    printf("WritingMode(0x%x)", mWritingMode);
   }
   printf(" }");
 }
 #endif
--- a/layout/style/RuleNodeCacheConditions.h
+++ b/layout/style/RuleNodeCacheConditions.h
@@ -35,30 +35,42 @@ namespace mozilla {
  * certain common conditions.  For these structs, setting conditions
  * (SetFontSizeDependency, SetWritingModeDependency) instead causes the
  * struct to be stored, with the condition, in the rule tree.
  */
 class RuleNodeCacheConditions
 {
 public:
   RuleNodeCacheConditions()
-    : mFontSize(0), mBits(0) {}
+    : mFontSize(0)
+    , mBits(0)
+    , mWritingMode(0)
+  {}
+
   RuleNodeCacheConditions(const RuleNodeCacheConditions& aOther)
-    : mFontSize(aOther.mFontSize), mBits(aOther.mBits) {}
+    : mFontSize(aOther.mFontSize)
+    , mBits(aOther.mBits)
+    , mWritingMode(aOther.mWritingMode)
+  {}
+
   RuleNodeCacheConditions& operator=(const RuleNodeCacheConditions& aOther)
   {
     mFontSize = aOther.mFontSize;
     mBits = aOther.mBits;
+    mWritingMode = aOther.mWritingMode;
     return *this;
   }
+
   bool operator==(const RuleNodeCacheConditions& aOther) const
   {
     return mFontSize == aOther.mFontSize &&
-           mBits == aOther.mBits;
+           mBits == aOther.mBits &&
+           mWritingMode == aOther.mWritingMode;
   }
+
   bool operator!=(const RuleNodeCacheConditions& aOther) const
   {
     return !(*this == aOther);
   }
 
   bool Matches(nsStyleContext* aStyleContext) const;
 
   /**
@@ -80,19 +92,19 @@ public:
   /**
    * Record that the data being computed depend on the writing mode of
    * the element for which they are being computed, which in turn
    * depends on its 'writing-mode', 'direction', and 'text-orientation'
    * properties.
    */
   void SetWritingModeDependency(uint8_t aWritingMode)
   {
-    MOZ_ASSERT(!(mBits & eHaveWritingMode) || GetWritingMode() == aWritingMode);
-    mBits |= (static_cast<uint64_t>(aWritingMode) << eWritingModeShift) |
-             eHaveWritingMode;
+    MOZ_ASSERT(!(mBits & eHaveWritingMode) || mWritingMode == aWritingMode);
+    mWritingMode = aWritingMode;
+    mBits |= eHaveWritingMode;
   }
 
   void SetUncacheable()
   {
     mBits |= eUncacheable;
   }
 
   void Clear()
@@ -102,55 +114,43 @@ public:
 
   bool Cacheable() const
   {
     return !(mBits & eUncacheable);
   }
 
   bool CacheableWithDependencies() const
   {
-    return !(mBits & eUncacheable) &&
-           (mBits & eHaveBitsMask) != 0;
+    return Cacheable() && mBits;
   }
 
   bool CacheableWithoutDependencies() const
   {
     // We're not uncacheable and we have don't have a font-size or
     // writing mode value.
-    return (mBits & eHaveBitsMask) == 0;
+    return mBits == 0;
   }
 
 #ifdef DEBUG
   void List() const;
 #endif
 
 private:
   enum {
-    eUncacheable      = 0x0001,
-    eHaveFontSize     = 0x0002,
-    eHaveWritingMode  = 0x0004,
-    eHaveBitsMask     = 0x00ff,
-    eWritingModeMask  = 0xff00,
-    eWritingModeShift = 8,
+    eUncacheable      = 1 << 0,
+    eHaveFontSize     = 1 << 1,
+    eHaveWritingMode  = 1 << 2,
   };
 
-  uint8_t GetWritingMode() const
-  {
-    return static_cast<uint8_t>(
-        (mBits & eWritingModeMask) >> eWritingModeShift);
-  }
-
   // The font size from which em units are derived.
   nscoord mFontSize;
 
   // Values in mBits:
   //   bit 0:      are we set to "uncacheable"?
   //   bit 1:      do we have a font size value?
   //   bit 2:      do we have a writing mode value?
-  //   bits 3-7:   unused
-  //   bits 8-15:  writing mode (uint8_t)
-  //   bits 16-31: unused
-  uint32_t mBits;
+  uint8_t mBits;
+  uint8_t mWritingMode;
 };
 
 } // namespace mozilla
 
 #endif // !defined(RuleNodeCacheConditions_h_)
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was a329c6a4184de3ccedb264cf2c3b0903fc506b94 (2017-07-14 15:35:51 +0300)
+The git commit ID used was 09aeb59259487a2fbeeca1824f1f68f55b6787f4 (2017-07-22 09:54:44 +1200)
--- a/media/libcubeb/src/cubeb_audiounit.cpp
+++ b/media/libcubeb/src/cubeb_audiounit.cpp
@@ -102,30 +102,44 @@ to_string(io_side side)
   switch (side) {
   case INPUT:
     return "input";
   case OUTPUT:
     return "output";
   }
 }
 
+typedef uint32_t device_flags_value;
+
+enum device_flags {
+  DEV_UKNOWN            = 0x00, /* Unkown */
+  DEV_INPUT             = 0x01, /* Record device like mic */
+  DEV_OUTPUT            = 0x02, /* Playback device like speakers */
+  DEV_SYSTEM_DEFAULT    = 0x04, /* System default device */
+  DEV_SELECTED_DEFAULT  = 0x08, /* User selected to use the system default device */
+};
+
+struct device_info {
+  AudioDeviceID id = kAudioObjectUnknown;
+  device_flags_value flags = DEV_UKNOWN;
+};
+
 struct cubeb_stream {
   explicit cubeb_stream(cubeb * context);
 
   cubeb * context;
   cubeb_data_callback data_callback = nullptr;
   cubeb_state_callback state_callback = nullptr;
   cubeb_device_changed_callback device_changed_callback = nullptr;
   owned_critical_section device_changed_callback_lock;
   /* Stream creation parameters */
   cubeb_stream_params input_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
   cubeb_stream_params output_stream_params = { CUBEB_SAMPLE_FLOAT32NE, 0, 0, CUBEB_LAYOUT_UNDEFINED };
-  bool is_default_input;
-  AudioDeviceID input_device = 0;
-  AudioDeviceID output_device = 0;
+  device_info input_device;
+  device_info output_device;
   /* User pointer of data_callback */
   void * user_ptr = nullptr;
   /* Format descriptions */
   AudioStreamBasicDescription input_desc;
   AudioStreamBasicDescription output_desc;
   /* I/O AudioUnits */
   AudioUnit input_unit = nullptr;
   AudioUnit output_unit = nullptr;
@@ -549,97 +563,97 @@ audiounit_init(cubeb ** context, char co
 
 static char const *
 audiounit_get_backend_id(cubeb * /* ctx */)
 {
   return "audiounit";
 }
 
 #if !TARGET_OS_IPHONE
-static int
-audiounit_get_output_device_id(AudioDeviceID * device_id)
-{
-  UInt32 size;
-  OSStatus r;
-  AudioObjectPropertyAddress output_device_address = {
-    kAudioHardwarePropertyDefaultOutputDevice,
-    kAudioObjectPropertyScopeGlobal,
-    kAudioObjectPropertyElementMaster
-  };
-
-  size = sizeof(*device_id);
-
-  r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
-                                 &output_device_address,
-                                 0,
-                                 NULL,
-                                 &size,
-                                 device_id);
-  if (r != noErr) {
-    LOG("output_device_id rv=%d", r);
-    return CUBEB_ERROR;
-  }
-
-  return CUBEB_OK;
-}
-
-static int
-audiounit_get_input_device_id(AudioDeviceID * device_id)
-{
-  UInt32 size;
-  OSStatus r;
-  AudioObjectPropertyAddress input_device_address = {
-    kAudioHardwarePropertyDefaultInputDevice,
-    kAudioObjectPropertyScopeGlobal,
-    kAudioObjectPropertyElementMaster
-  };
-
-  size = sizeof(*device_id);
-
-  r = AudioObjectGetPropertyData(kAudioObjectSystemObject,
-                                 &input_device_address,
-                                 0,
-                                 NULL,
-                                 &size,
-                                 device_id);
-  if (r != noErr) {
-    return CUBEB_ERROR;
-  }
-
-  return CUBEB_OK;
-}
 
 static int audiounit_stream_get_volume(cubeb_stream * stm, float * volume);
 static int audiounit_stream_set_volume(cubeb_stream * stm, float volume);
 static int audiounit_uninstall_device_changed_callback(cubeb_stream * stm);
+static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
+
+static void
+audiounit_set_device_info(cubeb_stream * stm, AudioDeviceID id, io_side side)
+{
+  assert(stm);
+
+  device_info * info = nullptr;
+  cubeb_device_type type = CUBEB_DEVICE_TYPE_UNKNOWN;
+
+  if (side == INPUT) {
+    info = &stm->input_device;
+    type = CUBEB_DEVICE_TYPE_INPUT;
+  } else if (side == OUTPUT) {
+    info = &stm->output_device;
+    type = CUBEB_DEVICE_TYPE_OUTPUT;
+  }
+  memset(info, 0, sizeof(device_info));
+
+  if (side == INPUT) {
+    info->flags |= DEV_INPUT;
+  } else if (side == OUTPUT) {
+    info->flags |= DEV_OUTPUT;
+  }
+
+  AudioDeviceID default_device_id = audiounit_get_default_device_id(type);
+  if (id == kAudioObjectUnknown) {
+    info->id = default_device_id;
+    info->flags |= DEV_SELECTED_DEFAULT;
+  }
+
+  if (info->id == default_device_id) {
+    info->flags |= DEV_SYSTEM_DEFAULT;
+  }
+
+  assert(info->id);
+  assert(info->flags & DEV_INPUT && !(info->flags & DEV_OUTPUT) ||
+           !(info->flags & DEV_INPUT) && info->flags & DEV_OUTPUT);
+}
+
 
 static int
-audiounit_reinit_stream(cubeb_stream * stm)
+audiounit_reinit_stream(cubeb_stream * stm, device_flags_value flags)
 {
   auto_lock context_lock(stm->context->mutex);
-  assert(stm->input_unit || stm->output_unit);
+  assert((flags & DEV_INPUT && stm->input_unit) ||
+         (flags & DEV_OUTPUT && stm->output_unit));
   if (!stm->shutdown) {
     audiounit_stream_stop_internal(stm);
   }
 
   int r = audiounit_uninstall_device_changed_callback(stm);
   if (r != CUBEB_OK) {
-    LOG("(%p) Could not uninstall the device changed callback", stm);
+    LOG("(%p) Could not uninstall all device change listeners.", stm);
   }
 
   {
     auto_lock lock(stm->mutex);
     float volume = 0.0;
     int vol_rv = CUBEB_ERROR;
     if (stm->output_unit) {
       vol_rv = audiounit_stream_get_volume(stm, &volume);
     }
 
     audiounit_close_stream(stm);
 
+    /* Reinit occurs in 2 cases. When the device is not alive any more and when the
+     * default system device change. In both cases cubeb switch on the new default
+     * device. This is considered the most expected behavior for the user. */
+    if (flags & DEV_INPUT) {
+      audiounit_set_device_info(stm, 0, INPUT);
+    }
+    /* Always use the default output on reinit. This is not correct in every case
+     * but it is sufficient for Firefox and prevent reinit from reporting failures.
+     * It will change soon when reinit mechanism will be updated. */
+    audiounit_set_device_info(stm, 0, OUTPUT);
+
     if (audiounit_setup_stream(stm) != CUBEB_OK) {
       LOG("(%p) Stream reinit failed.", stm);
       return CUBEB_ERROR;
     }
 
     if (vol_rv == CUBEB_OK) {
       audiounit_stream_set_volume(stm, volume);
     }
@@ -658,42 +672,43 @@ audiounit_reinit_stream(cubeb_stream * s
 
 static OSStatus
 audiounit_property_listener_callback(AudioObjectID /* id */, UInt32 address_count,
                                      const AudioObjectPropertyAddress * addresses,
                                      void * user)
 {
   cubeb_stream * stm = (cubeb_stream*) user;
   stm->switching_device = true;
+  device_flags_value switch_side = DEV_UKNOWN;
 
   LOG("(%p) Audio device changed, %u events.", stm, (unsigned int) address_count);
   for (UInt32 i = 0; i < address_count; i++) {
     switch(addresses[i].mSelector) {
       case kAudioHardwarePropertyDefaultOutputDevice: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultOutputDevice", (unsigned int) i);
           // Allow restart to choose the new default
-          stm->output_device = 0;
+          switch_side |= DEV_OUTPUT;
         }
         break;
       case kAudioHardwarePropertyDefaultInputDevice: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDefaultInputDevice", (unsigned int) i);
           // Allow restart to choose the new default
-          stm->input_device = 0;
+          switch_side |= DEV_INPUT;
         }
       break;
       case kAudioDevicePropertyDeviceIsAlive: {
           LOG("Event[%u] - mSelector == kAudioDevicePropertyDeviceIsAlive", (unsigned int) i);
           // If this is the default input device ignore the event,
           // kAudioHardwarePropertyDefaultInputDevice will take care of the switch
-          if (stm->is_default_input) {
+          if (stm->input_device.flags & DEV_SYSTEM_DEFAULT) {
             LOG("It's the default input device, ignore the event");
             return noErr;
           }
           // Allow restart to choose the new default. Event register only for input.
-          stm->input_device = 0;
+          switch_side |= DEV_INPUT;
         }
         break;
       case kAudioDevicePropertyDataSource: {
           LOG("Event[%u] - mSelector == kAudioHardwarePropertyDataSource", (unsigned int) i);
           return noErr;
         }
       default:
         LOG("Event[%u] - mSelector == Unexpected Event id %d, return", (unsigned int) i, addresses[i].mSelector);
@@ -715,17 +730,17 @@ audiounit_property_listener_callback(Aud
         break;
       }
     }
   }
 
   // Use a new thread, through the queue, to avoid deadlock when calling
   // Get/SetProperties method from inside notify callback
   dispatch_async(stm->context->serial_queue, ^() {
-    if (audiounit_reinit_stream(stm) != CUBEB_OK) {
+    if (audiounit_reinit_stream(stm, switch_side) != CUBEB_OK) {
       stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_STOPPED);
       LOG("(%p) Could not reopen the stream after switching.", stm);
     }
     stm->switching_device = false;
   });
 
   return noErr;
 }
@@ -753,76 +768,53 @@ audiounit_remove_listener(cubeb_stream *
       selector,
       scope,
       kAudioObjectPropertyElementMaster
   };
 
   return AudioObjectRemovePropertyListener(id, &address, listener, stm);
 }
 
-static AudioObjectID audiounit_get_default_device_id(cubeb_device_type type);
-
-static AudioObjectID
-audiounit_get_input_device_id(cubeb_stream * stm)
-{
-  AudioObjectID input_dev = stm->input_device ? stm->input_device :
-                            audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
-  assert(input_dev);
-  return input_dev;
-}
-
 static int
 audiounit_install_device_changed_callback(cubeb_stream * stm)
 {
-  OSStatus r;
+  OSStatus rv;
+  int r = CUBEB_OK;
 
   if (stm->output_unit) {
     /* This event will notify us when the data source on the same device changes,
      * for example when the user plugs in a normal (non-usb) headset in the
      * headphone jack. */
-    AudioDeviceID output_dev_id;
-    r = audiounit_get_output_device_id(&output_dev_id);
-    if (r != noErr) {
-      return CUBEB_ERROR;
-    }
-
-    r = audiounit_add_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
+    rv = audiounit_add_listener(stm, stm->output_device.id, kAudioDevicePropertyDataSource,
         kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d", r);
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectAddPropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id);
+      r = CUBEB_ERROR;
     }
   }
 
   if (stm->input_unit) {
     /* This event will notify us when the data source on the input device changes. */
-    AudioDeviceID input_dev_id;
-    r = audiounit_get_input_device_id(&input_dev_id);
-    if (r != noErr) {
-      return CUBEB_ERROR;
-    }
-
-    r = audiounit_add_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
+    rv = audiounit_add_listener(stm, stm->input_device.id, kAudioDevicePropertyDataSource,
         kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d", r);
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id);
+      r = CUBEB_ERROR;
     }
 
     /* Event to notify when the input is going away. */
-    AudioDeviceID dev = audiounit_get_input_device_id(stm);
-    r = audiounit_add_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
+    rv = audiounit_add_listener(stm, stm->input_device.id, kAudioDevicePropertyDeviceIsAlive,
         kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r);
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectAddPropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id =%d", rv, stm->input_device.id);
+      r = CUBEB_ERROR;
     }
   }
 
-  return CUBEB_OK;
+  return r;
 }
 
 static int
 audiounit_install_system_changed_callback(cubeb_stream * stm)
 {
   OSStatus r;
 
   if (stm->output_unit) {
@@ -849,54 +841,45 @@ audiounit_install_system_changed_callbac
   }
 
   return CUBEB_OK;
 }
 
 static int
 audiounit_uninstall_device_changed_callback(cubeb_stream * stm)
 {
-  OSStatus r;
+  OSStatus rv;
+  // Failing to uninstall listeners is not a fatal error.
+  int r = CUBEB_OK;
 
   if (stm->output_unit) {
-    AudioDeviceID output_dev_id;
-    r = audiounit_get_output_device_id(&output_dev_id);
-    if (r != noErr) {
-      return CUBEB_ERROR;
-    }
-
-    r = audiounit_remove_listener(stm, output_dev_id, kAudioDevicePropertyDataSource,
+    rv = audiounit_remove_listener(stm, stm->output_device.id, kAudioDevicePropertyDataSource,
         kAudioDevicePropertyScopeOutput, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectRemovePropertyListener/output/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->output_device.id);
+      r = CUBEB_ERROR;
     }
   }
 
   if (stm->input_unit) {
-    AudioDeviceID input_dev_id;
-    r = audiounit_get_input_device_id(&input_dev_id);
-    if (r != noErr) {
-      return CUBEB_ERROR;
-    }
-
-    r = audiounit_remove_listener(stm, input_dev_id, kAudioDevicePropertyDataSource,
+    rv = audiounit_remove_listener(stm, stm->input_device.id, kAudioDevicePropertyDataSource,
         kAudioDevicePropertyScopeInput, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDataSource rv=%d, device id=%d", rv, stm->input_device.id);
+      r = CUBEB_ERROR;
     }
 
-    AudioDeviceID dev = audiounit_get_input_device_id(stm);
-    r = audiounit_remove_listener(stm, dev, kAudioDevicePropertyDeviceIsAlive,
+    rv = audiounit_remove_listener(stm, stm->input_device.id, kAudioDevicePropertyDeviceIsAlive,
                                   kAudioObjectPropertyScopeGlobal, &audiounit_property_listener_callback);
-    if (r != noErr) {
-      LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d", r);
-      return CUBEB_ERROR;
+    if (rv != noErr) {
+      LOG("AudioObjectRemovePropertyListener/input/kAudioDevicePropertyDeviceIsAlive rv=%d, device id=%d", rv, stm->input_device.id);
+      r = CUBEB_ERROR;
     }
   }
-  return CUBEB_OK;
+  return r;
 }
 
 static int
 audiounit_uninstall_system_changed_callback(cubeb_stream * stm)
 {
   OSStatus r;
 
   if (stm->output_unit) {
@@ -925,17 +908,18 @@ audiounit_get_acceptable_latency_range(A
   OSStatus r;
   AudioDeviceID output_device_id;
   AudioObjectPropertyAddress output_device_buffer_size_range = {
     kAudioDevicePropertyBufferFrameSizeRange,
     kAudioDevicePropertyScopeOutput,
     kAudioObjectPropertyElementMaster
   };
 
-  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+  output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+  if (output_device_id == kAudioObjectUnknown) {
     LOG("Could not get default output device id.");
     return CUBEB_ERROR;
   }
 
   /* Get the buffer size range this device supports */
   size = sizeof(*latency_range);
 
   r = AudioObjectGetPropertyData(output_device_id,
@@ -990,17 +974,18 @@ audiounit_get_max_channel_count(cubeb * 
   AudioObjectPropertyAddress stream_format_address = {
     kAudioDevicePropertyStreamFormat,
     kAudioDevicePropertyScopeOutput,
     kAudioObjectPropertyElementMaster
   };
 
   assert(ctx && max_channels);
 
-  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+  output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+  if (output_device_id == kAudioObjectUnknown) {
     return CUBEB_ERROR;
   }
 
   size = sizeof(stream_format);
 
   r = AudioObjectGetPropertyData(output_device_id,
                                  &stream_format_address,
                                  0,
@@ -1051,17 +1036,18 @@ audiounit_get_preferred_sample_rate(cube
   Float64 fsamplerate;
   AudioDeviceID output_device_id;
   AudioObjectPropertyAddress samplerate_address = {
     kAudioDevicePropertyNominalSampleRate,
     kAudioObjectPropertyScopeGlobal,
     kAudioObjectPropertyElementMaster
   };
 
-  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+  output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+  if (output_device_id == kAudioObjectUnknown) {
     return CUBEB_ERROR;
   }
 
   size = sizeof(fsamplerate);
   r = AudioObjectGetPropertyData(output_device_id,
                                  &samplerate_address,
                                  0,
                                  NULL,
@@ -1138,17 +1124,18 @@ audiounit_get_current_channel_layout(Aud
 
 static cubeb_channel_layout
 audiounit_get_preferred_channel_layout()
 {
   OSStatus rv = noErr;
   UInt32 size = 0;
   AudioDeviceID id;
 
-  if (audiounit_get_output_device_id(&id) != CUBEB_OK) {
+  id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+  if (id == kAudioObjectUnknown) {
     return CUBEB_LAYOUT_UNDEFINED;
   }
 
   AudioObjectPropertyAddress adr = { kAudioDevicePropertyPreferredChannelLayout,
                                      kAudioDevicePropertyScopeOutput,
                                      kAudioObjectPropertyElementMaster };
   rv = AudioObjectGetPropertyDataSize(id, &adr, 0, NULL, &size);
   if (rv != noErr) {
@@ -1160,17 +1147,17 @@ audiounit_get_preferred_channel_layout()
   rv = AudioObjectGetPropertyData(id, &adr, 0, NULL, &size, layout.get());
   if (rv != noErr) {
     return CUBEB_LAYOUT_UNDEFINED;
   }
 
   return audiounit_convert_channel_layout(layout.get());
 }
 
-static int audiounit_create_unit(AudioUnit * unit, io_side side, AudioDeviceID device);
+static int audiounit_create_unit(AudioUnit * unit, device_info * device);
 
 static int
 audiounit_get_preferred_channel_layout(cubeb * ctx, cubeb_channel_layout * layout)
 {
   // The preferred layout is only returned when the connected sound device
   // (e.g. ASUS Xonar U7), has preferred layout setting.
   // For default output on Mac, there is no preferred channel layout,
   // so it might return UNDEFINED.
@@ -1184,18 +1171,23 @@ audiounit_get_preferred_channel_layout(c
     if (ctx->active_streams) {
       *layout = ctx->layout;
       return CUBEB_OK;
     }
 
     // If there is no existed stream, then we create a default ouput unit and
     // use it to get the current used channel layout.
     AudioUnit output_unit = nullptr;
-    audiounit_create_unit(&output_unit, OUTPUT, 0);
-    *layout = audiounit_get_current_channel_layout(output_unit);
+    device_info default_out_device;
+    default_out_device.id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+    default_out_device.flags = (DEV_OUTPUT | DEV_SYSTEM_DEFAULT);
+    if (default_out_device.id != kAudioObjectUnknown) {
+      audiounit_create_unit(&output_unit, &default_out_device);
+      *layout = audiounit_get_current_channel_layout(output_unit);
+    }
   }
 
   if (*layout == CUBEB_LAYOUT_UNDEFINED) {
     return CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
@@ -1634,17 +1626,17 @@ audiounit_activate_clock_drift_compensat
     if (rv != noErr) {
       LOG("AudioObjectSetPropertyData/kAudioSubDevicePropertyDriftCompensation, rv=%d", rv);
       return CUBEB_OK;
     }
   }
   return CUBEB_OK;
 }
 
-static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggregate_device_id);
+static int audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id);
 
 /*
  * Aggregate Device is a virtual audio interface which utilizes inputs and outputs
  * of one or more physical audio interfaces. It is possible to use the clock of
  * one of the devices as a master clock for all the combined devices and enable
  * drift compensation for the devices that are not designated clock master.
  *
  * Creating a new aggregate device programmatically requires [0][1]:
@@ -1661,48 +1653,46 @@ static int audiounit_destroy_aggregate_d
  * [2] CoreAudio.framework/Headers/AudioHardware.h
  * */
 static int
 audiounit_create_aggregate_device(cubeb_stream * stm)
 {
   int r = audiounit_create_blank_aggregate_device(&stm->plugin_id, &stm->aggregate_device_id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to create blank aggregate device", stm);
-    audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id);
+    audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return CUBEB_ERROR;
   }
 
-  AudioDeviceID input_device_id = audiounit_get_input_device_id(stm);
-  AudioDeviceID output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
-  r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, input_device_id, output_device_id);
+  r = audiounit_set_aggregate_sub_device_list(stm->aggregate_device_id, stm->input_device.id, stm->output_device.id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to set aggregate sub-device list", stm);
-    audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id);
+    audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return CUBEB_ERROR;
   }
 
   r = audiounit_set_master_aggregate_device(stm->aggregate_device_id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to set master sub-device for aggregate device", stm);
-    audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id);
+    audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return  CUBEB_ERROR;
   }
 
   r = audiounit_activate_clock_drift_compensation(stm->aggregate_device_id);
   if (r != CUBEB_OK) {
     LOG("(%p) Failed to activate clock drift compensation for aggregate device", stm);
-    audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id);
+    audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     return  CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
 
 static int
-audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID aggregate_device_id)
+audiounit_destroy_aggregate_device(AudioObjectID plugin_id, AudioDeviceID * aggregate_device_id)
 {
   AudioObjectPropertyAddress destroy_aggregate_device_addr = { kAudioPlugInDestroyAggregateDevice,
                                                                kAudioObjectPropertyScopeGlobal,
                                                                kAudioObjectPropertyElementMaster};
   UInt32 size;
   OSStatus rv = AudioObjectGetPropertyDataSize(plugin_id,
                                                &destroy_aggregate_device_addr,
                                                0,
@@ -1713,43 +1703,44 @@ audiounit_destroy_aggregate_device(Audio
     return CUBEB_ERROR;
   }
 
   rv = AudioObjectGetPropertyData(plugin_id,
                                   &destroy_aggregate_device_addr,
                                   0,
                                   NULL,
                                   &size,
-                                  &aggregate_device_id);
+                                  aggregate_device_id);
   if (rv != noErr) {
     LOG("AudioObjectGetPropertyData/kAudioPlugInDestroyAggregateDevice, rv=%d", rv);
     return CUBEB_ERROR;
   }
 
+  LOG("Destroyed aggregate device %d", *aggregate_device_id);
+  *aggregate_device_id = 0;
   return CUBEB_OK;
 }
 
 static int
-audiounit_new_unit_instance(AudioUnit * unit, io_side side, AudioDeviceID device)
+audiounit_new_unit_instance(AudioUnit * unit, device_info * device)
 {
   AudioComponentDescription desc;
   AudioComponent comp;
   OSStatus rv;
 
   desc.componentType = kAudioUnitType_Output;
 #if TARGET_OS_IPHONE
-  bool use_default_output = false;
   desc.componentSubType = kAudioUnitSubType_RemoteIO;
 #else
   // Use the DefaultOutputUnit for output when no device is specified
   // so we retain automatic output device switching when the default
   // changes.  Once we have complete support for device notifications
   // and switching, we can use the AUHAL for everything.
-  bool use_default_output = device == 0 && (side == OUTPUT);
-  if (use_default_output) {
+  if ((device->flags & DEV_SYSTEM_DEFAULT)
+      && (device->flags & DEV_OUTPUT)) {
     desc.componentSubType = kAudioUnitSubType_DefaultOutput;
   } else {
     desc.componentSubType = kAudioUnitSubType_HALOutput;
   }
 #endif
   desc.componentManufacturer = kAudioUnitManufacturer_Apple;
   desc.componentFlags = 0;
   desc.componentFlagsMask = 0;
@@ -1785,81 +1776,70 @@ audiounit_enable_unit_scope(AudioUnit * 
   if (rv != noErr) {
     LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_EnableIO rv=%d", rv);
     return CUBEB_ERROR;
   }
   return CUBEB_OK;
 }
 
 static int
-audiounit_create_unit(AudioUnit * unit, io_side side, AudioDeviceID device)
+audiounit_create_unit(AudioUnit * unit, device_info * device)
 {
-  AudioDeviceID devid;
+  assert(*unit == nullptr);
+  assert(device);
+
   OSStatus rv;
   int r;
 
-  assert(*unit == nullptr);
-  r = audiounit_new_unit_instance(unit, side, device);
+  r = audiounit_new_unit_instance(unit, device);
   if (r != CUBEB_OK) {
     return r;
   }
   assert(*unit);
 
-#if TARGET_OS_IPHONE
-  bool use_default_output = false;
-#else
-  bool use_default_output = device == 0 && (side == OUTPUT);
-#endif
-
-  if (!use_default_output) {
-    switch (side) {
-      case INPUT:
-        r = audiounit_enable_unit_scope(unit, INPUT, ENABLE);
-        if (r != CUBEB_OK) {
-          LOG("Failed to enable audiounit input scope ");
-          return r;
-        }
-        r = audiounit_enable_unit_scope(unit, OUTPUT, DISABLE);
-        if (r != CUBEB_OK) {
-          LOG("Failed to disable audiounit output scope ");
-          return r;
-        }
-        break;
-      case OUTPUT:
-        r = audiounit_enable_unit_scope(unit, OUTPUT, ENABLE);
-        if (r != CUBEB_OK) {
-          LOG("Failed to enable audiounit output scope ");
-          return r;
-        }
-        r = audiounit_enable_unit_scope(unit, INPUT, DISABLE);
-        if (r != CUBEB_OK) {
-          LOG("Failed to disable audiounit input scope ");
-          return r;
-        }
-        break;
-      default:
-        assert(false);
+  if ((device->flags & DEV_SYSTEM_DEFAULT)
+      && (device->flags & DEV_OUTPUT)) {
+    return CUBEB_OK;
+  }
+
+
+  if (device->flags & DEV_INPUT) {
+    r = audiounit_enable_unit_scope(unit, INPUT, ENABLE);
+    if (r != CUBEB_OK) {
+      LOG("Failed to enable audiounit input scope ");
+      return r;
+    }
+    r = audiounit_enable_unit_scope(unit, OUTPUT, DISABLE);
+    if (r != CUBEB_OK) {
+      LOG("Failed to disable audiounit output scope ");
+      return r;
     }
-
-    if (device == 0) {
-      assert(side == INPUT);
-      devid = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
-    } else {
-      devid = device;
+  } else if (device->flags & DEV_OUTPUT) {
+    r = audiounit_enable_unit_scope(unit, OUTPUT, ENABLE);
+    if (r != CUBEB_OK) {
+      LOG("Failed to enable audiounit output scope ");
+      return r;
+    }
+    r = audiounit_enable_unit_scope(unit, INPUT, DISABLE);
+    if (r != CUBEB_OK) {
+      LOG("Failed to disable audiounit input scope ");
+      return r;
     }
-
-    rv = AudioUnitSetProperty(*unit,
-                              kAudioOutputUnitProperty_CurrentDevice,
-                              kAudioUnitScope_Global,
-                              0,
-                              &devid, sizeof(AudioDeviceID));
-    if (rv != noErr) {
-      LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv);
-      return CUBEB_ERROR;
-    }
+  } else {
+    assert(false);
+  }
+
+  rv = AudioUnitSetProperty(*unit,
+                            kAudioOutputUnitProperty_CurrentDevice,
+                            kAudioUnitScope_Global,
+                            0,
+                            &device->id, sizeof(AudioDeviceID));
+  if (rv != noErr) {
+    LOG("AudioUnitSetProperty/kAudioOutputUnitProperty_CurrentDevice rv=%d", rv);
+    return CUBEB_ERROR;
   }
 
   return CUBEB_OK;
 }
 
 static int
 audiounit_init_input_linear_buffer(cubeb_stream * stream, uint32_t capacity)
 {
@@ -2273,47 +2253,46 @@ audiounit_configure_output(cubeb_stream 
 
 static int
 audiounit_setup_stream(cubeb_stream * stm)
 {
   stm->mutex.assert_current_thread_owns();
 
   int r = 0;
 
-  AudioDeviceID in_dev = stm->input_device;
-  AudioDeviceID out_dev = stm->output_device;
+  device_info in_dev_info = stm->input_device;
+  device_info out_dev_info = stm->output_device;
+
   if (has_input(stm) && has_output(stm)) {
     r = audiounit_create_aggregate_device(stm);
     if (r != CUBEB_OK) {
       stm->aggregate_device_id = 0;
       LOG("(%p) Create aggregate devices failed.", stm);
       // !!!NOTE: It is not necessary to return here. If it does not
       // return it will fallback to the old implementation. The intention
       // is to investigate how often it fails. I plan to remove
       // it after a couple of weeks.
       return r;
     } else {
-      in_dev = out_dev = stm->aggregate_device_id;
+      in_dev_info.id = out_dev_info.id = stm->aggregate_device_id;
+      in_dev_info.flags = DEV_INPUT;
+      out_dev_info.flags = DEV_OUTPUT;
     }
   }
 
   if (has_input(stm)) {
-    r = audiounit_create_unit(&stm->input_unit,
-                              INPUT,
-                              in_dev);
+    r = audiounit_create_unit(&stm->input_unit, &in_dev_info);
     if (r != CUBEB_OK) {
       LOG("(%p) AudioUnit creation for input failed.", stm);
       return r;
     }
   }
 
   if (has_output(stm)) {
-    r = audiounit_create_unit(&stm->output_unit,
-                              OUTPUT,
-                              out_dev);
+    r = audiounit_create_unit(&stm->output_unit, &out_dev_info);
     if (r != CUBEB_OK) {
       LOG("(%p) AudioUnit creation for output failed.", stm);
       return r;
     }
   }
 
   /* Latency cannot change if another stream is operating in parallel. In this case
   * latecy is set to the other stream value. */
@@ -2441,18 +2420,17 @@ audiounit_setup_stream(cubeb_stream * st
     // According to the I/O hardware rate it is expected a specific pattern of callbacks
     // for example is input is 44100 and output is 48000 we expected no more than 2
     // out callback in a row.
     stm->expected_output_callbacks_in_a_row = ceilf(stm->output_hw_rate / stm->input_hw_rate);
   }
 
   r = audiounit_install_device_changed_callback(stm);
   if (r != CUBEB_OK) {
-    LOG("(%p) Could not install the device change callback.", stm);
-    return r;
+    LOG("(%p) Could not install all device change callback.", stm);
   }
 
 
   return CUBEB_OK;
 }
 
 cubeb_stream::cubeb_stream(cubeb * context)
   : context(context)
@@ -2492,23 +2470,21 @@ audiounit_stream_init(cubeb * context,
   /* These could be different in the future if we have both
    * full-duplex stream and different devices for input vs output. */
   stm->data_callback = data_callback;
   stm->state_callback = state_callback;
   stm->user_ptr = user_ptr;
   stm->latency_frames = latency_frames;
   if (input_stream_params) {
     stm->input_stream_params = *input_stream_params;
-    stm->input_device = reinterpret_cast<uintptr_t>(input_device);
-    stm->is_default_input = stm->input_device == 0 ||
-                            (audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT) == stm->input_device);
+    audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(input_device), INPUT);
   }
   if (output_stream_params) {
     stm->output_stream_params = *output_stream_params;
-    stm->output_device = reinterpret_cast<uintptr_t>(output_device);
+    audiounit_set_device_info(stm.get(), reinterpret_cast<uintptr_t>(output_device), OUTPUT);
   }
 
   auto_lock context_lock(context->mutex);
   {
     // It's not critical to lock here, because no other thread has been started
     // yet, but it allows to assert that the lock has been taken in
     // `audiounit_setup_stream`.
     context->active_streams += 1;
@@ -2550,34 +2526,34 @@ audiounit_close_stream(cubeb_stream *stm
     AudioComponentInstanceDispose(stm->output_unit);
     stm->output_unit = nullptr;
   }
 
   stm->resampler.reset();
   stm->mixer.reset();
 
   if (stm->aggregate_device_id) {
-    audiounit_destroy_aggregate_device(stm->plugin_id, stm->aggregate_device_id);
+    audiounit_destroy_aggregate_device(stm->plugin_id, &stm->aggregate_device_id);
     stm->aggregate_device_id = 0;
   }
 }
 
 static void
 audiounit_stream_destroy(cubeb_stream * stm)
 {
   stm->shutdown = true;
 
   int r = audiounit_uninstall_system_changed_callback(stm);
   if (r != CUBEB_OK) {
     LOG("(%p) Could not uninstall the device changed callback", stm);
   }
 
   r = audiounit_uninstall_device_changed_callback(stm);
   if (r != CUBEB_OK) {
-    LOG("(%p) Could not uninstall the device changed callback", stm);
+    LOG("(%p) Could not uninstall all device change listeners", stm);
   }
 
   auto_lock context_lock(stm->context->mutex);
   audiounit_stream_stop_internal(stm);
 
   // Execute close in serial queue to avoid collision
   // with reinit when un/plug devices
   dispatch_sync(stm->context->serial_queue, ^() {
@@ -2677,18 +2653,18 @@ audiounit_stream_get_latency(cubeb_strea
       kAudioObjectPropertyElementMaster
     };
     AudioObjectPropertyAddress safety_offset_address = {
       kAudioDevicePropertySafetyOffset,
       kAudioDevicePropertyScopeOutput,
       kAudioObjectPropertyElementMaster
     };
 
-    r = audiounit_get_output_device_id(&output_device_id);
-    if (r != noErr) {
+    output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+    if (output_device_id == kAudioObjectUnknown) {
       return CUBEB_ERROR;
     }
 
     size = sizeof(unit_latency_sec);
     r = AudioUnitGetProperty(stm->output_unit,
                              kAudioUnitProperty_Latency,
                              kAudioUnitScope_Global,
                              0,
@@ -2801,17 +2777,18 @@ int audiounit_stream_get_current_device(
   AudioObjectPropertyAddress datasource_address_input = {
     kAudioDevicePropertyDataSource,
     kAudioDevicePropertyScopeInput,
     kAudioObjectPropertyElementMaster
   };
 
   *device = NULL;
 
-  if (audiounit_get_output_device_id(&output_device_id) != CUBEB_OK) {
+  output_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_OUTPUT);
+  if (output_device_id == kAudioObjectUnknown) {
     return CUBEB_ERROR;
   }
 
   *device = new cubeb_device;
   if (!*device) {
     return CUBEB_ERROR;
   }
   PodZero(*device, 1);
@@ -2835,17 +2812,18 @@ int audiounit_stream_get_current_device(
   strdata[0] = (char)(data >> 24);
   strdata[1] = (char)(data >> 16);
   strdata[2] = (char)(data >> 8);
   strdata[3] = (char)(data);
 
   memcpy((*device)->output_name, strdata, size);
   (*device)->output_name[size] = '\0';
 
-  if (audiounit_get_input_device_id(&input_device_id) != CUBEB_OK) {
+  input_device_id = audiounit_get_default_device_id(CUBEB_DEVICE_TYPE_INPUT);
+  if (input_device_id == kAudioObjectUnknown) {
     return CUBEB_ERROR;
   }
 
   size = sizeof(UInt32);
   r = AudioObjectGetPropertyData(input_device_id, &datasource_address_input, 0, NULL, &size, &data);
   if (r != noErr) {
     LOG("(%p) Error when getting device !", stm);
     size = 0;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/CodecProxy.java
@@ -52,33 +52,32 @@ public final class CodecProxy {
         @Override // JNIObject
         protected void disposeNative() {
             throw new UnsupportedOperationException();
         }
     }
 
     private class CallbacksForwarder extends ICodecCallbacks.Stub {
         private final Callbacks mCallbacks;
-        private boolean mEndOfInput;
         private boolean mCodecProxyReleased;
 
         CallbacksForwarder(Callbacks callbacks) {
             mCallbacks = callbacks;
         }
 
         @Override
         public synchronized void onInputQueued(long timestamp) throws RemoteException {
-            if (!mEndOfInput && !mCodecProxyReleased) {
+            if (!mCodecProxyReleased) {
                 mCallbacks.onInputStatus(timestamp, true /* processed */);
             }
         }
 
         @Override
         public synchronized void onInputPending(long timestamp) throws RemoteException {
-            if (!mEndOfInput && !mCodecProxyReleased) {
+            if (!mCodecProxyReleased) {
                 mCallbacks.onInputStatus(timestamp, false /* processed */);
             }
         }
 
         @Override
         public synchronized void onOutputFormatChanged(FormatParam format) throws RemoteException {
             if (!mCodecProxyReleased) {
                 mCallbacks.onOutputFormatChanged(format.asFormat());
@@ -113,20 +112,16 @@ public final class CodecProxy {
         }
 
         private synchronized void reportError(boolean fatal) {
             if (!mCodecProxyReleased) {
                 mCallbacks.onError(fatal);
             }
         }
 
-        private void setEndOfInput(boolean end) {
-            mEndOfInput = end;
-        }
-
         private synchronized void setCodecProxyReleased() {
             mCodecProxyReleased = true;
         }
     }
 
     @WrapForJNI
     public static CodecProxy create(boolean isEncoder,
                                     MediaFormat format,
@@ -198,17 +193,16 @@ public final class CodecProxy {
     @WrapForJNI
     public synchronized boolean input(ByteBuffer bytes, BufferInfo info, CryptoInfo cryptoInfo) {
         if (mRemote == null) {
             Log.e(LOGTAG, "cannot send input to an ended codec");
             return false;
         }
 
         boolean eos = info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM;
-        mCallbacks.setEndOfInput(eos);
 
         if (eos) {
             return sendInput(Sample.EOS);
         }
 
         try {
             return sendInput(mRemote.dequeueInput(info.size).set(bytes, info, cryptoInfo));
         } catch (RemoteException | NullPointerException e) {
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -197,16 +197,17 @@ pref("dom.requestIdleCallback.enabled", 
 pref("dom.gamepad.enabled", true);
 pref("dom.gamepad.test.enabled", false);
 #ifdef RELEASE_OR_BETA
 pref("dom.gamepad.non_standard_events.enabled", false);
 #else
 pref("dom.gamepad.non_standard_events.enabled", true);
 #endif
 pref("dom.gamepad.extensions.enabled", true);
+pref("dom.gamepad.haptic_feedback.enabled", true);
 
 // If this is true, TextEventDispatcher dispatches keydown and keyup events
 // even during composition (keypress events are never fired during composition
 // even if this is true).
 pref("dom.keyboardevent.dispatch_during_composition", false);
 
 // Whether to run add-on code in different compartments from browser code. This
 // causes a separate compartment for each (addon, global) combination, which may
@@ -439,17 +440,17 @@ pref("media.decoder-doctor.decode-warnin
 // Whether we report partial failures.
 pref("media.decoder-doctor.verbose", false);
 // Whether DD should consider WMF-disabled a WMF failure, useful for testing.
 pref("media.decoder-doctor.wmf-disabled-is-failure", false);
 // URL to report decode issues
 pref("media.decoder-doctor.new-issue-endpoint", "https://webcompat.com/issues/new");
 
 // Whether to suspend decoding of videos in background tabs.
-#ifdef RELEASE
+#ifdef RELEASE_OR_BETA
 pref("media.suspend-bkgnd-video.enabled", false);
 #else
 pref("media.suspend-bkgnd-video.enabled", true);
 #endif
 // Delay, in ms, from time window goes to background to suspending
 // video decoders. Defaults to 10 seconds.
 pref("media.suspend-bkgnd-video.delay-ms", 10000);
 // Resume video decoding when the cursor is hovering on a background tab to
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -1666,17 +1666,17 @@ source = "registry+https://github.com/ru
 dependencies = [
  "heapsize 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "phf_codegen 0.7.21 (registry+https://github.com/rust-lang/crates.io-index)",
  "rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "string_cache_codegen 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tendril 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "matches"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -3194,17 +3194,17 @@ name = "tempdir"
 version = "0.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "rand 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "tendril"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "futf 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "mac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "utf-8 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -3868,17 +3868,17 @@ dependencies = [
 "checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad"
 "checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6"
 "checksum synstructure 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cf318c34a2f8381a4f3d4db2c91b45bca2b1cd8cbe56caced900647be164800c"
 "checksum syntex 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f5e3aaa79319573d19938ea38d068056b826db9883a5d47f86c1cecc688f0e"
 "checksum syntex_errors 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "867cc5c2d7140ae7eaad2ae9e8bf39cb18a67ca651b7834f88d46ca98faadb9c"
 "checksum syntex_pos 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13ad4762fe52abc9f4008e85c4fb1b1fe3aa91ccb99ff4826a439c7c598e1047"
 "checksum syntex_syntax 0.58.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6e0e4dbae163dd98989464c23dd503161b338790640e11537686f2ef0f25c791"
 "checksum tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "87974a6f5c1dfb344d733055601650059a3363de2a6104819293baff662132d6"
-"checksum tendril 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "01576be96a211e017bf90b1603b1272baf9fe93a1bf9b4845257c4ba09c9b25f"
+"checksum tendril 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1b72f8e2f5b73b65c315b1a70c730f24b9d7a25f39e98de8acbe2bb795caea"
 "checksum term 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d168af3930b369cfe245132550579d47dfd873d69470755a19c2c6568dbbd989"
 "checksum term_size 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "07b6c1ac5b3fffd75073276bca1ceed01f67a28537097a2a9539e116e50fb21a"
 "checksum thread-id 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8df7875b676fddfadffd96deea3b1124e5ede707d4884248931077518cf1f773"
 "checksum thread_local 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c85048c6260d17cf486ceae3282d9fb6b90be220bf5b28c400f5485ffc29f0c7"
 "checksum thread_profiler 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "5920e77802b177479ab5795767fa48e68f61b2f516c2ac0041e2978dd8efe483"
 "checksum threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "59f6d3eff89920113dac9db44dde461d71d01e88a5b57b258a0466c32b5d7fe1"
 "checksum time 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "ffd7ccbf969a892bf83f1e441126968a07a3941c24ff522a26af9f9f4585d1a3"
 "checksum tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1d401358cd71aca93d5f4fccd3db5b87d970ae70fe457911929d99f4a87f7531"
--- a/servo/components/gfx/text/shaping/harfbuzz.rs
+++ b/servo/components/gfx/text/shaping/harfbuzz.rs
@@ -416,17 +416,17 @@ lazy_static! {
     static ref HB_FONT_FUNCS: ptr::Unique<hb_font_funcs_t> = unsafe {
         let hb_funcs = hb_font_funcs_create();
         hb_font_funcs_set_glyph_func(hb_funcs, Some(glyph_func), ptr::null_mut(), None);
         hb_font_funcs_set_glyph_h_advance_func(
             hb_funcs, Some(glyph_h_advance_func), ptr::null_mut(), None);
         hb_font_funcs_set_glyph_h_kerning_func(
             hb_funcs, Some(glyph_h_kerning_func), ptr::null_mut(), None);
 
-        ptr::Unique::new(hb_funcs)
+        ptr::Unique::new_unchecked(hb_funcs)
     };
 }
 
 extern fn glyph_func(_: *mut hb_font_t,
                      font_data: *mut c_void,
                      unicode: hb_codepoint_t,
                      _: hb_codepoint_t,
                      glyph: *mut hb_codepoint_t,
--- a/servo/components/layout_thread/dom_wrapper.rs
+++ b/servo/components/layout_thread/dom_wrapper.rs
@@ -251,17 +251,17 @@ impl<'ln> LayoutNode for ServoLayoutNode
         self.script_type_id().into()
     }
 
     unsafe fn initialize_data(&self) {
         if self.get_raw_data().is_none() {
             let ptr: *mut StyleAndLayoutData =
                 Box::into_raw(Box::new(StyleAndLayoutData::new()));
             let opaque = OpaqueStyleAndLayoutData {
-                ptr: NonZero::new(ptr as *mut StyleData),
+                ptr: NonZero::new_unchecked(ptr as *mut StyleData),
             };
             self.init_style_and_layout_data(opaque);
         };
     }
 
     unsafe fn init_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData) {
         self.get_jsmanaged().init_style_and_layout_data(data);
     }
--- a/servo/components/remutex/lib.rs
+++ b/servo/components/remutex/lib.rs
@@ -30,17 +30,17 @@ use std::sync::atomic::{AtomicUsize, Ord
 pub struct ThreadId(NonZero<usize>);
 
 lazy_static!{ static ref THREAD_COUNT: AtomicUsize = AtomicUsize::new(1); }
 
 impl ThreadId {
     #[allow(unsafe_code)]
     fn new() -> ThreadId {
         let number = THREAD_COUNT.fetch_add(1, Ordering::SeqCst);
-        ThreadId(unsafe { NonZero::new(number) })
+        ThreadId(NonZero::new(number).unwrap())
     }
     pub fn current() -> ThreadId {
         THREAD_ID.with(|tls| tls.clone())
     }
 }
 
 thread_local!{ static THREAD_ID: ThreadId = ThreadId::new() }
 
@@ -54,23 +54,23 @@ impl AtomicOptThreadId {
     }
     pub fn store(&self, value: Option<ThreadId>, ordering: Ordering) {
         let number = value.map(|id| id.0.get()).unwrap_or(0);
         self.0.store(number, ordering);
     }
     #[allow(unsafe_code)]
     pub fn load(&self, ordering: Ordering) -> Option<ThreadId> {
         let number = self.0.load(ordering);
-        if number == 0 { None } else { Some(ThreadId(unsafe { NonZero::new(number) })) }
+        NonZero::new(number).map(ThreadId)
     }
     #[allow(unsafe_code)]
     pub fn swap(&self, value: Option<ThreadId>, ordering: Ordering) -> Option<ThreadId> {
         let number = value.map(|id| id.0.get()).unwrap_or(0);
         let number = self.0.swap(number, ordering);
-        if number == 0 { None } else { Some(ThreadId(unsafe { NonZero::new(number) })) }
+        NonZero::new(number).map(ThreadId)
     }
 }
 
 /// A type for hand-over-hand mutexes.
 ///
 /// These support `lock` and `unlock` functions. `lock` blocks waiting to become the
 /// mutex owner. `unlock` can only be called by the lock owner, and panics otherwise.
 /// They have the same happens-before and poisoning semantics as `Mutex`.
--- a/servo/components/script/dom/bindings/iterable.rs
+++ b/servo/components/script/dom/bindings/iterable.rs
@@ -101,17 +101,17 @@ impl<T: DomObject + JSTraceable + Iterab
                     }
                     key_and_value_return(cx, rval.handle_mut(), key.handle(), value.handle())
                 }
             }
         };
         self.index.set(index + 1);
         result.map(|_| {
             assert!(!rval.is_null());
-            unsafe { NonZero::new(rval.get()) }
+            unsafe { NonZero::new_unchecked(rval.get()) }
         })
     }
 }
 
 fn dict_return(cx: *mut JSContext,
                result: MutableHandleObject,
                done: bool,
                value: HandleValue) -> Fallible<()> {
--- a/servo/components/script/dom/bindings/js.rs
+++ b/servo/components/script/dom/bindings/js.rs
@@ -76,17 +76,17 @@ impl<T> JS<T> {
 }
 
 impl<T: DomObject> JS<T> {
     /// Create a JS<T> from a &T
     #[allow(unrooted_must_root)]
     pub fn from_ref(obj: &T) -> JS<T> {
         debug_assert!(thread_state::get().is_script());
         JS {
-            ptr: unsafe { NonZero::new(&*obj) },
+            ptr: unsafe { NonZero::new_unchecked(&*obj) },
         }
     }
 }
 
 impl<'root, T: DomObject + 'root> RootedReference<'root> for JS<T> {
     type Ref = &'root T;
     fn r(&'root self) -> &'root T {
         &self
@@ -130,30 +130,30 @@ impl<T: Castable> LayoutJS<T> {
     /// Cast a DOM object root upwards to one of the interfaces it derives from.
     pub fn upcast<U>(&self) -> LayoutJS<U>
         where U: Castable,
               T: DerivedFrom<U>
     {
         debug_assert!(thread_state::get().is_layout());
         let ptr: *const T = self.ptr.get();
         LayoutJS {
-            ptr: unsafe { NonZero::new(ptr as *const U) },
+            ptr: unsafe { NonZero::new_unchecked(ptr as *const U) },
         }
     }
 
     /// Cast a DOM object downwards to one of the interfaces it might implement.
     pub fn downcast<U>(&self) -> Option<LayoutJS<U>>
         where U: DerivedFrom<T>
     {
         debug_assert!(thread_state::get().is_layout());
         unsafe {
             if (*self.unsafe_get()).is::<U>() {
                 let ptr: *const T = self.ptr.get();
                 Some(LayoutJS {
-                    ptr: NonZero::new(ptr as *const U),
+                    ptr: NonZero::new_unchecked(ptr as *const U),
                 })
             } else {
                 None
             }
         }
     }
 }
 
@@ -218,17 +218,17 @@ impl <T> Clone for LayoutJS<T> {
 
 impl LayoutJS<Node> {
     /// Create a new JS-owned value wrapped from an address known to be a
     /// `Node` pointer.
     pub unsafe fn from_trusted_node_address(inner: TrustedNodeAddress) -> LayoutJS<Node> {
         debug_assert!(thread_state::get().is_layout());
         let TrustedNodeAddress(addr) = inner;
         LayoutJS {
-            ptr: NonZero::new(addr as *const Node),
+            ptr: NonZero::new_unchecked(addr as *const Node),
         }
     }
 }
 
 /// A holder that provides interior mutability for GC-managed values such as
 /// `JS<T>`.  Essentially a `Cell<JS<T>>`, but safer.
 ///
 /// This should only be used as a field in other DOM objects; see warning
@@ -549,17 +549,17 @@ impl<T: DomObject> Root<T> {
                 ptr: unrooted,
                 root_list: collection,
             }
         })
     }
 
     /// Generate a new root from a reference
     pub fn from_ref(unrooted: &T) -> Root<T> {
-        Root::new(unsafe { NonZero::new(unrooted) })
+        Root::new(unsafe { NonZero::new_unchecked(unrooted) })
     }
 }
 
 impl<'root, T: DomObject + 'root> RootedReference<'root> for Root<T> {
     type Ref = &'root T;
     fn r(&'root self) -> &'root T {
         self
     }
--- a/servo/components/script/dom/bindings/refcounted.rs
+++ b/servo/components/script/dom/bindings/refcounted.rs
@@ -195,17 +195,17 @@ impl<T: DomObject> Trusted<T> {
     /// obtained.
     pub fn root(&self) -> Root<T> {
         assert!(LIVE_REFERENCES.with(|ref r| {
             let r = r.borrow();
             let live_references = r.as_ref().unwrap();
             self.owner_thread == (&*live_references) as *const _ as *const libc::c_void
         }));
         unsafe {
-            Root::new(NonZero::new(self.refcount.0 as *const T))
+            Root::new(NonZero::new_unchecked(self.refcount.0 as *const T))
         }
     }
 }
 
 impl<T: DomObject> Clone for Trusted<T> {
     fn clone(&self) -> Trusted<T> {
         Trusted {
             refcount: self.refcount.clone(),
--- a/servo/components/script/dom/bindings/weakref.rs
+++ b/servo/components/script/dom/bindings/weakref.rs
@@ -53,29 +53,29 @@ pub trait WeakReferenceable: DomObject +
             let object = self.reflector().get_jsobject().get();
             let mut ptr = JS_GetReservedSlot(object,
                                              DOM_WEAK_SLOT)
                               .to_private() as *mut WeakBox<Self>;
             if ptr.is_null() {
                 trace!("Creating new WeakBox holder for {:p}.", self);
                 ptr = Box::into_raw(box WeakBox {
                     count: Cell::new(1),
-                    value: Cell::new(Some(NonZero::new(self))),
+                    value: Cell::new(Some(NonZero::new_unchecked(self))),
                 });
                 JS_SetReservedSlot(object, DOM_WEAK_SLOT, PrivateValue(ptr as *const c_void));
             }
             let box_ = &*ptr;
             assert!(box_.value.get().is_some());
             let new_count = box_.count.get() + 1;
             trace!("Incrementing WeakBox refcount for {:p} to {}.",
                    self,
                    new_count);
             box_.count.set(new_count);
             WeakRef {
-                ptr: NonZero::new(ptr),
+                ptr: NonZero::new_unchecked(ptr),
             }
         }
     }
 }
 
 impl<T: WeakReferenceable> WeakRef<T> {
     /// Create a new weak reference from a `WeakReferenceable` interface instance.
     /// This is just a convenience wrapper around `<T as WeakReferenceable>::downgrade`
--- a/servo/components/script/dom/crypto.rs
+++ b/servo/components/script/dom/crypto.rs
@@ -60,17 +60,17 @@ impl CryptoMethods for Crypto {
         }
 
         if data.len() > 65536 {
             return Err(Error::QuotaExceeded);
         }
 
         self.rng.borrow_mut().fill_bytes(&mut data);
 
-        Ok(NonZero::new(input))
+        Ok(NonZero::new_unchecked(input))
     }
 }
 
 fn is_integer_buffer(array_type: Type) -> bool {
     match array_type {
         Type::Uint8 |
         Type::Uint8Clamped |
         Type::Int8 |
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -3493,28 +3493,28 @@ impl DocumentMethods for Document {
             // Step 1.
             let mut elements = root.traverse_preorder()
                                    .filter(|node| filter_by_name(&name, &node))
                                    .peekable();
             if let Some(first) = elements.next() {
                 if elements.peek().is_none() {
                     // TODO: Step 2.
                     // Step 3.
-                    return Some(NonZero::new(first.reflector().get_jsobject().get()));
+                    return Some(NonZero::new_unchecked(first.reflector().get_jsobject().get()));
                 }
             } else {
                 return None;
             }
         }
         // Step 4.
         let filter = NamedElementFilter {
             name: name,
         };
         let collection = HTMLCollection::create(self.window(), root, box filter);
-        Some(NonZero::new(collection.reflector().get_jsobject().get()))
+        Some(NonZero::new_unchecked(collection.reflector().get_jsobject().get()))
     }
 
     // https://html.spec.whatwg.org/multipage/#dom-tree-accessors:supported-property-names
     fn SupportedPropertyNames(&self) -> Vec<DOMString> {
         // FIXME: unimplemented (https://github.com/servo/servo/issues/7273)
         vec![]
     }
 
--- a/servo/components/script/dom/gamepad.rs
+++ b/servo/components/script/dom/gamepad.rs
@@ -123,17 +123,17 @@ impl GamepadMethods for Gamepad {
     // https://w3c.github.io/gamepad/#dom-gamepad-mapping
     fn Mapping(&self) -> DOMString {
         DOMString::from(self.mapping_type.clone())
     }
 
     #[allow(unsafe_code)]
     // https://w3c.github.io/gamepad/#dom-gamepad-axes
     unsafe fn Axes(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.axes.get())
+        NonZero::new_unchecked(self.axes.get())
     }
 
     // https://w3c.github.io/gamepad/#dom-gamepad-buttons
     fn Buttons(&self) -> Root<GamepadButtonList> {
         Root::from_ref(&*self.buttons)
     }
 
     // https://w3c.github.io/gamepad/extensions.html#gamepadhand-enum
--- a/servo/components/script/dom/imagedata.rs
+++ b/servo/components/script/dom/imagedata.rs
@@ -156,11 +156,11 @@ impl ImageDataMethods for ImageData {
     fn Height(&self) -> u32 {
         self.height
     }
 
     #[allow(unsafe_code)]
     // https://html.spec.whatwg.org/multipage/#dom-imagedata-data
     unsafe fn Data(&self, _: *mut JSContext) -> NonZero<*mut JSObject> {
         assert!(!self.data.get().is_null());
-        NonZero::new(self.data.get())
+        NonZero::new_unchecked(self.data.get())
     }
 }
--- a/servo/components/script/dom/testbinding.rs
+++ b/servo/components/script/dom/testbinding.rs
@@ -149,27 +149,27 @@ impl TestBindingMethods for TestBinding 
     fn Union9Attribute(&self) -> ByteStringOrLong {
         ByteStringOrLong::ByteString(ByteString::new(vec!()))
     }
     fn SetUnion9Attribute(&self, _: ByteStringOrLong) {}
     #[allow(unsafe_code)]
     unsafe fn ArrayAttribute(&self, cx: *mut JSContext) -> NonZero<*mut JSObject> {
         rooted!(in(cx) let array = JS_NewUint8ClampedArray(cx, 16));
         assert!(!array.is_null());
-        NonZero::new(array.get())
+        NonZero::new_unchecked(array.get())
     }
     #[allow(unsafe_code)]
     unsafe fn AnyAttribute(&self, _: *mut JSContext) -> JSVal { NullValue() }
     #[allow(unsafe_code)]
     unsafe fn SetAnyAttribute(&self, _: *mut JSContext, _: HandleValue) {}
     #[allow(unsafe_code)]
     unsafe fn ObjectAttribute(&self, cx: *mut JSContext) -> NonZero<*mut JSObject> {
         rooted!(in(cx) let obj = JS_NewPlainObject(cx));
         assert!(!obj.is_null());
-        NonZero::new(obj.get())
+        NonZero::new_unchecked(obj.get())
     }
     #[allow(unsafe_code)]
     unsafe fn SetObjectAttribute(&self, _: *mut JSContext, _: *mut JSObject) {}
 
     fn GetBooleanAttributeNullable(&self) -> Option<bool> { Some(false) }
     fn SetBooleanAttributeNullable(&self, _: Option<bool>) {}
     fn GetByteAttributeNullable(&self) -> Option<i8> { Some(0) }
     fn SetByteAttributeNullable(&self, _: Option<i8>) {}
--- a/servo/components/script/dom/textencoder.rs
+++ b/servo/components/script/dom/textencoder.rs
@@ -48,11 +48,11 @@ impl TextEncoderMethods for TextEncoder 
     #[allow(unsafe_code)]
     // https://encoding.spec.whatwg.org/#dom-textencoder-encode
     unsafe fn Encode(&self, cx: *mut JSContext, input: USVString) -> NonZero<*mut JSObject> {
         let encoded = input.0.as_bytes();
 
         rooted!(in(cx) let mut js_object = ptr::null_mut());
         assert!(Uint8Array::create(cx, CreateWith::Slice(&encoded), js_object.handle_mut()).is_ok());
 
-        NonZero::new(js_object.get())
+        NonZero::new_unchecked(js_object.get())
     }
 }
--- a/servo/components/script/dom/vreyeparameters.rs
+++ b/servo/components/script/dom/vreyeparameters.rs
@@ -56,17 +56,17 @@ impl VREyeParameters {
         eye_parameters
     }
 }
 
 impl VREyeParametersMethods for VREyeParameters {
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vreyeparameters-offset
     unsafe fn Offset(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.offset.get())
+        NonZero::new_unchecked(self.offset.get())
     }
 
     // https://w3c.github.io/webvr/#dom-vreyeparameters-fieldofview
     fn FieldOfView(&self) -> Root<VRFieldOfView> {
         Root::from_ref(&*self.fov)
     }
 
     // https://w3c.github.io/webvr/#dom-vreyeparameters-renderwidth
--- a/servo/components/script/dom/vrframedata.rs
+++ b/servo/components/script/dom/vrframedata.rs
@@ -114,34 +114,34 @@ impl VRFrameDataMethods for VRFrameData 
     // https://w3c.github.io/webvr/#dom-vrframedata-timestamp
     fn Timestamp(&self) -> Finite<f64> {
         Finite::wrap(self.timestamp.get() - self.first_timestamp.get())
     }
 
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vrframedata-leftprojectionmatrix
     unsafe fn LeftProjectionMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.left_proj.get())
+        NonZero::new_unchecked(self.left_proj.get())
     }
 
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vrframedata-leftviewmatrix
     unsafe fn LeftViewMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.left_view.get())
+        NonZero::new_unchecked(self.left_view.get())
     }
 
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vrframedata-rightprojectionmatrix
     unsafe fn RightProjectionMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.right_proj.get())
+        NonZero::new_unchecked(self.right_proj.get())
     }
 
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vrframedata-rightviewmatrix
     unsafe fn RightViewMatrix(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.right_view.get())
+        NonZero::new_unchecked(self.right_view.get())
     }
 
     // https://w3c.github.io/webvr/#dom-vrframedata-pose
     fn Pose(&self) -> Root<VRPose> {
         Root::from_ref(&*self.pose)
     }
 }
--- a/servo/components/script/dom/vrpose.rs
+++ b/servo/components/script/dom/vrpose.rs
@@ -53,17 +53,17 @@ unsafe fn update_or_create_typed_array(c
 #[inline]
 #[allow(unsafe_code)]
 fn heap_to_option(heap: &Heap<*mut JSObject>) -> Option<NonZero<*mut JSObject>> {
     let js_object = heap.get();
     if js_object.is_null() {
         None
     } else {
         unsafe {
-            Some(NonZero::new(js_object))
+            Some(NonZero::new_unchecked(js_object))
         }
     }
 }
 
 impl VRPose {
     fn new_inherited() -> VRPose {
         VRPose {
             reflector_: Reflector::new(),
--- a/servo/components/script/dom/vrstageparameters.rs
+++ b/servo/components/script/dom/vrstageparameters.rs
@@ -65,17 +65,17 @@ impl VRStageParameters {
         *self.parameters.borrow_mut() = parameters.clone();
     }
 }
 
 impl VRStageParametersMethods for VRStageParameters {
     #[allow(unsafe_code)]
     // https://w3c.github.io/webvr/#dom-vrstageparameters-sittingtostandingtransform
     unsafe fn SittingToStandingTransform(&self, _cx: *mut JSContext) -> NonZero<*mut JSObject> {
-        NonZero::new(self.transform.get())
+        NonZero::new_unchecked(self.transform.get())
     }
 
     // https://w3c.github.io/webvr/#dom-vrstageparameters-sizex
     fn SizeX(&self) -> Finite<f32> {
         Finite::wrap(self.parameters.borrow().size_x)
     }
 
     // https://w3c.github.io/webvr/#dom-vrstageparameters-sizez
--- a/servo/components/script/dom/webgl_extensions/wrapper.rs
+++ b/servo/components/script/dom/webgl_extensions/wrapper.rs
@@ -52,17 +52,17 @@ impl<T> WebGLExtensionWrapper for TypedW
         let extension = self.extension.or_init(|| {
             enabled = false;
             T::new(ctx)
         });
         if !enabled {
             self.enable(ext);
         }
         unsafe {
-            NonZero::new(extension.reflector().get_jsobject().get())
+            NonZero::new_unchecked(extension.reflector().get_jsobject().get())
         }
     }
 
     fn is_supported(&self, ext: &WebGLExtensions) -> bool {
         self.extension.get().is_some() || T::is_supported(ext)
     }
 
     fn enable(&self, ext: &WebGLExtensions) {
--- a/servo/components/style/invalidation/element/invalidator.rs
+++ b/servo/components/style/invalidation/element/invalidator.rs
@@ -30,27 +30,68 @@ pub struct TreeStyleInvalidator<'a, 'b: 
 {
     element: E,
     data: Option<&'a mut ElementData>,
     shared_context: &'a SharedStyleContext<'b>,
 }
 
 type InvalidationVector = SmallVec<[Invalidation; 10]>;
 
+/// The kind of invalidation we're processing.
+///
+/// We can use this to avoid pushing invalidations of the same kind to our
+/// descendants or siblings.
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+enum InvalidationKind {
+    Descendant,
+    Sibling,
+}
+
 /// An `Invalidation` is a complex selector that describes which elements,
 /// relative to a current element we are processing, must be restyled.
 ///
 /// When `offset` points to the right-most compound selector in `selector`,
 /// then the Invalidation `represents` the fact that the current element
 /// must be restyled if the compound selector matches.  Otherwise, if
 /// describes which descendants (or later siblings) must be restyled.
 #[derive(Clone)]
 struct Invalidation {
     selector: Selector<SelectorImpl>,
     offset: usize,
+    /// Whether the invalidation was already matched by any previous sibling or
+    /// ancestor.
+    ///
+    /// If this is the case, we can avoid pushing invalidations generated by
+    /// this one if the generated invalidation is effective for all the siblings
+    /// or descendants after us.
+    matched_by_any_previous: bool,
+}
+
+impl Invalidation {
+    /// Whether this invalidation is effective for the next sibling or
+    /// descendant after us.
+    fn effective_for_next(&self) -> bool {
+        // TODO(emilio): For pseudo-elements this should be mostly false, except
+        // for the weird pseudos in <input type="number">.
+        //
+        // We should be able to do better here!
+        match self.selector.combinator_at(self.offset) {
+            Combinator::NextSibling |
+            Combinator::Child => false,
+            _ => true,
+        }
+    }
+
+    fn kind(&self) -> InvalidationKind {
+        if self.selector.combinator_at(self.offset).is_ancestor() {
+            InvalidationKind::Descendant
+        } else {
+            InvalidationKind::Sibling
+        }
+    }
 }
 
 impl fmt::Debug for Invalidation {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         use cssparser::ToCss;
 
         f.write_str("Invalidation(")?;
         for component in self.selector.iter_raw_parse_order_from(self.offset - 1) {
@@ -62,19 +103,19 @@ impl fmt::Debug for Invalidation {
         f.write_str(")")
     }
 }
 
 /// The result of processing a single invalidation for a given element.
 struct InvalidationResult {
     /// Whether the element itself was invalidated.
     invalidated_self: bool,
-    /// Whether the invalidation we've processed is effective for the next
-    /// sibling or descendant after us.
-    effective_for_next: bool,
+    /// Whether the invalidation matched, either invalidating the element or
+    /// generating another invalidation.
+    matched: bool,
 }
 
 impl<'a, 'b: 'a, E> TreeStyleInvalidator<'a, 'b, E>
     where E: TElement,
 {
     /// Trivially constructs a new `TreeStyleInvalidator`.
     pub fn new(
         element: E,
@@ -455,24 +496,26 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
         let mut i = 0;
         let mut new_sibling_invalidations = InvalidationVector::new();
         let mut invalidated_self = false;
 
         while i < sibling_invalidations.len() {
             let result = self.process_invalidation(
                 &sibling_invalidations[i],
                 descendant_invalidations,
-                &mut new_sibling_invalidations
+                &mut new_sibling_invalidations,
+                InvalidationKind::Sibling,
             );
 
             invalidated_self |= result.invalidated_self;
-            if !result.effective_for_next {
+            sibling_invalidations[i].matched_by_any_previous |= result.matched;
+            if sibling_invalidations[i].effective_for_next() {
+                i += 1;
+            } else {
                 sibling_invalidations.remove(i);
-            } else {
-                i += 1;
             }
         }
 
         sibling_invalidations.extend(new_sibling_invalidations.drain());
         invalidated_self
     }
 
     /// Process a given invalidation list coming from our parent,
@@ -488,20 +531,23 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
     ) -> bool {
         let mut invalidated = false;
 
         for invalidation in invalidations {
             let result = self.process_invalidation(
                 invalidation,
                 descendant_invalidations,
                 sibling_invalidations,
+                InvalidationKind::Descendant,
             );
 
             invalidated |= result.invalidated_self;
-            if result.effective_for_next {
+            if invalidation.effective_for_next() {
+                let mut invalidation = invalidation.clone();
+                invalidation.matched_by_any_previous |= result.matched;
                 descendant_invalidations.push(invalidation.clone());
             }
         }
 
         invalidated
     }
 
     /// Processes a given invalidation, potentially invalidating the style of
@@ -509,20 +555,21 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
     ///
     /// Returns whether invalidated the style of the element, and whether the
     /// invalidation should be effective to subsequent siblings or descendants
     /// down in the tree.
     fn process_invalidation(
         &mut self,
         invalidation: &Invalidation,
         descendant_invalidations: &mut InvalidationVector,
-        sibling_invalidations: &mut InvalidationVector
+        sibling_invalidations: &mut InvalidationVector,
+        invalidation_kind: InvalidationKind,
     ) -> InvalidationResult {
-        debug!("TreeStyleInvalidator::process_invalidation({:?}, {:?})",
-               self.element, invalidation);
+        debug!("TreeStyleInvalidator::process_invalidation({:?}, {:?}, {:?})",
+               self.element, invalidation, invalidation_kind);
 
         let mut context =
             MatchingContext::new_for_visited(
                 MatchingMode::Normal,
                 None,
                 VisitedHandlingMode::AllLinksVisitedAndUnvisited,
                 self.shared_context.quirks_mode,
             );
@@ -530,24 +577,27 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
         let matching_result = matches_compound_selector(
             &invalidation.selector,
             invalidation.offset,
             &mut context,
             &self.element
         );
 
         let mut invalidated_self = false;
+        let mut matched = false;
         match matching_result {
             CompoundSelectorMatchingResult::Matched { next_combinator_offset: 0 } => {
                 debug!(" > Invalidation matched completely");
+                matched = true;
                 invalidated_self = true;
             }
             CompoundSelectorMatchingResult::Matched { next_combinator_offset } => {
                 let next_combinator =
                     invalidation.selector.combinator_at(next_combinator_offset);
+                matched = true;
 
                 if matches!(next_combinator, Combinator::PseudoElement) {
                     let pseudo_selector =
                         invalidation.selector
                             .iter_raw_parse_order_from(next_combinator_offset - 1)
                             .next()
                             .unwrap();
                     let pseudo = match *pseudo_selector {
@@ -573,50 +623,112 @@ impl<'a, 'b: 'a, E> TreeStyleInvalidator
                         invalidated_self = true;
                     }
                 }
 
 
                 let next_invalidation = Invalidation {
                     selector: invalidation.selector.clone(),
                     offset: next_combinator_offset,
+                    matched_by_any_previous: false,
                 };
 
                 debug!(" > Invalidation matched, next: {:?}, ({:?})",
                         next_invalidation, next_combinator);
-                if next_combinator.is_ancestor() {
-                    descendant_invalidations.push(next_invalidation);
+
+                let next_invalidation_kind = next_invalidation.kind();
+
+                // We can skip pushing under some circumstances, and we should
+                // because otherwise the invalidation list could grow
+                // exponentially.
+                //
+                //  * First of all, both invalidations need to be of the same
+                //    kind. This is because of how we propagate them going to
+                //    the right of the tree for sibling invalidations and going
+                //    down the tree for children invalidations. A sibling
+                //    invalidation that ends up generating a children
+                //    invalidation ends up (correctly) in five different lists,
+                //    not in the same list five different times.
+                //
+                //  * Then, the invalidation needs to be matched by a previous
+                //    ancestor/sibling, in order to know that this invalidation
+                //    has been generated already.
+                //
+                //  * Finally, the new invalidation needs to be
+                //    `effective_for_next()`, in order for us to know that it is
+                //    still in the list, since we remove the dependencies that
+                //    aren't from the lists for our children / siblings.
+                //
+                // To go through an example, let's imagine we are processing a
+                // dom subtree like:
+                //
+                //   <div><address><div><div/></div></address></div>
+                //
+                // And an invalidation list with a single invalidation like:
+                //
+                //   [div div div]
+                //
+                // When we process the invalidation list for the outer div, we
+                // match it, and generate a `div div` invalidation, so for the
+                // <address> child we have:
+                //
+                //   [div div div, div div]
+                //
+                // With the first of them marked as `matched`.
+                //
+                // When we process the <address> child, we don't match any of
+                // them, so both invalidations go untouched to our children.
+                //
+                // When we process the second <div>, we match _both_
+                // invalidations.
+                //
+                // However, when matching the first, we can tell it's been
+                // matched, and not push the corresponding `div div`
+                // invalidation, since we know it's necessarily already on the
+                // list.
+                //
+                // Thus, without skipping the push, we'll arrive to the
+                // innermost <div> with:
+                //
+                //   [div div div, div div, div div, div]
+                //
+                // While skipping it, we won't arrive here with duplicating
+                // dependencies:
+                //
+                //   [div div div, div div, div]
+                //
+                let can_skip_pushing =
+                    next_invalidation_kind == invalidation_kind &&
+                    invalidation.matched_by_any_previous &&
+                    next_invalidation.effective_for_next();
+
+                if can_skip_pushing {
+                    debug!(" > Can avoid push, since the invalidation had \
+                           already been matched before");
                 } else {
-                    sibling_invalidations.push(next_invalidation);
+                    match next_invalidation_kind {
+                        InvalidationKind::Descendant => {
+                            descendant_invalidations.push(next_invalidation);
+                        }
+                        InvalidationKind::Sibling => {
+                            sibling_invalidations.push(next_invalidation);
+                        }
+                    }
                 }
             }
             CompoundSelectorMatchingResult::NotMatched => {}
         }
 
         if invalidated_self {
             if let Some(ref mut data) = self.data {
                 data.restyle.hint.insert(RESTYLE_SELF);
             }
         }
 
-        // TODO(emilio): For pseudo-elements this should be mostly false, except
-        // for the weird pseudos in <input type="number">.
-        //
-        // We should be able to do better here!
-        let effective_for_next =
-            match invalidation.selector.combinator_at(invalidation.offset) {
-                Combinator::NextSibling |
-                Combinator::Child => false,
-                _ => true,
-            };
-
-        InvalidationResult {
-            invalidated_self: invalidated_self,
-            effective_for_next: effective_for_next,
-        }
+        InvalidationResult { invalidated_self, matched, }
     }
 }
 
 struct InvalidationCollector<'a, 'b: 'a, E>
     where E: TElement,
 {
     element: E,
     wrapper: ElementWrapper<'b, E>,
@@ -825,22 +937,24 @@ impl<'a, 'b: 'a, E> InvalidationCollecto
         }
 
         if dependency.affects_descendants() {
             debug_assert_ne!(dependency.selector_offset, 0);
             debug_assert!(!dependency.affects_later_siblings());
             self.descendant_invalidations.push(Invalidation {
                 selector: dependency.selector.clone(),
                 offset: dependency.selector_offset,
+                matched_by_any_previous: false,
             });
         } else if dependency.affects_later_siblings() {
             debug_assert_ne!(dependency.selector_offset, 0);
             self.sibling_invalidations.push(Invalidation {
                 selector: dependency.selector.clone(),
                 offset: dependency.selector_offset,
+                matched_by_any_previous: false,
             });
         }
     }
 
     /// Returns whether `dependency` may cause us to invalidate the style of
     /// more elements than what we've already invalidated.
     fn dependency_may_be_relevant(&self, dependency: &Dependency) -> bool {
         if dependency.affects_descendants() || dependency.affects_later_siblings() {
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -373,20 +373,23 @@ impl ${style_struct.gecko_struct_name} {
 
 <%def name="impl_simple_clone(ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
         self.gecko.${gecko_ffi_name}
     }
 </%def>
 
-<%def name="impl_simple_copy(ident, gecko_ffi_name, *kwargs)">
+<%def name="impl_simple_copy(ident, gecko_ffi_name, on_set=None, *kwargs)">
     #[allow(non_snake_case)]
     pub fn copy_${ident}_from(&mut self, other: &Self) {
         self.gecko.${gecko_ffi_name} = other.gecko.${gecko_ffi_name};
+        % if on_set:
+        self.${on_set}();
+        % endif
     }
 </%def>
 
 <%def name="impl_coord_copy(ident, gecko_ffi_name)">
     #[allow(non_snake_case)]
     pub fn copy_${ident}_from(&mut self, other: &Self) {
         self.gecko.${gecko_ffi_name}.copy_from(&other.gecko.${gecko_ffi_name});
     }
@@ -473,17 +476,17 @@ def set_gecko_property(ffi_name, expr):
     #[allow(non_snake_case)]
     pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
         ${get_gecko_property(gecko_ffi_name)}.into()
     }
 </%def>
 
 <%def name="impl_keyword(ident, gecko_ffi_name, keyword, need_clone, cast_type='u8', **kwargs)">
 <%call expr="impl_keyword_setter(ident, gecko_ffi_name, keyword, cast_type, **kwargs)"></%call>
-<%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
+<%call expr="impl_simple_copy(ident, gecko_ffi_name, **kwargs)"></%call>
 %if need_clone:
 <%call expr="impl_keyword_clone(ident, gecko_ffi_name, keyword, cast_type)"></%call>
 % endif
 </%def>
 
 <%def name="impl_simple(ident, gecko_ffi_name, need_clone=False)">
 <%call expr="impl_simple_setter(ident, gecko_ffi_name)"></%call>
 <%call expr="impl_simple_copy(ident, gecko_ffi_name)"></%call>
--- a/servo/components/style/stylist.rs
+++ b/servo/components/style/stylist.rs
@@ -1198,17 +1198,17 @@ impl Stylist {
             // Gecko skips author normal rules if cutting off inheritance.
             // See nsStyleSet::FileRules().
             if !cut_off_inheritance {
                 // Step 3c: Author normal rules.
                 map.author.get_all_matching_rules(element,
                                                   &rule_hash_target,
                                                   applicable_declarations,
                                                   context,
-                                              self.quirks_mode,
+                                                  self.quirks_mode,
                                                   flags_setter,
                                                   CascadeLevel::AuthorNormal);
             } else {
                 debug!("skipping author normal rules due to cut off inheritance");
             }
         } else {
             debug!("skipping author normal rules");
         }
--- a/servo/rust-commit-hash
+++ b/servo/rust-commit-hash
@@ -1,1 +1,1 @@
-504328a31a211814f9cac6de84c5a7ed154f58eb
+599be0d18f4c6ddf36366d2a5a2ca6dc65886896
--- a/testing/marionette/client/marionette_driver/marionette.py
+++ b/testing/marionette/client/marionette_driver/marionette.py
@@ -2012,17 +2012,17 @@ class Marionette(object):
     @property
     def window_size(self):
         """Get the current browser window size.
 
         Will return the current browser window size in pixels. Refers to
         window outerWidth and outerHeight values, which include scroll bars,
         title bars, etc.
 
-        :returns: dictionary representation of current window width and height
+        :returns: Window rect.
         """
         warnings.warn("window_size property has been deprecated, please use get_window_rect()",
                       DeprecationWarning)
         return self._send_message("getWindowSize",
                                   key="value" if self.protocol == 1 else None)
 
     def set_window_size(self, width, height):
         """Resize the browser window currently in focus.
@@ -2031,28 +2031,48 @@ class Marionette(object):
         and `outerHeight` values, which include scroll bars, title bars, etc.
 
         An error will be returned if the requested window size would result
         in the window being in the maximised state.
 
         :param width: The width to resize the window to.
         :param height: The height to resize the window to.
 
+        :returns Window rect.
         """
         warnings.warn("set_window_size() has been deprecated, please use set_window_rect()",
                       DeprecationWarning)
         body = {"width": width, "height": height}
         return self._send_message("setWindowSize", body)
 
+    def minimize_window(self):
+        """Iconify the browser window currently receiving commands.
+        The action should be equivalent to the user pressing the minimize
+        button in the OS window.
+
+        Note that this command is not available on Fennec.  It may also
+        not be available in certain window managers.
+
+        :returns Window rect.
+        """
+        return self._send_message("WebDriver:MinimizeWindow")
+
     def maximize_window(self):
-        """ Resize the browser window currently receiving commands. The action
-        should be equivalent to the user pressing the maximize button
+        """Resize the browser window currently receiving commands.
+        The action should be equivalent to the user pressing the maximize
+        button in the OS window.
+
+
+        Note that this command is not available on Fennec.  It may also
+        not be available in certain window managers.
+
+        :returns: Window rect.
         """
         return self._send_message("maximizeWindow")
 
     def fullscreen(self):
-        """ Synchronously sets the user agent window to full screen as if the user
-        had done "View > Enter Full Screen",  or restores it if it is already
-        in full screen.
+        """Synchronously sets the user agent window to full screen as
+        if the user had done "View > Enter Full Screen",  or restores
+        it if it is already in full screen.
 
-        :returns: dictionary representation of current window width and height
+        :returns: Window rect.
         """
         return self._send_message("fullscreen")
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -2886,16 +2886,59 @@ GeckoDriver.prototype.setScreenOrientati
   }
 
   if (!win.screen.mozLockOrientation(mozOr)) {
     throw new WebDriverError(`Unable to set screen orientation: ${or}`);
   }
 };
 
 /**
+ * Synchronously minimizes the user agent window as if the user pressed
+ * the minimize button, or restores it if it is already minimized.
+ *
+ * Not supported on Fennec.
+ *
+ * @return {Object.<string, number>}
+ *     Window rect and window state.
+ *
+ * @throws {UnsupportedOperationError}
+ *     Not available for current application.
+ * @throws {NoSuchWindowError}
+ *     Top-level browsing context has been discarded.
+ * @throws {UnexpectedAlertOpenError}
+ *     A modal dialog is open, blocking this operation.
+ */
+GeckoDriver.prototype.minimizeWindow = function* (cmd, resp) {
+  assert.firefox();
+  const win = assert.window(this.getCurrentWindow());
+  assert.noUserPrompt(this.dialog);
+
+  let state;
+  yield new Promise(resolve => {
+    win.addEventListener("sizemodechange", resolve, {once: true});
+
+    if (win.windowState == win.STATE_MINIMIZED) {
+      win.restore();
+      state = "normal";
+    } else {
+      win.minimize();
+      state = "minimized";
+    }
+  });
+
+  resp.body = {
+    x: win.screenX,
+    y: win.screenY,
+    width: win.outerWidth,
+    height: win.outerHeight,
+    state,
+  };
+};
+
+/**
  * Synchronously maximizes the user agent window as if the user pressed
  * the maximize button, or restores it if it is already maximized.
  *
  * Not supported on Fennec.
  *
  * @return {Map.<string, number>}
  *     Window rect.
  *
@@ -3421,16 +3464,17 @@ GeckoDriver.prototype.commands = {
   "WebDriver:GetTitle": GeckoDriver.prototype.getTitle,
   "WebDriver:GetWindowHandle": GeckoDriver.prototype.getWindowHandle,
   "WebDriver:GetWindowHandles": GeckoDriver.prototype.getWindowHandles,
   "WebDriver:GetWindowRect": GeckoDriver.prototype.getWindowRect,
   "WebDriver:GetWindowType": GeckoDriver.prototype.getWindowType,
   "WebDriver:IsElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
   "WebDriver:IsElementEnabled": GeckoDriver.prototype.isElementEnabled,
   "WebDriver:IsElementSelected": GeckoDriver.prototype.isElementSelected,
+  "WebDriver:MinimizeWindow": GeckoDriver.prototype.minimizeWindow,
   "WebDriver:MaximizeWindow": GeckoDriver.prototype.maximizeWindow,
   "WebDriver:Navigate": GeckoDriver.prototype.get,
   "WebDriver:NewSession": GeckoDriver.prototype.newSession,
   "WebDriver:PerformActions": GeckoDriver.prototype.performActions,
   "WebDriver:Refresh":  GeckoDriver.prototype.refresh,
   "WebDriver:ReleaseActions": GeckoDriver.prototype.releaseActions,
   "WebDriver:SendAlertText": GeckoDriver.prototype.sendKeysToDialog,
   "WebDriver:SetScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
new file mode 100644
--- /dev/null
+++ b/testing/marionette/harness/marionette_harness/tests/unit/test_window_minimize.py
@@ -0,0 +1,40 @@
+# 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/.
+
+from marionette_driver.errors import InvalidArgumentException
+
+from marionette_harness import MarionetteTestCase
+
+class TestWindowMinimize(MarionetteTestCase):
+
+    def setUp(self):
+        MarionetteTestCase.setUp(self)
+
+        self.original_size = self.marionette.window_size
+
+    def assert_window_minimized(self, resp):
+        self.assertEqual("minimized", resp["state"])
+
+    def assert_window_restored(self, actual):
+        self.assertEqual("normal", actual["state"])
+        self.assertEqual(self.original_size["width"], actual["width"])
+        self.assertEqual(self.original_size["height"], actual["height"])
+
+    def test_minimize_twice_restores(self):
+        resp = self.marionette.minimize_window()
+        self.assert_window_minimized(resp)
+
+        # restore the window
+        resp = self.marionette.minimize_window()
+        self.assert_window_restored(resp)
+
+    def test_minimize_stress(self):
+        for i in range(1, 25):
+            expect_minimized = bool(i % 2)
+
+            resp = self.marionette.minimize_window()
+            if expect_minimized:
+                self.assert_window_minimized(resp)
+            else:
+                self.assert_window_restored(resp)
--- a/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
+++ b/testing/marionette/harness/marionette_harness/tests/unit/unit-tests.ini
@@ -64,16 +64,18 @@ skip-if = appname == 'fennec'
 [test_window_handles_content.py]
 [test_window_close_chrome.py]
 skip-if = appname == 'fennec'
 [test_window_close_content.py]
 [test_window_rect.py]
 skip-if = appname == 'fennec'
 [test_window_maximize.py]
 skip-if = appname == 'fennec'
+[test_window_minimize.py]
+skip-if = appname == 'fennec' || headless
 [test_window_status_content.py]
 [test_window_status_chrome.py]
 
 [test_screenshot.py]
 skip-if = headless # Relies on native styling which headless doesn't support.
 [test_cookies.py]
 [test_title.py]
 [test_title_chrome.py]
--- a/testing/mozbase/mozprofile/mozprofile/addons.py
+++ b/testing/mozbase/mozprofile/mozprofile/addons.py
@@ -1,26 +1,30 @@
 # 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 os
 import shutil
 import sys
 import tempfile
 import urllib2
 import zipfile
+import hashlib
 from xml.dom import minidom
 
 import mozfile
 from mozlog.unstructured import getLogger
 
 # Needed for the AMO's rest API -
 # https://developer.mozilla.org/en/addons.mozilla.org_%28AMO%29_API_Developers%27_Guide/The_generic_AMO_API
 AMO_API_VERSION = "1.5"
+_SALT = os.urandom(32).encode('hex')
+_TEMPORARY_ADDON_SUFFIX = "@temporary-addon"
+
 
 # Logger for 'mozprofile.addons' module
 module_logger = getLogger(__name__)
 
 
 class AddonFormatError(Exception):
     """Exception for not well-formed add-on manifest files"""
 
@@ -228,16 +232,22 @@ class AddonManager(object):
         """
         response = urllib2.urlopen(query)
         dom = minidom.parseString(response.read())
         for node in dom.getElementsByTagName('install')[0].childNodes:
             if node.nodeType == node.TEXT_NODE:
                 return node.data
 
     @classmethod
+    def _gen_iid(cls, addon_path):
+        hash = hashlib.sha1(_SALT)
+        hash.update(addon_path)
+        return hash.hexdigest() + _TEMPORARY_ADDON_SUFFIX
+
+    @classmethod
     def addon_details(cls, addon_path):
         """
         Returns a dictionary of details about the addon.
 
         :param addon_path: path to the add-on directory or XPI
 
         Returns::
 
@@ -271,72 +281,94 @@ class AddonManager(object):
             for node in element.childNodes:
                 if node.nodeType == node.TEXT_NODE:
                     rc.append(node.data)
             return ''.join(rc).strip()
 
         if not os.path.exists(addon_path):
             raise IOError('Add-on path does not exist: %s' % addon_path)
 
+        is_webext = False
         try:
             if zipfile.is_zipfile(addon_path):
                 # Bug 944361 - We cannot use 'with' together with zipFile because
                 # it will cause an exception thrown in Python 2.6.
                 try:
                     compressed_file = zipfile.ZipFile(addon_path, 'r')
-                    manifest = compressed_file.read('install.rdf')
+                    filenames = [f.filename for f in (compressed_file).filelist]
+                    if 'install.rdf' in filenames:
+                        manifest = compressed_file.read('install.rdf')
+                    elif 'manifest.json' in filenames:
+                        is_webext = True
+                        manifest = compressed_file.read('manifest.json')
+                        manifest = json.loads(manifest)
+                    else:
+                        raise KeyError("No manifest")
                 finally:
                     compressed_file.close()
             elif os.path.isdir(addon_path):
-                with open(os.path.join(addon_path, 'install.rdf'), 'r') as f:
-                    manifest = f.read()
+                try:
+                    with open(os.path.join(addon_path, 'install.rdf')) as f:
+                        manifest = f.read()
+                except IOError:
+                    with open(os.path.join(addon_path, 'manifest.json')) as f:
+                        manifest = json.loads(f.read())
+                        is_webext = True
             else:
                 raise IOError('Add-on path is neither an XPI nor a directory: %s' % addon_path)
         except (IOError, KeyError) as e:
             raise AddonFormatError(str(e)), None, sys.exc_info()[2]
 
-        try:
-            doc = minidom.parseString(manifest)
-
-            # Get the namespaces abbreviations
-            em = get_namespace_id(doc, 'http://www.mozilla.org/2004/em-rdf#')
-            rdf = get_namespace_id(doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+        if is_webext:
+            details['version'] = manifest['version']
+            details['name'] = manifest['name']
+            try:
+                details['id'] = manifest['applications']['gecko']['id']
+            except KeyError:
+                details['id'] = cls._gen_iid(addon_path)
+            details['unpack'] = False
+        else:
+            try:
+                doc = minidom.parseString(manifest)
 
-            description = doc.getElementsByTagName(rdf + 'Description').item(0)
-            for entry, value in description.attributes.items():
-                # Remove the namespace prefix from the tag for comparison
-                entry = entry.replace(em, "")
-                if entry in details.keys():
-                    details.update({entry: value})
-            for node in description.childNodes:
-                # Remove the namespace prefix from the tag for comparison
-                entry = node.nodeName.replace(em, "")
-                if entry in details.keys():
-                    details.update({entry: get_text(node)})
-        except Exception as e:
-            raise AddonFormatError(str(e)), None, sys.exc_info()[2]
+                # Get the namespaces abbreviations
+                em = get_namespace_id(doc, 'http://www.mozilla.org/2004/em-rdf#')
+                rdf = get_namespace_id(doc, 'http://www.w3.org/1999/02/22-rdf-syntax-ns#')
+
+                description = doc.getElementsByTagName(rdf + 'Description').item(0)
+                for entry, value in description.attributes.items():
+                    # Remove the namespace prefix from the tag for comparison
+                    entry = entry.replace(em, "")
+                    if entry in details.keys():
+                        details.update({entry: value})
+                for node in description.childNodes:
+                    # Remove the namespace prefix from the tag for comparison
+                    entry = node.nodeName.replace(em, "")
+                    if entry in details.keys():
+                        details.update({entry: get_text(node)})
+            except Exception as e:
+                raise AddonFormatError(str(e)), None, sys.exc_info()[2]
 
         # turn unpack into a true/false value
         if isinstance(details['unpack'], basestring):
             details['unpack'] = details['unpack'].lower() == 'true'
 
         # If no ID is set, the add-on is invalid
-        if details.get('id') is None:
+        if details.get('id') is None and not is_webext:
             raise AddonFormatError('Add-on id could not be found.')
 
         return details
 
     def install_from_path(self, path, unpack=False):
         """
         Installs addon from a filepath, url or directory of addons in the profile.
 
         :param path: url, path to .xpi, or directory of addons
         :param unpack: whether to unpack unless specified otherwise in the install.rdf
         """
-
         # if the addon is a URL, download it
         # note that this won't work with protocols urllib2 doesn't support
         if mozfile.is_url(path):
             path = self.download(path)
             self.downloaded_addons.append(path)
 
         addons = [path]
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fa721a4f76bad44da79286edb596050f7cc27cde
GIT binary patch
literal 3371
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0ed64f79ac9e2d0ea2459fc6d6f9f95d593e8b3b
GIT binary patch
literal 3412
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
bfZtUH+Em##ND$jb8kljZG0qBtY(#$p+lgxr
--- a/testing/mozbase/mozprofile/tests/test_addons.py
+++ b/testing/mozbase/mozprofile/tests/test_addons.py
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 import shutil
 import tempfile
 import unittest
 import urllib2
+import zipfile
 
 import mozunit
 
 from manifestparser import ManifestParser
 import mozfile
 import mozhttpd
 import mozlog.unstructured as mozlog
 import mozprofile
@@ -108,16 +109,55 @@ class TestAddonsManager(unittest.TestCas
         # Download from an invalid URL
         addon = 'not_existent.xpi'
         self.assertRaises(ValueError,
                           self.am.download, addon, self.tmpdir)
         self.assertEqual(os.listdir(self.tmpdir), [])
 
         server.stop()
 
+    def test_install_webextension_from_dir(self):
+        addon = os.path.join(here, 'addons', 'apply-css.xpi')
+        zipped = zipfile.ZipFile(addon)
+        try:
+            zipped.extractall(self.tmpdir)
+        finally:
+            zipped.close()
+        self.am.install_from_path(self.tmpdir)
+        self.assertEqual(len(self.am.installed_addons), 1)
+        self.assertTrue(os.path.isdir(self.am.installed_addons[0]))
+
+    def test_install_webextension(self):
+        server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))
+        server.start()
+        try:
+            addon = server.get_url() + 'apply-css.xpi'
+            self.am.install_from_path(addon)
+        finally:
+            server.stop()
+
+        self.assertEqual(len(self.am.downloaded_addons), 1)
+        self.assertTrue(os.path.isfile(self.am.downloaded_addons[0]))
+        self.assertIn('test-webext@quality.mozilla.org.xpi',
+                      os.path.basename(self.am.downloaded_addons[0]))
+
+    def test_install_webextension_sans_id(self):
+        server = mozhttpd.MozHttpd(docroot=os.path.join(here, 'addons'))
+        server.start()
+        try:
+            addon = server.get_url() + 'apply-css-sans-id.xpi'
+            self.am.install_from_path(addon)
+        finally:
+            server.stop()
+
+        self.assertEqual(len(self.am.downloaded_addons), 1)
+        self.assertTrue(os.path.isfile(self.am.downloaded_addons[0]))
+        self.assertIn('temporary-addon.xpi',
+                      os.path.basename(self.am.downloaded_addons[0]))
+
     def test_install_from_path_xpi(self):
         addons_to_install = []
         addons_installed = []
 
         # Generate installer stubs and install them
         for ext in ['test-addon-1@mozilla.org', 'test-addon-2@mozilla.org']:
             temp_addon = generate_addon(ext, path=self.tmpdir)
             addons_to_install.append(self.am.addon_details(temp_addon)['id'])
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/head_telemetry.js
@@ -0,0 +1,34 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+/* exported IS_OOP, arraySum, clearHistograms, getSnapshots, promiseTelemetryRecorded */
+
+XPCOMUtils.defineLazyModuleGetter(this, "ContentTaskUtils",
+                                  "resource://testing-common/ContentTaskUtils.jsm");
+
+const IS_OOP = Services.prefs.getBoolPref("extensions.webextensions.remote");
+
+function arraySum(arr) {
+  return arr.reduce((a, b) => a + b, 0);
+}
+
+function clearHistograms() {
+  Services.telemetry.snapshotSubsessionHistograms(true);
+}
+
+function getSnapshots(process) {
+  return Services.telemetry.snapshotSubsessionHistograms()[process];
+}
+
+// There is no good way to make sure that the parent received the histogram
+// entries from the extension and content processes.
+// Let's stick to the ugly, spinning the event loop until we have a good
+// approach (Bug 1357509).
+function promiseTelemetryRecorded(id, process, expectedCount) {
+  let condition = () => {
+    let snapshot = Services.telemetry.snapshotSubsessionHistograms()[process][id];
+    return snapshot && arraySum(snapshot.counts) >= expectedCount;
+  };
+  return ContentTaskUtils.waitForCondition(condition);
+}
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extension_content_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extension_content_telemetry.js
@@ -36,38 +36,41 @@ add_task(async function test_telemetry()
       }],
     },
 
     files: {
       "content_script.js": contentScript,
     },
   });
 
-  let histogram = Services.telemetry.getHistogramById(HISTOGRAM);
-  histogram.clear();
-  equal(histogram.snapshot().sum, 0,
-        `No data recorded for histogram: ${HISTOGRAM}.`);
+  clearHistograms();
+
+  let process = IS_OOP ? "content" : "parent";
+  ok(!(HISTOGRAM in getSnapshots(process)), `No data recorded for histogram: ${HISTOGRAM}.`);
 
   await extension1.startup();
-  equal(histogram.snapshot().sum, 0,
-        `No data recorded for histogram after startup: ${HISTOGRAM}.`);
+  ok(!(HISTOGRAM in getSnapshots(process)),
+     `No data recorded for histogram after startup: ${HISTOGRAM}.`);
 
   let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
   await extension1.awaitMessage("content-script-run");
-  let histogramSum = histogram.snapshot().sum;
-  ok(histogramSum > 0,
-     `Data recorded for first extension for histogram: ${HISTOGRAM}.`);
+  await promiseTelemetryRecorded(HISTOGRAM, process, 1);
+
+  equal(arraySum(getSnapshots(process)[HISTOGRAM].counts), 1,
+        `Data recorded for histogram: ${HISTOGRAM}.`);
 
   await contentPage.close();
   await extension1.unload();
 
   await extension2.startup();
-  equal(histogram.snapshot().sum, histogramSum,
+  equal(arraySum(getSnapshots(process)[HISTOGRAM].counts), 1,
         `No data recorded for histogram after startup: ${HISTOGRAM}.`);
 
   contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
   await extension2.awaitMessage("content-script-run");
-  ok(histogram.snapshot().sum > histogramSum,
-     `Data recorded for second extension for histogram: ${HISTOGRAM}.`);
+  await promiseTelemetryRecorded(HISTOGRAM, process, 2);
+
+  equal(arraySum(getSnapshots(process)[HISTOGRAM].counts), 2,
+        `Data recorded for histogram: ${HISTOGRAM}.`);
 
   await contentPage.close();
   await extension2.unload();
 });
--- a/toolkit/components/extensions/test/xpcshell/test_ext_storage_telemetry.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_storage_telemetry.js
@@ -1,20 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 const HISTOGRAM_IDS = [
   "WEBEXT_STORAGE_LOCAL_SET_MS", "WEBEXT_STORAGE_LOCAL_GET_MS",
 ];
 
-function arraySum(arr) {
-  return arr.reduce((a, b) => a + b, 0);
-}
-
 add_task(async function test_telemetry_background() {
   const server = createHttpServer();
   server.registerDirectory("/data/", do_get_file("data"));
 
   const BASE_URL = `http://localhost:${server.identity.primaryPort}/data`;
 
   async function contentScript() {
     await browser.storage.local.set({a: "b"});
@@ -44,51 +40,65 @@ add_task(async function test_telemetry_b
     files: {
       "content_script.js": contentScript,
     },
   };
 
   let extension1 = ExtensionTestUtils.loadExtension(extInfo);
   let extension2 = ExtensionTestUtils.loadExtension(extInfo);
 
-  // Initialize and clear histograms.
-  let histograms = {};
+  clearHistograms();
+
+  let process = IS_OOP ? "extension" : "parent";
+  let snapshots = getSnapshots(process);
   for (let id of HISTOGRAM_IDS) {
-    histograms[id] = Services.telemetry.getHistogramById(id);
-    histograms[id].clear();
-    equal(arraySum(histograms[id].snapshot().counts), 0,
-          `No data recorded for histogram: ${id}.`);
+    ok(!(id in snapshots), `No data recorded for histogram: ${id}.`);
   }
 
   await extension1.startup();
   await extension1.awaitMessage("backgroundDone");
+  for (let id of HISTOGRAM_IDS) {
+    await promiseTelemetryRecorded(id, process, 1);
+  }
 
   // Telemetry from extension1's background page should be recorded.
-  for (let id in histograms) {
-    equal(arraySum(histograms[id].snapshot().counts), 1,
+  snapshots = getSnapshots(process);
+  for (let id of HISTOGRAM_IDS) {
+    equal(arraySum(snapshots[id].counts), 1,
           `Data recorded for histogram: ${id}.`);
   }
 
   await extension2.startup();
   await extension2.awaitMessage("backgroundDone");
+  for (let id of HISTOGRAM_IDS) {
+    await promiseTelemetryRecorded(id, process, 2);
+  }
 
   // Telemetry from extension2's background page should be recorded.
-  for (let id in histograms) {
-    equal(arraySum(histograms[id].snapshot().counts), 2,
+  snapshots = getSnapshots(process);
+  for (let id of HISTOGRAM_IDS) {
+    equal(arraySum(snapshots[id].counts), 2,
           `Additional data recorded for histogram: ${id}.`);
   }
 
   await extension2.unload();
 
   // Run a content script.
+  process = IS_OOP ? "content" : "parent";
+  let expectedCount = IS_OOP ? 1 : 3;
   let contentScriptPromise = extension1.awaitMessage("contentDone");
   let contentPage = await ExtensionTestUtils.loadContentPage(`${BASE_URL}/file_sample.html`);
   await contentScriptPromise;
   await contentPage.close();
 
+  for (let id of HISTOGRAM_IDS) {
+    await promiseTelemetryRecorded(id, process, expectedCount);
+  }
+
   // Telemetry from extension1's content script should be recorded.
-  for (let id in histograms) {
-    equal(arraySum(histograms[id].snapshot().counts), 3,
+  snapshots = getSnapshots(process);
+  for (let id of HISTOGRAM_IDS) {
+    equal(arraySum(snapshots[id].counts), expectedCount,
           `Data recorded in content script for histogram: ${id}.`);
   }
 
   await extension1.unload();
 });
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini
@@ -8,30 +8,33 @@
 [test_ext_background_global_history.js]
 skip-if = os == "android" # Android does not use Places for history.
 [test_ext_background_private_browsing.js]
 [test_ext_background_runtime_connect_params.js]
 [test_ext_background_sub_windows.js]
 [test_ext_background_telemetry.js]
 [test_ext_background_window_properties.js]
 skip-if = os == "android"
+[test_ext_browserSettings.js]
 [test_ext_contextual_identities.js]
 skip-if = os == "android" # Containers are not exposed to android.
 [test_ext_debugging_utils.js]
 [test_ext_downloads.js]
 [test_ext_downloads_download.js]
 skip-if = os == "android"
 [test_ext_downloads_misc.js]
 skip-if = os == "android" || (os=='linux' && bits==32) # linux32: bug 1324870
 [test_ext_downloads_search.js]
 skip-if = os == "android"
 [test_ext_experiments.js]
 [test_ext_extension.js]
 [test_ext_extensionPreferencesManager.js]
 [test_ext_extensionSettingsStore.js]
+[test_ext_extension_content_telemetry.js]
+skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_extension_startup_telemetry.js]
 [test_ext_idle.js]
 [test_ext_legacy_extension_context.js]
 [test_ext_legacy_extension_embedding.js]
 [test_ext_localStorage.js]
 [test_ext_management.js]
 [test_ext_management_uninstall_self.js]
 [test_ext_onmessage_removelistener.js]
@@ -51,12 +54,14 @@ skip-if = true # This test no longer tes
 [test_ext_simple.js]
 [test_ext_startup_cache.js]
 [test_ext_storage.js]
 [test_ext_storage_sync.js]
 head = head.js head_sync.js
 skip-if = os == "android"
 [test_ext_storage_sync_crypto.js]
 skip-if = os == "android"
+[test_ext_storage_telemetry.js]
+skip-if = os == "android" # checking for telemetry needs to be updated: 1384923
 [test_ext_topSites.js]
 skip-if = os == "android"
 [test_native_messaging.js]
 skip-if = os == "android"
--- a/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell-remote.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-head = head.js head_remote.js head_e10s.js
+head = head.js head_remote.js head_e10s.js head_telemetry.js
 tail =
 firefox-appdir = browser
 skip-if = appname == "thunderbird" || os == "android"
 dupe-manifest =
 support-files =
   data/**
   xpcshell-content.ini
 tags = webextensions remote-webextensions
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-head = head.js
+head = head.js head_telemetry.js
 firefox-appdir = browser
 skip-if = appname == "thunderbird"
 dupe-manifest =
 support-files =
   data/**
   head_sync.js
   xpcshell-content.ini
 tags = webextensions in-process-webextensions
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -541,20 +541,20 @@ this.History = Object.freeze({
     }
 
     guidOrURI = PlacesUtils.normalizeToURLOrGUID(guidOrURI);
     let isGuid = typeof guidOrURI == "string";
     let sqlFragment = isGuid ? "guid = :val"
                              : "url_hash = hash(:val) AND url = :val "
 
     return PlacesUtils.promiseDBConnection().then(async db => {
-      let rows = await db.execute(`SELECT 1 FROM moz_places
-                                    WHERE ${sqlFragment}
-                                      AND last_visit_date NOTNULL`,
-                                  { val: isGuid ? guidOrURI : guidOrURI.href });
+      let rows = await db.executeCached(`SELECT 1 FROM moz_places
+                                         WHERE ${sqlFragment}
+                                         AND last_visit_date NOTNULL`,
+                                        { val: isGuid ? guidOrURI : guidOrURI.href });
       return !!rows.length;
     });
   },
 
   /**
    * Clear all history.
    *
    * @return (Promise)
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -393,16 +393,31 @@ preferences:
       Set to true if user.js exists and was read.
     expires: "62"
     kind: boolean
     notification_emails:
       - bsmedberg@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - main
+  search_query:
+    bug_numbers:
+      - 1359306
+    description: >-
+      Each key is a search query string when user performs a search action within
+      about:preferences, and each value is the number of times that key is recorded.
+      The telemetry data will be recorded if there is a successful search result highlighted.
+    expires: "62"
+    kind: uint
+    keyed: true
+    notification_emails:
+      - chsiang@mozilla.com
+    release_channel_collection: opt-in
+    record_in_processes:
+      - main
 
 # The following section contains WebRTC nICEr scalars
 # For more info on ICE, see https://tools.ietf.org/html/rfc5245
 # For more info on STUN, see https://tools.ietf.org/html/rfc5389
 # For more info on TURN, see https://tools.ietf.org/html/rfc5766
 webrtc.nicer:
   stun_retransmits:
     bug_numbers:
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -71,16 +71,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySession",
                                   "resource://gre/modules/TelemetrySession.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetrySend",
                                   "resource://gre/modules/TelemetrySend.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryReportingPolicy",
                                   "resource://gre/modules/TelemetryReportingPolicy.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryModules",
                                   "resource://gre/modules/TelemetryModules.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "UpdatePing",
+                                  "resource://gre/modules/UpdatePing.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryHealthPing",
                                   "resource://gre/modules/TelemetryHealthPing.jsm");
 
 /**
  * Setup Telemetry logging. This function also gets called when loggin related
  * preferences change.
  */
 var gLogger = null;
@@ -692,16 +694,20 @@ var Impl = {
     TelemetrySend.earlyInit();
 
     // For very short session durations, we may never load the client
     // id from disk.
     // We try to cache it in prefs to avoid this, even though this may
     // lead to some stale client ids.
     this._clientID = ClientID.getCachedClientID();
 
+    // Init the update ping telemetry as early as possible. This won't have
+    // an impact on startup.
+    UpdatePing.earlyInit();
+
     // Delay full telemetry initialization to give the browser time to
     // run various late initializers. Otherwise our gathered memory
     // footprint and other numbers would be too optimistic.
     this._delayedInitTaskDeferred = PromiseUtils.defer();
     this._delayedInitTask = new DeferredTask(async () => {
       try {
         // TODO: This should probably happen after all the delayed init here.
         this._initialized = true;
@@ -778,16 +784,18 @@ var Impl = {
     this._detachObservers();
 
     // Now do an orderly shutdown.
     try {
       if (this._delayedNewPingTask) {
         await this._delayedNewPingTask.finalize();
       }
 
+      UpdatePing.shutdown();
+
       // Stop the datachoices infobar display.
       TelemetryReportingPolicy.shutdown();
       TelemetryEnvironment.shutdown();
 
       // Stop any ping sending.
       await TelemetrySend.shutdown();
 
       // Send latest data.
--- a/toolkit/components/telemetry/TelemetryUtils.jsm
+++ b/toolkit/components/telemetry/TelemetryUtils.jsm
@@ -32,16 +32,17 @@ this.TelemetryUtils = {
     FirstRun: "toolkit.telemetry.reportingpolicy.firstRun",
     HealthPingEnabled: "toolkit.telemetry.healthping.enabled",
     OverrideOfficialCheck: "toolkit.telemetry.send.overrideOfficialCheck",
     Server: "toolkit.telemetry.server",
     ShutdownPingSender: "toolkit.telemetry.shutdownPingSender.enabled",
     ShutdownPingSenderFirstSession: "toolkit.telemetry.shutdownPingSender.enabledFirstSession",
     TelemetryEnabled: "toolkit.telemetry.enabled",
     Unified: "toolkit.telemetry.unified",
+    UpdatePing: "toolkit.telemetry.updatePing.enabled",
     NewProfilePingEnabled: "toolkit.telemetry.newProfilePing.enabled",
     NewProfilePingDelay: "toolkit.telemetry.newProfilePing.delay",
     PreviousBuildID: "toolkit.telemetry.previousBuildID",
 
     // Log Preferences
     LogLevel: "toolkit.telemetry.log.level",
     LogDump: "toolkit.telemetry.log.dump",
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/UpdatePing.jsm
@@ -0,0 +1,104 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* 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 strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm", this);
+Cu.import("resource://gre/modules/Preferences.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm", this);
+Cu.import("resource://gre/modules/TelemetryUtils.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+XPCOMUtils.defineLazyModuleGetter(this, "TelemetryController",
+                                  "resource://gre/modules/TelemetryController.jsm");
+
+const LOGGER_NAME = "Toolkit.Telemetry";
+const PING_TYPE = "update";
+const UPDATE_DOWNLOADED_TOPIC = "update-downloaded";
+
+this.EXPORTED_SYMBOLS = ["UpdatePing"];
+
+/**
+ * This module is responsible for listening to all the relevant update
+ * signals, gathering the needed information and assembling the "update"
+ * ping.
+ */
+this.UpdatePing = {
+  earlyInit() {
+    this._log = Log.repository.getLoggerWithMessagePrefix(LOGGER_NAME, "UpdatePing::");
+    this._enabled = Preferences.get(TelemetryUtils.Preferences.UpdatePing, false);
+
+    this._log.trace("init - enabled: " + this._enabled);
+
+    if (!this._enabled) {
+      return;
+    }
+
+    Services.obs.addObserver(this, UPDATE_DOWNLOADED_TOPIC);
+  },
+
+  /**
+   * Generate an "update" ping with reason "ready" and dispatch it
+   * to the Telemetry system.
+   *
+   * @param {String} aUpdateState The state of the downloaded patch. See
+   *        nsIUpdateService.idl for a list of possible values.
+   */
+  _handleUpdateReady(aUpdateState) {
+    const ALLOWED_STATES = [
+      "applied", "applied-service", "pending", "pending-service", "pending-elevate"
+    ];
+    if (!ALLOWED_STATES.includes(aUpdateState)) {
+      this._log.trace("Unexpected update state: " + aUpdateState);
+      return;
+    }
+
+    // Get the information about the update we're going to apply from the
+    // update manager.
+    let updateManager =
+      Cc["@mozilla.org/updates/update-manager;1"].getService(Ci.nsIUpdateManager);
+    if (!updateManager || !updateManager.activeUpdate) {
+      this._log.trace("Cannot get the update manager or no update is currently active.");
+      return;
+    }
+
+    let update = updateManager.activeUpdate;
+
+    const payload = {
+      reason: "ready",
+      targetChannel: update.channel,
+      targetVersion: update.appVersion,
+      targetBuildId: update.buildID,
+    };
+
+    const options = {
+      addClientId: true,
+      addEnvironment: true,
+      usePingSender: true,
+    };
+
+    TelemetryController.submitExternalPing(PING_TYPE, payload, options)
+                       .catch(e => this._log.error("_handleUpdateReady - failed to submit update ping", e));
+  },
+
+  /**
+   * The notifications handler.
+   */
+  observe(aSubject, aTopic, aData) {
+    this._log.trace("observe - aTopic: " + aTopic);
+    if (aTopic == UPDATE_DOWNLOADED_TOPIC) {
+      this._handleUpdateReady(aData);
+    }
+  },
+
+  shutdown() {
+    if (!this._enabled) {
+      return;
+    }
+    Services.obs.removeObserver(this, UPDATE_DOWNLOADED_TOPIC);
+  },
+};
--- a/toolkit/components/telemetry/docs/concepts/pings.rst
+++ b/toolkit/components/telemetry/docs/concepts/pings.rst
@@ -22,11 +22,11 @@ We send Telemetry with different ping ty
 
 Pings sent from code that ships with Firefox are listed in the :doc:`data documentation <../data/index>`.
 
 Important examples are:
 
 * :doc:`main <../data/main-ping>` - contains the information collected by Telemetry (Histograms, hang stacks, ...)
 * :doc:`saved-session <../data/main-ping>` - has the same format as a main ping, but it contains the *"classic"* Telemetry payload with measurements covering the whole browser session. This is only a separate type to make storage of saved-session easier server-side. This is temporary and will be removed soon.
 * :doc:`crash <../data/crash-ping>` - a ping that is captured and sent after Firefox crashes.
-* :doc:`new-profile <../data/new-profile-ping>` - sent on the first run of a new profile
-* ``upgrade`` - *planned* - sent right after an upgrade
-* :doc:`deletion <../data/deletion-ping>` - sent when FHR upload is disabled, requesting deletion of the data associated with this user
+* :doc:`new-profile <../data/new-profile-ping>` - sent on the first run of a new profile.
+* :doc:`update <../data/update-ping>` - sent right after an update is downloaded.
+* :doc:`deletion <../data/deletion-ping>` - sent when FHR upload is disabled, requesting deletion of the data associated with this user.
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/docs/data/update-ping.rst
@@ -0,0 +1,52 @@
+
+"update" ping
+==================
+
+This opt-out ping is sent from Firefox Desktop when a browser update is ready to be applied. There is a
+plan to send this ping after an update is successfully applied and the work will happen in `bug 1380256 <https://bugzilla.mozilla.org/show_bug.cgi?id=1380256>`_.
+
+Structure:
+
+.. code-block:: js
+
+    {
+      type: "update",
+      ... common ping data
+      clientId: <UUID>,
+      environment: { ... },
+      payload: {
+        reason: <string>, // "ready"
+        targetChannel: <string>, // "nightly"
+        targetVersion: <string>, // "56.01a"
+        targetBuildId: <string>, // "20080811053724"
+      }
+    }
+
+payload.reason
+--------------
+This field only supports the value ``ready``, meaning that the ping was generated after an update was downloaded
+and marked as ready to be processed. For *non-staged* updates this happens as soon as the download
+finishes and is verified while for *staged* updates this happens before the staging step is started.
+
+payload.targetChannel
+-----------------------
+The Firefox channel the update was fetched from (only valid for pings with reason "ready").
+
+payload.targetVersion
+-----------------------
+The Firefox version the browser is updating to. Follows the same format a application.version (only valid for pings with reason "ready").
+
+payload.targetBuildId
+-----------------------
+The Firefox build id the browser is updating to. Follows the same format a application.buildId (only valid for pings with reason "ready").
+
+Expected behaviours
+-------------------
+The following is a list of conditions and expected behaviours for the ``update`` ping:
+
+- **The ping is generated once every time an update is downloaded, after it was verified:**
+
+  - *for users who saw the privacy policy*, the ``update`` ping is sent immediately;
+  - *for users who did not see the privacy policy*, the ``update`` ping is saved to disk and after the policy is displayed.
+- **If the download of the update retries or other fallback occur**: the ``update`` ping will not be generated
+  multiple times, but only one time once the download is complete and verified.
--- a/toolkit/components/telemetry/docs/internals/preferences.rst
+++ b/toolkit/components/telemetry/docs/internals/preferences.rst
@@ -61,16 +61,20 @@ Preferences
 ``toolkit.telemetry.newProfilePing.enabled``
 
   Enable the :doc:`../data/new-profile` ping on new profiles.
 
 ``toolkit.telemetry.newProfilePing.delay``
 
   Controls the delay after which the :doc:`../data/new-profile` is sent on new profiles.
 
+``toolkit.telemetry.updatePing.enabled``
+
+  Enable the :doc:`../data/update-ping` on browser updates.
+
 Data-choices notification
 -------------------------
 
 ``toolkit.telemetry.reportingpolicy.firstRun``
 
   This preference is not present until the first run. After, its value is set to false. This is used to show the infobar with a more aggressive timeout if it wasn't shown yet.
 
 ``datareporting.policy.firstRunURL``
--- a/toolkit/components/telemetry/moz.build
+++ b/toolkit/components/telemetry/moz.build
@@ -92,16 +92,17 @@ EXTRA_JS_MODULES += [
     'TelemetrySend.jsm',
     'TelemetrySession.jsm',
     'TelemetryStopwatch.jsm',
     'TelemetryStorage.jsm',
     'TelemetryTimestamps.jsm',
     'TelemetryUtils.jsm',
     'ThirdPartyCookieProbe.jsm',
     'UITelemetry.jsm',
+    'UpdatePing.jsm',
 ]
 
 TESTING_JS_MODULES += [
   'tests/unit/TelemetryArchiveTesting.jsm',
 ]
 
 PYTHON_UNITTEST_MANIFESTS += [
     'tests/python/python.ini',
--- a/toolkit/mozapps/update/tests/browser/browser.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 tags = appupdate
 support-files =
   head.js
   downloadPage.html
   testConstants.js
 
+[browser_TelemetryUpdatePing.js]
 [browser_updatesBackgroundWindow.js]
 [browser_updatesBackgroundWindowFailures.js]
 [browser_updatesBasicPrompt.js]
 skip-if = asan
 reason = Bug 1168003
 [browser_updatesBasicPromptNoStaging.js]
 [browser_updatesCantApply.js]
 skip-if = os != 'win'
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_TelemetryUpdatePing.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Cu.import("resource://testing-common/TelemetryArchiveTesting.jsm", this);
+
+/**
+ * Please note that this is really a Telemetry test, not an
+ * "update UI" test like the rest of the tests in this directory.
+ * This test does not live in toolkit/components/telemetry/tests to prevent
+ * duplicating the code for all the test dependencies. Unfortunately, due
+ * to a limitation in the build system, we were not able to simply reference
+ * the dependencies as "support-files" in the test manifest.
+ */
+add_task(async function testUpdatePingReady() {
+  SpecialPowers.pushPrefEnv({set: [
+    [PREF_APP_UPDATE_STAGING_ENABLED, false],
+    [PREF_APP_UPDATE_AUTO, false]
+  ]});
+
+  let updateParams = "promptWaitTime=0";
+
+  let archiveChecker = new TelemetryArchiveTesting.Checker();
+  await archiveChecker.promiseInit();
+
+  // Trigger an "update" ping by downloading and applying an update.
+  await runUpdateTest(updateParams, 1, [
+    {
+      notificationId: "update-available",
+      button: "button",
+      beforeClick() {
+        checkWhatsNewLink("update-available-whats-new");
+      }
+    },
+    {
+      notificationId: "update-restart",
+      button: "secondarybutton",
+      cleanup() {
+        AppMenuNotifications.removeNotification(/.*/);
+      }
+    },
+  ]);
+
+  // We cannot control when the ping will be generated/archived after we trigger
+  // an update, so let's make sure to have one before moving on with validation.
+  let updatePing;
+  await BrowserTestUtils.waitForCondition(async function() {
+    // Check that the ping made it into the Telemetry archive.
+    // The test data is defined in ../data/sharedUpdateXML.js
+    updatePing = await archiveChecker.promiseFindPing("update", [
+        [["payload", "reason"], "ready"],
+        [["payload", "targetBuildId"], "20080811053724"]
+      ]);
+    return !!updatePing;
+  }, "Make sure the ping is generated before trying to validate it.", 500, 100);
+
+  ok(updatePing, "The 'update' ping must be correctly sent.");
+
+  // We don't know the exact value for the other fields, so just check
+  // that they're available.
+  for (let f of ["targetVersion", "targetChannel"]) {
+    ok(f in updatePing.payload,
+       `${f} must be available in the update ping payload.`);
+    ok(typeof(updatePing.payload[f]) == "string",
+       `${f} must have the correct format.`);
+  }
+
+  // Also make sure that the ping contains both a client id and an
+  // environment section.
+  ok("clientId" in updatePing, "The update ping must report a client id.");
+  ok("environment" in updatePing, "The update ping must report the environment.");
+});
--- a/toolkit/mozapps/update/tests/moz.build
+++ b/toolkit/mozapps/update/tests/moz.build
@@ -107,8 +107,11 @@ FINAL_TARGET_FILES += [
     'data/sharedUpdateXML.js',
     'data/simple.mar',
     'data/wrong_product_channel.mar',
     'data/xpcshellUtilsAUS.js',
     'TestAUSReadStrings1.ini',
     'TestAUSReadStrings2.ini',
     'TestAUSReadStrings3.ini',
 ]
+
+with Files("browser/browser_TelemetryUpdatePing.js"):
+    BUG_COMPONENT = ("Toolkit", "Telemetry")