merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 03 May 2017 10:11:26 +0200
changeset 406633 82c2d17e74ef9cdf38a5d5ac4eb3ae846ec30ba4
parent 406601 604acb6a6aece0d9f296dbb7ca091f45713c0c9d (current diff)
parent 406632 ca9dcf386ceb81146a0ff101dc35cbee20e212b0 (diff)
child 406634 f01842786742851345064e9e036f44ce29e3a6bd
child 406648 941dbb6d3cf98adcfe03c673b4508c035877e8bd
child 406725 11cf201a043178c03629304d4767c79f32c396bc
child 407511 438c49f67c639f96b4120500d21f3ef278406b02
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
82c2d17e74ef / 55.0a1 / 20170503100523 / files
nightly linux64
82c2d17e74ef / 55.0a1 / 20170503100422 / files
nightly mac
82c2d17e74ef / 55.0a1 / 20170503030212 / files
nightly win32
82c2d17e74ef / 55.0a1 / 20170503030212 / files
nightly win64
82c2d17e74ef / 55.0a1 / 20170503030212 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/app/profile/firefox.js
layout/style/test/stylo-failures.md
toolkit/components/telemetry/Histograms.json
toolkit/mozapps/extensions/internal/XPIProvider.jsm
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1450,17 +1450,17 @@ DocAccessible::NotifyOfLoading(bool aIsR
 {
   // Mark the document accessible as loading, if it stays alive then we'll mark
   // it as loaded when we receive proper notification.
   mLoadState &= ~eDOMLoaded;
 
   if (!IsLoadEventTarget())
     return;
 
-  if (aIsReloading) {
+  if (aIsReloading && !mLoadEventType) {
     // Fire reload and state busy events on existing document accessible while
     // event from user input flag can be calculated properly and accessible
     // is alive. When new document gets loaded then this one is destroyed.
     RefPtr<AccEvent> reloadEvent =
       new AccEvent(nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD, this);
     nsEventShell::FireEvent(reloadEvent);
   }
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1629,17 +1629,17 @@ pref("browser.crashReports.unsubmittedCh
 // crash report notification across different days and shutdown
 // without a user choice before we suppress the notification for
 // some number of days.
 pref("browser.crashReports.unsubmittedCheck.chancesUntilSuppress", 4);
 pref("browser.crashReports.unsubmittedCheck.autoSubmit", false);
 
 // Preferences for the form autofill system extension
 pref("browser.formautofill.experimental", false);
-pref("browser.formautofill.enabled", false);
+pref("browser.formautofill.enabled", true);
 pref("browser.formautofill.loglevel", "Warn");
 
 // Whether or not to restore a session with lazy-browser tabs.
 pref("browser.sessionstore.restore_tabs_lazily", true);
 
 // Enable safebrowsing v4 tables (suffixed by "-proto") update.
 #ifdef NIGHTLY_BUILD
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,goog-malware-proto,goog-unwanted-proto,test-malware-simple,test-unwanted-simple");
--- a/browser/base/content/newtab/page.js
+++ b/browser/base/content/newtab/page.js
@@ -89,24 +89,24 @@ var gPage = {
       return;
     }
 
     // Bail out if we scheduled before.
     if (this._scheduleUpdateTimeout) {
       return;
     }
 
-    this._scheduleUpdateTimeout = setTimeout(() => {
+    this._scheduleUpdateTimeout = requestIdleCallback(() => {
       // Refresh if the grid is ready.
       if (gGrid.ready) {
         gGrid.refresh();
       }
 
       this._scheduleUpdateTimeout = null;
-    }, SCHEDULE_UPDATE_TIMEOUT_MS);
+    }, {timeout: SCHEDULE_UPDATE_TIMEOUT_MS});
   },
 
   /**
    * Internally initializes the page. This runs only when/if the feature
    * is/gets enabled.
    */
   _init: function Page_init() {
     if (this._initialized)
@@ -216,17 +216,17 @@ var gPage = {
         if (gDrag.isValid(aEvent) && gDrag.draggedSite) {
           aEvent.preventDefault();
           aEvent.stopPropagation();
         }
         break;
       case "visibilitychange":
         // Cancel any delayed updates for hidden pages now that we're visible.
         if (this._scheduleUpdateTimeout) {
-          clearTimeout(this._scheduleUpdateTimeout);
+          cancelIdleCallback(this._scheduleUpdateTimeout);
           this._scheduleUpdateTimeout = null;
 
           // An update was pending so force an update now.
           this.update();
         }
 
         setTimeout(() => this.onPageFirstVisible());
         removeEventListener("visibilitychange", this);
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -72,23 +72,16 @@ registerCleanupFunction(function() {
     gWindow.gBrowser.removeTab(gWindow.gBrowser.tabs[1]);
 
   Object.keys(oldSize).forEach(prop => {
     if (oldSize[prop]) {
       gBrowser.contentWindow[prop] = oldSize[prop];
     }
   });
 
-  // Stop any update timers to prevent unexpected updates in later tests
-  let timer = NewTabUtils.allPages._scheduleUpdateTimeout;
-  if (timer) {
-    clearTimeout(timer);
-    delete NewTabUtils.allPages._scheduleUpdateTimeout;
-  }
-
   Services.prefs.clearUserPref(PREF_NEWTAB_ENABLED);
   Services.prefs.setCharPref(PREF_NEWTAB_DIRECTORYSOURCE, gOrigDirectorySource);
 
   return watchLinksChangeOnce();
 });
 
 function pushPrefs(...aPrefs) {
   return SpecialPowers.pushPrefEnv({"set": aPrefs});
--- a/browser/extensions/formautofill/moz.build
+++ b/browser/extensions/formautofill/moz.build
@@ -16,12 +16,14 @@ FINAL_TARGET_FILES.features['formautofil
 FINAL_TARGET_PP_FILES.features['formautofill@mozilla.org'] += [
   'install.rdf.in'
 ]
 
 BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
+MOCHITEST_MANIFESTS += ['test/mochitest/mochitest.ini']
+
 JAR_MANIFESTS += ['jar.mn']
 
 with Files('**'):
     BUG_COMPONENT = ('Toolkit', 'Form Manager')
--- a/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
+++ b/browser/extensions/formautofill/test/browser/browser_privacyPreferences.js
@@ -17,33 +17,33 @@ add_task(function* test_aboutPreferences
     yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
       is(content.document.querySelector(args.group).hidden, true,
         "Form Autofill group should be hidden");
     });
   });
 });
 
 // Visibility of form autofill group should be visible when opening
-// directly to privacy page. Checkbox is not checked by default.
+// directly to privacy page. Checkbox is checked by default.
 add_task(function* test_aboutPreferencesPrivacy() {
   yield BrowserTestUtils.withNewTab({gBrowser, url: PAGE_PRIVACY}, function* (browser) {
     yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
       is(content.document.querySelector(args.group).hidden, false,
         "Form Autofill group should be visible");
+      is(content.document.querySelector(args.checkbox).checked, true,
+        "Checkbox should be checked");
+    });
+  });
+});
+
+// Checkbox should be unchecked when form autofill is disabled.
+add_task(function* test_autofillDisabledCheckbox() {
+  SpecialPowers.pushPrefEnv({set: [[PREF_AUTOFILL_ENABLED, false]]});
+
+  yield BrowserTestUtils.withNewTab({gBrowser, url: PAGE_PRIVACY}, function* (browser) {
+    yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
+      is(content.document.querySelector(args.group).hidden, false,
+        "Form Autofill group should be visible");
       is(content.document.querySelector(args.checkbox).checked, false,
-        "Checkbox should be unchecked");
+        "Checkbox should be unchecked when Form Autofill is disabled");
     });
   });
 });
-
-// Checkbox should be checked when form autofill is enabled.
-add_task(function* test_autofillEnabledCheckbox() {
-  SpecialPowers.pushPrefEnv({set: [[PREF_AUTOFILL_ENABLED, true]]});
-
-  yield BrowserTestUtils.withNewTab({gBrowser, url: PAGE_PRIVACY}, function* (browser) {
-    yield ContentTask.spawn(browser, TEST_SELECTORS, (args) => {
-      is(content.document.querySelector(args.group).hidden, false,
-        "Form Autofill group should be visible");
-      is(content.document.querySelector(args.checkbox).checked, true,
-        "Checkbox should be checked when Form Autofill is enabled");
-    });
-  });
-});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+  "extends": [
+    "plugin:mozilla/mochitest-test"
+  ],
+};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -0,0 +1,75 @@
+/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SimpleTest.js */
+/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
+/* eslint-disable no-unused-vars */
+
+"use strict";
+
+let formFillChromeScript;
+
+function setInput(selector, value) {
+  let input = document.querySelector("input" + selector);
+  input.value = value;
+  input.focus();
+}
+
+function checkMenuEntries(expectedValues) {
+  let actualValues = getMenuEntries();
+
+  is(actualValues.length, expectedValues.length, " Checking length of expected menu");
+  for (let i = 0; i < expectedValues.length; i++) {
+    is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
+  }
+}
+
+function addProfile(profile) {
+  return new Promise(resolve => {
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:AddProfile", {profile});
+    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileAdded", function onAdded(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileAdded", onAdded);
+
+      resolve();
+    });
+  });
+}
+
+function removeProfile(guid) {
+  return new Promise(resolve => {
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:RemoveProfile", {guid});
+    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileRemoved", function onDeleted(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileRemoved", onDeleted);
+
+      resolve();
+    });
+  });
+}
+
+function updateProfile(guid, profile) {
+  return new Promise(resolve => {
+    formFillChromeScript.sendAsyncMessage("FormAutofillTest:UpdateProfile", {profile, guid});
+    formFillChromeScript.addMessageListener("FormAutofillTest:ProfileUpdated", function onUpdated(data) {
+      formFillChromeScript.removeMessageListener("FormAutofillTest:ProfileUpdated", onUpdated);
+
+      resolve();
+    });
+  });
+}
+
+function formAutoFillCommonSetup() {
+  let chromeURL = SimpleTest.getTestFileURL("formautofill_parent_utils.js");
+  formFillChromeScript = SpecialPowers.loadChromeScript(chromeURL);
+  SpecialPowers.setBoolPref("dom.forms.autocomplete.experimental", true);
+  formFillChromeScript.addMessageListener("onpopupshown", ({results}) => {
+    gLastAutoCompleteResults = results;
+    if (gPopupShownListener) {
+      gPopupShownListener({results});
+    }
+  });
+
+  SimpleTest.registerCleanupFunction(() => {
+    SpecialPowers.clearUserPref("dom.forms.autocomplete.experimental");
+    formFillChromeScript.sendAsyncMessage("cleanup");
+    formFillChromeScript.destroy();
+  });
+}
+
+formAutoFillCommonSetup();
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -0,0 +1,63 @@
+// assert is available to chrome scripts loaded via SpecialPowers.loadChromeScript.
+/* global assert */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var ParentUtils = {
+  cleanUpProfile() {
+    Services.cpmm.addMessageListener("FormAutofill:Profiles", function getResult(result) {
+      Services.cpmm.removeMessageListener("FormAutofill:Profiles", getResult);
+
+      let profiles = result.data;
+      Services.cpmm.sendAsyncMessage("FormAutofill:RemoveProfiles",
+                                     {guids: profiles.map(profile => profile.guid)});
+    });
+
+    Services.cpmm.sendAsyncMessage("FormAutofill:GetProfiles", {searchString: ""});
+  },
+
+  updateProfile(type, chromeMsg, msgData, contentMsg) {
+    Services.cpmm.sendAsyncMessage(chromeMsg, msgData);
+    Services.obs.addObserver(function observer(subject, topic, data) {
+      if (data != type) {
+        return;
+      }
+
+      Services.obs.removeObserver(observer, topic);
+      sendAsyncMessage(contentMsg);
+    }, "formautofill-storage-changed");
+  },
+
+  observe(subject, topic, data) {
+    assert.ok(topic === "formautofill-storage-changed");
+    sendAsyncMessage("formautofill-storage-changed", {subject: null, topic, data});
+  },
+
+  cleanup() {
+    Services.obs.removeObserver(this, "formautofill-storage-changed");
+    this.cleanUpProfile();
+  },
+};
+
+ParentUtils.cleanUpProfile();
+Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
+
+addMessageListener("FormAutofillTest:AddProfile", (msg) => {
+  ParentUtils.updateProfile("add", "FormAutofill:SaveProfile", msg, "FormAutofillTest:ProfileAdded");
+});
+
+addMessageListener("FormAutofillTest:RemoveProfile", (msg) => {
+  ParentUtils.updateProfile("remove", "FormAutofill:Removefile", msg, "FormAutofillTest:ProfileRemoved");
+});
+
+addMessageListener("FormAutofillTest:UpdateProfile", (msg) => {
+  ParentUtils.updateProfile("update", "FormAutofill:SaveProfile", msg, "FormAutofillTest:ProfileUpdated");
+});
+
+addMessageListener("cleanup", () => {
+  ParentUtils.cleanup();
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/mochitest.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+support-files =
+  ../../../../../toolkit/components/satchel/test/satchel_common.js
+  ../../../../../toolkit/components/satchel/test/parent_utils.js
+  formautofill_common.js
+  formautofill_parent_utils.js
+
+[test_basic_autocomplete_form.html]
+
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/mochitest/test_basic_autocomplete_form.html
@@ -0,0 +1,176 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test basic autofill</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="formautofill_common.js"></script>
+  <script type="text/javascript" src="satchel_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+Form autofill test: simple form profile autofill
+
+<script>
+/* import-globals-from ../../../../../testing/mochitest/tests/SimpleTest/SpawnTask.js */
+/* import-globals-from ../../../../../toolkit/components/satchel/test/satchel_common.js */
+/* import-globals-from formautofill_common.js */
+
+"use strict";
+
+let expectingPopup = null;
+let MOCK_STORAGE = [{
+  organization: "Sesame Street",
+  "street-address": "123 Sesame Street.",
+  tel: "1-345-345-3456",
+}, {
+  organization: "Mozilla",
+  "street-address": "331 E. Evelyn Avenue",
+  tel: "1-650-903-0800",
+}];
+
+function expectPopup() {
+  info("expecting a popup");
+  return new Promise(resolve => {
+    expectingPopup = resolve;
+  });
+}
+
+function popupShownListener() {
+  info("popup shown for test ");
+  if (expectingPopup) {
+    expectingPopup();
+    expectingPopup = null;
+  }
+}
+
+function checkInputFilled(element, expectedvalue) {
+  return new Promise(resolve => {
+    element.addEventListener("change", function onChange() {
+      is(element.value, expectedvalue, "Checking " + element.name + " field");
+      resolve();
+    }, {once: true});
+  });
+}
+
+function checkAutoCompleteInputFilled(element, expectedvalue) {
+  return new Promise(resolve => {
+    element.addEventListener("DOMAutoComplete", function onChange() {
+      is(element.value, expectedvalue, "Checking " + element.name + " field");
+      resolve();
+    }, {once: true});
+  });
+}
+
+function checkFormFilled(profile) {
+  let promises = [];
+  for (let prop in profile) {
+    let element = document.getElementById(prop);
+    if (document.activeElement == element) {
+      promises.push(checkAutoCompleteInputFilled(element, profile[prop]));
+    } else {
+      promises.push(checkInputFilled(element, profile[prop]));
+    }
+  }
+  doKey("return");
+  return Promise.all(promises);
+}
+
+function* setupProfileStorage() {
+  yield addProfile(MOCK_STORAGE[0]);
+  yield addProfile(MOCK_STORAGE[1]);
+}
+
+function* setupFormHistory() {
+  yield updateFormHistory([
+    {op: "add", fieldname: "tel", value: "1-234-567-890"},
+    {op: "add", fieldname: "country", value: "US"},
+  ]);
+}
+
+// Form with history only.
+add_task(function* history_only_menu_checking() {
+  yield setupFormHistory();
+
+  setInput("#tel", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(["1-234-567-890"]);
+});
+
+// Form with both history and profile storage.
+add_task(function* check_menu_when_both_existed() {
+  yield setupProfileStorage();
+
+  setInput("#organization", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(profile =>
+    JSON.stringify({primary: profile.organization, secondary: profile["street-address"]})
+  ));
+
+  setInput("#street-address", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(profile =>
+    JSON.stringify({primary: profile["street-address"], secondary: profile.organization})
+  ));
+
+  setInput("#tel", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(profile =>
+    JSON.stringify({primary: profile.tel, secondary: profile["street-address"]})
+  ));
+});
+
+// Display history search result if no matched data in profiles.
+add_task(function* check_fallback_for_mismatched_field() {
+  setInput("#country", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(["US"]);
+});
+
+// Autofill the profile from dropdown menu.
+add_task(function* check_fields_after_form_autofill() {
+  setInput("#organization", "Moz");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(MOCK_STORAGE.map(profile =>
+    JSON.stringify({primary: profile.organization, secondary: profile["street-address"]})
+  ).slice(1));
+  doKey("down");
+  yield checkFormFilled(MOCK_STORAGE[1]);
+});
+
+// Fallback to history search after autofill profile.
+add_task(function* check_fallback_after_form_autofill() {
+  setInput("#tel", "");
+  doKey("down");
+  yield expectPopup();
+  checkMenuEntries(["1-234-567-890"]);
+});
+
+registerPopupShownListener(popupShownListener);
+
+</script>
+
+<p id="display"></p>
+
+<div id="content">
+
+  <form id="form1">
+    <p>This is a basic form.</p>
+    <p><label>organization: <input id="organization" name="organization" autocomplete="organization" type="text"></label></p>
+    <p><label>streetAddress: <input id="street-address" name="street-address" autocomplete="street-address" type="text"></label></p>
+    <p><label>tel: <input id="tel" name="tel" autocomplete="tel" type="text"></label></p>
+    <p><label>country: <input id="country" name="country" autocomplete="country" type="text"></label></p>
+  </form>
+
+</div>
+
+<pre id="test"></pre>
+</body>
+</html>
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -16,16 +16,18 @@ add_task(function* test_profileSavedFiel
 
   formAutofillParent._uninit();
 });
 
 add_task(function* test_profileSavedFieldNames_observe() {
   let formAutofillParent = new FormAutofillParent();
   sinon.stub(formAutofillParent, "_updateSavedFieldNames");
 
+  formAutofillParent.init();
+
   // profile added => Need to trigger updateValidFields
   formAutofillParent.observe(null, "formautofill-storage-changed", "add");
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
 
   // profile removed => Need to trigger updateValidFields
   formAutofillParent._updateSavedFieldNames.reset();
   formAutofillParent.observe(null, "formautofill-storage-changed", "remove");
   do_check_eq(formAutofillParent._updateSavedFieldNames.called, true);
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -293,16 +293,17 @@ bool nsContentUtils::sIsWebComponentsEna
 bool nsContentUtils::sIsCustomElementsEnabled = false;
 bool nsContentUtils::sPrivacyResistFingerprinting = false;
 bool nsContentUtils::sSendPerformanceTimingNotifications = false;
 bool nsContentUtils::sUseActivityCursor = false;
 bool nsContentUtils::sAnimationsAPICoreEnabled = false;
 bool nsContentUtils::sAnimationsAPIElementAnimateEnabled = false;
 bool nsContentUtils::sGetBoxQuadsEnabled = false;
 bool nsContentUtils::sSkipCursorMoveForSameValueSet = false;
+bool nsContentUtils::sRequestIdleCallbackEnabled = false;
 
 int32_t nsContentUtils::sPrivacyMaxInnerWidth = 1000;
 int32_t nsContentUtils::sPrivacyMaxInnerHeight = 1000;
 
 uint32_t nsContentUtils::sHandlingInputTimeout = 1000;
 
 uint32_t nsContentUtils::sCookiesLifetimePolicy = nsICookieService::ACCEPT_NORMALLY;
 uint32_t nsContentUtils::sCookiesBehavior = nsICookieService::BEHAVIOR_ACCEPT;
@@ -645,16 +646,19 @@ nsContentUtils::Init()
 
   Preferences::AddBoolVarCache(&sGetBoxQuadsEnabled,
                                "layout.css.getBoxQuads.enabled", false);
 
   Preferences::AddBoolVarCache(&sSkipCursorMoveForSameValueSet,
                                "dom.input.skip_cursor_move_for_same_value_set",
                                true);
 
+  Preferences::AddBoolVarCache(&sRequestIdleCallbackEnabled,
+                               "dom.requestIdleCallback.enabled", false);
+
   Element::InitCCCallbacks();
 
   nsCOMPtr<nsIUUIDGenerator> uuidGenerator =
     do_GetService("@mozilla.org/uuid-generator;1", &rv);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   uuidGenerator.forget(&sUUIDGenerator);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -2178,16 +2178,24 @@ public:
    * Returns true if the getBoxQuads API should be enabled.
    */
   static bool GetBoxQuadsEnabled()
   {
     return sGetBoxQuadsEnabled;
   }
 
   /**
+   * Returns true if the requestIdleCallback API should be enabled.
+   */
+  static bool RequestIdleCallbackEnabled()
+  {
+    return sRequestIdleCallbackEnabled;
+  }
+
+  /**
    * Return true if this doc is controlled by a ServiceWorker.
    */
   static bool IsControlledByServiceWorker(nsIDocument* aDocument);
 
   /**
    * Fire mutation events for changes caused by parsing directly into a
    * context node.
    *
@@ -3015,16 +3023,17 @@ private:
   static bool sIsCustomElementsEnabled;
   static bool sPrivacyResistFingerprinting;
   static bool sSendPerformanceTimingNotifications;
   static bool sUseActivityCursor;
   static bool sAnimationsAPICoreEnabled;
   static bool sAnimationsAPIElementAnimateEnabled;
   static bool sGetBoxQuadsEnabled;
   static bool sSkipCursorMoveForSameValueSet;
+  static bool sRequestIdleCallbackEnabled;
   static uint32_t sCookiesLifetimePolicy;
   static uint32_t sCookiesBehavior;
 
   static int32_t sPrivacyMaxInnerWidth;
   static int32_t sPrivacyMaxInnerHeight;
 
   static nsHtml5StringParser* sHTMLFragmentParser;
   static nsIParser* sXMLFragmentParser;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -5164,16 +5164,24 @@ nsGlobalWindow::IsShowModalDialogEnabled
   if (!sAddedPrefCache) {
     Preferences::AddBoolVarCache(&sIsDisabled, sShowModalDialogPref, false);
     sAddedPrefCache = true;
   }
 
   return !sIsDisabled && !XRE_IsContentProcess();
 }
 
+/* static */ bool
+nsGlobalWindow::IsRequestIdleCallbackEnabled(JSContext* aCx, JSObject* aObj)
+{
+  // The requestIdleCallback should always be enabled for system code.
+  return nsContentUtils::RequestIdleCallbackEnabled() ||
+         nsContentUtils::IsSystemCaller(aCx);
+}
+
 nsIDOMOfflineResourceList*
 nsGlobalWindow::GetApplicationCache(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   if (!mApplicationCache) {
     nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell()));
     if (!webNav || !mDoc) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -470,16 +470,18 @@ public:
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
   already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
 
   static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj);
 
   static bool IsShowModalDialogEnabled(JSContext* /* unused */ = nullptr,
                                        JSObject* /* unused */ = nullptr);
 
+  static bool IsRequestIdleCallbackEnabled(JSContext* aCx, JSObject* /* unused */);
+
   bool DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj,
                  JS::Handle<jsid> aId,
                  JS::MutableHandle<JS::PropertyDescriptor> aDesc);
   // The return value is whether DoResolve might end up resolving the given id.
   // If in doubt, return true.
   static bool MayResolve(jsid aId);
 
   void GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames,
--- a/dom/webidl/IdleDeadline.webidl
+++ b/dom/webidl/IdleDeadline.webidl
@@ -2,13 +2,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is:
  * https://w3c.github.io/requestidlecallback/
  */
 
-[Pref="dom.requestIdleCallback.enabled"]
+[Func="nsGlobalWindow::IsRequestIdleCallbackEnabled"]
 interface IdleDeadline {
   DOMHighResTimeStamp timeRemaining();
   readonly attribute boolean didTimeout;
 };
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -482,20 +482,20 @@ partial interface Window {
     [Pref="dom.paintWorklet.enabled", Throws]
     readonly attribute Worklet paintWorklet;
 };
 
 Window implements ChromeWindow;
 Window implements WindowOrWorkerGlobalScope;
 
 partial interface Window {
-  [Throws, Pref="dom.requestIdleCallback.enabled"]
+  [Throws, Func="nsGlobalWindow::IsRequestIdleCallbackEnabled"]
   unsigned long requestIdleCallback(IdleRequestCallback callback,
                                     optional IdleRequestOptions options);
-  [Pref="dom.requestIdleCallback.enabled"]
+  [Func="nsGlobalWindow::IsRequestIdleCallbackEnabled"]
   void          cancelIdleCallback(unsigned long handle);
 };
 
 dictionary IdleRequestOptions {
   unsigned long timeout;
 };
 
 callback IdleRequestCallback = void (IdleDeadline deadline);
--- a/image/test/mochitest/bug1217571-iframe.html
+++ b/image/test/mochitest/bug1217571-iframe.html
@@ -6,12 +6,12 @@ https://bugzilla.mozilla.org/show_bug.cg
 <head>
   <title>iframe for Bug 1217571</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
-  <img src="damon.jpg">
+  <img src="bug1217571.jpg">
 </div>
 </body>
 </html>
copy from image/test/mochitest/damon.jpg
copy to image/test/mochitest/bug1217571.jpg
--- a/image/test/mochitest/mochitest.ini
+++ b/image/test/mochitest/mochitest.ini
@@ -36,16 +36,17 @@ support-files =
   bug89419.sjs
   bug900200.png
   bug900200-ref.png
   bug1132427.html
   bug1132427.gif
   bug1180105.sjs
   bug1180105-waiter.sjs
   bug1217571-iframe.html
+  bug1217571.jpg
   bug1319025.png
   bug1319025-ref.png
   clear.gif
   clear.png
   clear2.gif
   clear2-results.gif
   damon.jpg
   error-early.png
--- a/layout/style/ServoBindings.cpp
+++ b/layout/style/ServoBindings.cpp
@@ -1537,17 +1537,17 @@ Gecko_nsStyleSVGPaint_CopyFrom(nsStyleSV
 {
   *aDest = *aSrc;
 }
 
 void
 Gecko_nsStyleSVGPaint_SetURLValue(nsStyleSVGPaint* aPaint, ServoBundledURI aURI)
 {
   RefPtr<css::URLValue> url = aURI.IntoCssUrl();
-  aPaint->SetPaintServer(url.get(), NS_RGB(0, 0, 0));
+  aPaint->SetPaintServer(url.get());
 }
 
 void Gecko_nsStyleSVGPaint_Reset(nsStyleSVGPaint* aPaint)
 {
   aPaint->SetNone();
 }
 
 void
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -4187,16 +4187,26 @@ StyleClipBasicShapeToCSSArray(const Styl
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown shape type");
       return false;
   }
   aResult->Item(1).SetEnumValue(aClipPath.GetReferenceBox());
   return true;
 }
 
+static void
+SetFallbackValue(nsCSSValuePair* aPair, const nsStyleSVGPaint& aPaint)
+{
+  if (aPaint.GetFallbackType() == eStyleSVGFallbackType_Color) {
+    aPair->mYValue.SetColorValue(aPaint.GetFallbackColor());
+  } else {
+    aPair->mYValue.SetNoneValue();
+  }
+}
+
 bool
 StyleAnimationValue::ExtractComputedValue(nsCSSPropertyID aProperty,
                                           nsStyleContext* aStyleContext,
                                           StyleAnimationValue& aComputedValue)
 {
   MOZ_ASSERT(0 <= aProperty && aProperty < eCSSProperty_COUNT_no_shorthands,
              "bad property");
   const void* styleStruct =
@@ -4704,32 +4714,43 @@ StyleAnimationValue::ExtractComputedValu
           aComputedValue.SetColorValue(paint.GetColor());
           return true;
         case eStyleSVGPaintType_Server: {
           css::URLValue* url = paint.GetPaintServer();
           if (!url) {
             NS_WARNING("Null paint server");
             return false;
           }
-          nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
-          pair->mXValue.SetURLValue(url);
-          pair->mYValue.SetColorValue(paint.GetFallbackColor());
-          aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
-                                                      eUnit_CSSValuePair);
+          if (paint.GetFallbackType() != eStyleSVGFallbackType_NotSet) {
+            nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+            pair->mXValue.SetURLValue(url);
+            SetFallbackValue(pair, paint);
+            aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+                                                        eUnit_CSSValuePair);
+          } else {
+            auto result = MakeUnique<nsCSSValue>();
+            result->SetURLValue(url);
+            aComputedValue.SetAndAdoptCSSValueValue(
+              result.release(), eUnit_URL);
+          }
           return true;
         }
         case eStyleSVGPaintType_ContextFill:
         case eStyleSVGPaintType_ContextStroke: {
-          nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
-          pair->mXValue.SetIntValue(paint.Type() == eStyleSVGPaintType_ContextFill ?
-                                    NS_COLOR_CONTEXT_FILL : NS_COLOR_CONTEXT_STROKE,
-                                    eCSSUnit_Enumerated);
-          pair->mYValue.SetColorValue(paint.GetFallbackColor());
-          aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
-                                                      eUnit_CSSValuePair);
+          int32_t value = paint.Type() == eStyleSVGPaintType_ContextFill ?
+                            NS_COLOR_CONTEXT_FILL : NS_COLOR_CONTEXT_STROKE;
+          if (paint.GetFallbackType() != eStyleSVGFallbackType_NotSet) {
+            nsAutoPtr<nsCSSValuePair> pair(new nsCSSValuePair);
+            pair->mXValue.SetIntValue(value, eCSSUnit_Enumerated);
+            SetFallbackValue(pair, paint);
+            aComputedValue.SetAndAdoptCSSValuePairValue(pair.forget(),
+                                                        eUnit_CSSValuePair);
+          } else {
+            aComputedValue.SetIntValue(value, eUnit_Enumerated);
+          }
           return true;
         }
         default:
           MOZ_ASSERT(paint.Type() == eStyleSVGPaintType_None,
                      "Unexpected SVG paint type");
           aComputedValue.SetNoneValue();
           return true;
       }
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -17297,34 +17297,34 @@ CSSParserImpl::ParsePaint(nsCSSPropertyI
   nsCSSValue x, y;
 
   if (ParseVariant(x, VARIANT_HC | VARIANT_NONE | VARIANT_URL |
                       VARIANT_OPENTYPE_SVG_KEYWORD,
                    nsCSSProps::kContextPatternKTable) != CSSParseResult::Ok) {
     return false;
   }
 
+  bool hasFallback = false;
   bool canHaveFallback = x.GetUnit() == eCSSUnit_URL ||
                          x.GetUnit() == eCSSUnit_Enumerated;
   if (canHaveFallback) {
     CSSParseResult result =
       ParseVariant(y, VARIANT_COLOR | VARIANT_NONE, nullptr);
     if (result == CSSParseResult::Error) {
       return false;
-    } else if (result == CSSParseResult::NotFound) {
-      y.SetNoneValue();
-    }
-  }
-
-  if (!canHaveFallback) {
-    AppendValue(aPropID, x);
-  } else {
+    }
+    hasFallback = (result != CSSParseResult::NotFound);
+  }
+
+  if (hasFallback) {
     nsCSSValue val;
     val.SetPairValue(x, y);
     AppendValue(aPropID, val);
+  } else {
+    AppendValue(aPropID, x);
   }
   return true;
 }
 
 bool
 CSSParserImpl::ParseDasharray()
 {
   nsCSSValue value;
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -5764,55 +5764,68 @@ nsComputedDOMStyle::GetFrameBoundsHeight
 
   AssertFlushedPendingReflows();
 
   aHeight = nsStyleTransformMatrix::TransformReferenceBox(mInnerFrame).Height();
   return true;
 }
 
 already_AddRefed<CSSValue>
+nsComputedDOMStyle::GetFallbackValue(const nsStyleSVGPaint* aPaint)
+{
+  RefPtr<nsROCSSPrimitiveValue> fallback = new nsROCSSPrimitiveValue;
+  if (aPaint->GetFallbackType() == eStyleSVGFallbackType_Color) {
+    SetToRGBAColor(fallback, aPaint->GetFallbackColor());
+  } else {
+    fallback->SetIdent(eCSSKeyword_none);
+  }
+  return fallback.forget();
+}
+
+already_AddRefed<CSSValue>
 nsComputedDOMStyle::GetSVGPaintFor(bool aFill)
 {
   RefPtr<nsROCSSPrimitiveValue> val = new nsROCSSPrimitiveValue;
 
   const nsStyleSVG* svg = StyleSVG();
-  const nsStyleSVGPaint* paint = nullptr;
-
-  if (aFill)
-    paint = &svg->mFill;
-  else
-    paint = &svg->mStroke;
+  const nsStyleSVGPaint* paint = aFill ? &svg->mFill : &svg->mStroke;
 
   nsAutoString paintString;
 
   switch (paint->Type()) {
     case eStyleSVGPaintType_None:
       val->SetIdent(eCSSKeyword_none);
       break;
     case eStyleSVGPaintType_Color:
       SetToRGBAColor(val, paint->GetColor());
       break;
     case eStyleSVGPaintType_Server: {
-      RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
-      RefPtr<nsROCSSPrimitiveValue> fallback = new nsROCSSPrimitiveValue;
       SetValueToURLValue(paint->GetPaintServer(), val);
-      SetToRGBAColor(fallback, paint->GetFallbackColor());
-
-      valueList->AppendCSSValue(val.forget());
-      valueList->AppendCSSValue(fallback.forget());
-      return valueList.forget();
+      if (paint->GetFallbackType() != eStyleSVGFallbackType_NotSet) {
+        RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+        RefPtr<CSSValue> fallback = GetFallbackValue(paint);
+        valueList->AppendCSSValue(val.forget());
+        valueList->AppendCSSValue(fallback.forget());
+        return valueList.forget();
+      }
+      break;
     }
     case eStyleSVGPaintType_ContextFill:
-      val->SetIdent(eCSSKeyword_context_fill);
-      // XXXheycam context-fill and context-stroke can have fallback colors,
-      // so they should be serialized here too
+    case eStyleSVGPaintType_ContextStroke: {
+      val->SetIdent(paint->Type() == eStyleSVGPaintType_ContextFill ?
+                    eCSSKeyword_context_fill : eCSSKeyword_context_stroke);
+      if (paint->GetFallbackType() != eStyleSVGFallbackType_NotSet) {
+        RefPtr<nsDOMCSSValueList> valueList = GetROCSSValueList(false);
+        RefPtr<CSSValue> fallback = GetFallbackValue(paint);
+        valueList->AppendCSSValue(val.forget());
+        valueList->AppendCSSValue(fallback.forget());
+        return valueList.forget();
+      }
       break;
-    case eStyleSVGPaintType_ContextStroke:
-      val->SetIdent(eCSSKeyword_context_stroke);
-      break;
+    }
   }
 
   return val.forget();
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetFill()
 {
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -193,16 +193,18 @@ private:
   already_AddRefed<CSSValue> GetBorderStyleFor(mozilla::Side aSide);
 
   already_AddRefed<CSSValue> GetBorderWidthFor(mozilla::Side aSide);
 
   already_AddRefed<CSSValue> GetBorderColorFor(mozilla::Side aSide);
 
   already_AddRefed<CSSValue> GetMarginWidthFor(mozilla::Side aSide);
 
+  already_AddRefed<CSSValue> GetFallbackValue(const nsStyleSVGPaint* aPaint);
+
   already_AddRefed<CSSValue> GetSVGPaintFor(bool aFill);
 
   // Appends all aLineNames (may be empty) space-separated to aResult.
   void AppendGridLineNames(nsString& aResult,
                            const nsTArray<nsString>& aLineNames);
   // Appends aLineNames as a CSSValue* to aValueList.  If aLineNames is empty
   // a value ("[]") is only appended if aSuppressEmptyList is false.
   void AppendGridLineNames(nsDOMCSSValueList* aValueList,
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -9352,42 +9352,61 @@ SetSVGPaint(const nsCSSValue& aValue, co
   } else if (aValue.GetUnit() == eCSSUnit_None) {
     aResult.SetNone();
   } else if (aValue.GetUnit() == eCSSUnit_Initial) {
     if (aInitialPaintType == eStyleSVGPaintType_None) {
       aResult.SetNone();
     } else {
       aResult.SetColor(NS_RGB(0, 0, 0));
     }
+  } else if (aValue.GetUnit() == eCSSUnit_URL) {
+    aResult.SetPaintServer(aValue.GetURLStructValue());
+  } else if (aValue.GetUnit() == eCSSUnit_Enumerated) {
+    switch (aValue.GetIntValue()) {
+    case NS_COLOR_CONTEXT_FILL:
+      aResult.SetContextValue(eStyleSVGPaintType_ContextFill);
+      break;
+    case NS_COLOR_CONTEXT_STROKE:
+      aResult.SetContextValue(eStyleSVGPaintType_ContextStroke);
+      break;
+    default:
+      NS_NOTREACHED("unknown keyword as paint server value");
+    }
   } else if (SetColor(aValue, NS_RGB(0, 0, 0), aPresContext, aContext,
                       color, aConditions)) {
     aResult.SetColor(color);
   } else if (aValue.GetUnit() == eCSSUnit_Pair) {
     const nsCSSValuePair& pair = aValue.GetPairValue();
 
+    nsStyleSVGFallbackType fallbackType;
     nscolor fallback;
     if (pair.mYValue.GetUnit() == eCSSUnit_None) {
+      fallbackType = eStyleSVGFallbackType_None;
       fallback = NS_RGBA(0, 0, 0, 0);
     } else {
       MOZ_ASSERT(pair.mYValue.GetUnit() != eCSSUnit_Inherit,
                  "cannot inherit fallback colour");
+      fallbackType = eStyleSVGFallbackType_Color;
       SetColor(pair.mYValue, NS_RGB(0, 0, 0), aPresContext, aContext,
                fallback, aConditions);
     }
 
     if (pair.mXValue.GetUnit() == eCSSUnit_URL) {
-      aResult.SetPaintServer(pair.mXValue.GetURLStructValue(), fallback);
+      aResult.SetPaintServer(pair.mXValue.GetURLStructValue(),
+                             fallbackType, fallback);
     } else if (pair.mXValue.GetUnit() == eCSSUnit_Enumerated) {
 
       switch (pair.mXValue.GetIntValue()) {
       case NS_COLOR_CONTEXT_FILL:
-        aResult.SetContextValue(eStyleSVGPaintType_ContextFill, fallback);
+        aResult.SetContextValue(eStyleSVGPaintType_ContextFill,
+                                fallbackType, fallback);
         break;
       case NS_COLOR_CONTEXT_STROKE:
-        aResult.SetContextValue(eStyleSVGPaintType_ContextStroke, fallback);
+        aResult.SetContextValue(eStyleSVGPaintType_ContextStroke,
+                                fallbackType, fallback);
         break;
       default:
         NS_NOTREACHED("unknown keyword as paint server value");
       }
 
     } else {
       NS_NOTREACHED("malformed paint server value");
     }
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1315,78 +1315,87 @@ nsStyleSVGPaint::Assign(const nsStyleSVG
     case eStyleSVGPaintType_None:
       SetNone();
       break;
     case eStyleSVGPaintType_Color:
       SetColor(aOther.mPaint.mColor);
       break;
     case eStyleSVGPaintType_Server:
       SetPaintServer(aOther.mPaint.mPaintServer,
+                     aOther.mFallbackType,
                      aOther.mFallbackColor);
       break;
     case eStyleSVGPaintType_ContextFill:
     case eStyleSVGPaintType_ContextStroke:
-      SetContextValue(aOther.mType, aOther.mFallbackColor);
+      SetContextValue(aOther.mType,
+                      aOther.mFallbackType,
+                      aOther.mFallbackColor);
       break;
   }
 }
 
 void
 nsStyleSVGPaint::SetNone()
 {
   Reset();
   mType = eStyleSVGPaintType_None;
 }
 
 void
 nsStyleSVGPaint::SetContextValue(nsStyleSVGPaintType aType,
+                                 nsStyleSVGFallbackType aFallbackType,
                                  nscolor aFallbackColor)
 {
   MOZ_ASSERT(aType == eStyleSVGPaintType_ContextFill ||
              aType == eStyleSVGPaintType_ContextStroke);
   Reset();
   mType = aType;
+  mFallbackType = aFallbackType;
   mFallbackColor = aFallbackColor;
 }
 
 void
 nsStyleSVGPaint::SetColor(nscolor aColor)
 {
   Reset();
   mType = eStyleSVGPaintType_Color;
   mPaint.mColor = aColor;
 }
 
 void
 nsStyleSVGPaint::SetPaintServer(css::URLValue* aPaintServer,
+                                nsStyleSVGFallbackType aFallbackType,
                                 nscolor aFallbackColor)
 {
   MOZ_ASSERT(aPaintServer);
   Reset();
   mType = eStyleSVGPaintType_Server;
   mPaint.mPaintServer = aPaintServer;
   mPaint.mPaintServer->AddRef();
+  mFallbackType = aFallbackType;
   mFallbackColor = aFallbackColor;
 }
 
 bool nsStyleSVGPaint::operator==(const nsStyleSVGPaint& aOther) const
 {
   if (mType != aOther.mType) {
     return false;
   }
   switch (mType) {
     case eStyleSVGPaintType_Color:
       return mPaint.mColor == aOther.mPaint.mColor;
     case eStyleSVGPaintType_Server:
       return DefinitelyEqualURIs(mPaint.mPaintServer,
                                  aOther.mPaint.mPaintServer) &&
+             mFallbackType == aOther.mFallbackType &&
              mFallbackColor == aOther.mFallbackColor;
     case eStyleSVGPaintType_ContextFill:
     case eStyleSVGPaintType_ContextStroke:
-      return mFallbackColor == aOther.mFallbackColor;
+      return mFallbackType == aOther.mFallbackType &&
+             mFallbackColor == aOther.mFallbackColor;
     default:
       MOZ_ASSERT(mType == eStyleSVGPaintType_None,
                  "Unexpected SVG paint type");
       return true;
   }
 }
 
 // --------------------
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -3405,19 +3405,28 @@ public:
 
   nsStyleSVGPaint& operator=(const nsStyleSVGPaint& aOther);
 
   nsStyleSVGPaintType Type() const { return mType; }
 
   void SetNone();
   void SetColor(nscolor aColor);
   void SetPaintServer(mozilla::css::URLValue* aPaintServer,
+                      nsStyleSVGFallbackType aFallbackType,
                       nscolor aFallbackColor);
+  void SetPaintServer(mozilla::css::URLValue* aPaintServer) {
+    SetPaintServer(aPaintServer, eStyleSVGFallbackType_NotSet,
+                   NS_RGB(0, 0, 0));
+  }
   void SetContextValue(nsStyleSVGPaintType aType,
+                       nsStyleSVGFallbackType aFallbackType,
                        nscolor aFallbackColor);
+  void SetContextValue(nsStyleSVGPaintType aType) {
+    SetContextValue(aType, eStyleSVGFallbackType_NotSet, NS_RGB(0, 0, 0));
+  }
 
   nscolor GetColor() const {
     MOZ_ASSERT(mType == eStyleSVGPaintType_Color);
     return mPaint.mColor;
   }
 
   mozilla::css::URLValue* GetPaintServer() const {
     MOZ_ASSERT(mType == eStyleSVGPaintType_Server);
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -69,18 +69,16 @@ to mochitest command.
   * test_bug413958.html `monitorConsole` [3]
   * test_parser_diagnostics_unprintables.html [550]
 * Transition support:
   * test_transitions.html: pseudo elements [12]
   * Events:
     * test_animations_event_order.html [2]
 * test_computed_style.html `gradient`: -moz- and -webkit-prefixed gradient values [35]
 * ... `mask`: mask-image isn't set properly bug 1341667 [10]
-* ... `fill`: svg paint should distinguish whether there is fallback bug 1347409 [2]
-* ... `stroke`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * character not properly escaped servo/servo#15947
   * test_parse_url.html [1]
   * test_bug829816.html [8]
 * \@counter-style support bug 1328319
   * test_counter_descriptor_storage.html [1]
   * test_counter_style.html [5]
   * test_rule_insertion.html `@counter-style` [1]
   * ... `cjk-decimal` [1]
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -465,32 +465,32 @@ var noframe_container = document.getElem
   frame_container.appendChild(p);
 
   var docPath = document.URL.substring(0, document.URL.lastIndexOf("/") + 1);
 
   var localURL = "url(\"#foo\")";
   var nonLocalURL = "url(\"foo.svg#foo\")";
   var resolvedNonLocalURL = "url(\"" + docPath + "foo.svg#foo\")";
 
-  var testStyles = {
-    "mask" : "",
-    "markerStart" : "",
-    "markerMid" : "",
-    "markerEnd" : "",
-    "clipPath" : "",
-    "filter" : "",
-    "fill" : " rgba(0, 0, 0, 0)",
-    "stroke" : " rgba(0, 0, 0, 0)",
-  };
+  var testStyles = [
+    "mask",
+    "markerStart",
+    "markerMid",
+    "markerEnd",
+    "clipPath",
+    "filter",
+    "fill",
+    "stroke",
+  ];
 
-  for (var prop in testStyles) {
+  for (var prop of testStyles) {
     p.style[prop] = localURL;
-    is(cs[prop], localURL + testStyles[prop], "computed value of " + prop);
+    is(cs[prop], localURL, "computed value of " + prop);
     p.style[prop] = nonLocalURL;
-    is(cs[prop], resolvedNonLocalURL + testStyles[prop], "computed value of " + prop);
+    is(cs[prop], resolvedNonLocalURL, "computed value of " + prop);
   }
 
   p.remove();
 })();
 
 (function test_bug_1347164() {
   // Test that computed color values are serialized as "rgb()"
   // IFF they're fully-opaque (and otherwise as "rgba()").
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1449,20 +1449,28 @@ nsSVGUtils::PathExtentsToMaxStrokeExtent
 // ----------------------------------------------------------------------
 
 /* static */ nscolor
 nsSVGUtils::GetFallbackOrPaintColor(nsStyleContext *aStyleContext,
                                     nsStyleSVGPaint nsStyleSVG::*aFillOrStroke)
 {
   const nsStyleSVGPaint &paint = aStyleContext->StyleSVG()->*aFillOrStroke;
   nsStyleContext *styleIfVisited = aStyleContext->GetStyleIfVisited();
-  bool isServer = paint.Type() == eStyleSVGPaintType_Server ||
-                  paint.Type() == eStyleSVGPaintType_ContextFill ||
-                  paint.Type() == eStyleSVGPaintType_ContextStroke;
-  nscolor color = isServer ? paint.GetFallbackColor() : paint.GetColor();
+  nscolor color;
+  switch (paint.Type()) {
+    case eStyleSVGPaintType_Server:
+    case eStyleSVGPaintType_ContextFill:
+    case eStyleSVGPaintType_ContextStroke:
+      color = paint.GetFallbackType() == eStyleSVGFallbackType_Color ?
+                paint.GetFallbackColor() : NS_RGBA(0, 0, 0, 0);
+      break;
+    default:
+      color = paint.GetColor();
+      break;
+  }
   if (styleIfVisited) {
     const nsStyleSVGPaint &paintIfVisited =
       styleIfVisited->StyleSVG()->*aFillOrStroke;
     // To prevent Web content from detecting if a user has visited a URL
     // (via URL loading triggered by paint servers or performance
     // differences between paint servers or between a paint server and a
     // color), we do not allow whether links are visited to change which
     // paint server is used or switch between paint servers and simple
@@ -1538,16 +1546,20 @@ nsSVGUtils::MakeFillPatternFor(nsIFrame*
       ;
     }
     if (pattern) {
       aOutPattern->Init(*pattern->GetPattern(dt));
       return result;
     }
   }
 
+  if (style->mFill.GetFallbackType() == eStyleSVGFallbackType_None) {
+    return DrawResult::SUCCESS;
+  }
+
   // On failure, use the fallback colour in case we have an
   // objectBoundingBox where the width or height of the object is zero.
   // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
   Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(),
                                                       &nsStyleSVG::mFill)));
   color.a *= fillOpacity;
   aOutPattern->InitColorPattern(ToDeviceColor(color));
 
@@ -1614,16 +1626,20 @@ nsSVGUtils::MakeStrokePatternFor(nsIFram
       ;
     }
     if (pattern) {
       aOutPattern->Init(*pattern->GetPattern(dt));
       return result;
     }
   }
 
+  if (style->mStroke.GetFallbackType() == eStyleSVGFallbackType_None) {
+    return DrawResult::SUCCESS;
+  }
+
   // On failure, use the fallback colour in case we have an
   // objectBoundingBox where the width or height of the object is zero.
   // See http://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
   Color color(Color::FromABGR(GetFallbackOrPaintColor(aFrame->StyleContext(),
                                                       &nsStyleSVG::mStroke)));
   color.a *= strokeOpacity;
   aOutPattern->InitColorPattern(ToDeviceColor(color));
 
--- a/netwerk/base/SimpleBuffer.cpp
+++ b/netwerk/base/SimpleBuffer.cpp
@@ -9,22 +9,21 @@
 
 namespace mozilla {
 namespace net {
 
 SimpleBuffer::SimpleBuffer()
   : mStatus(NS_OK)
   , mAvailable(0)
 {
-  mOwningThread = PR_GetCurrentThread();
 }
 
 nsresult SimpleBuffer::Write(char *src, size_t len)
 {
-  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+  NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
   if (NS_FAILED(mStatus)) {
     return mStatus;
   }
 
   while (len > 0) {
     SimpleBufferPage *p = mBufferList.getLast();
     if (p && (p->mWriteOffset == SimpleBufferPage::kSimpleBufferPageSize)) {
       // no room.. make a new page
@@ -46,17 +45,17 @@ nsresult SimpleBuffer::Write(char *src, 
     p->mWriteOffset += toWrite;
     mAvailable += toWrite;
   }
   return NS_OK;
 }
 
 size_t SimpleBuffer::Read(char *dest, size_t maxLen)
 {
-  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+  NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
   if (NS_FAILED(mStatus)) {
     return 0;
   }
 
   size_t rv = 0;
   for (SimpleBufferPage *p = mBufferList.getFirst();
        p && (rv < maxLen); p = mBufferList.getFirst()) {
     size_t avail = p->mWriteOffset - p->mReadOffset;
@@ -72,23 +71,23 @@ size_t SimpleBuffer::Read(char *dest, si
 
   MOZ_ASSERT(mAvailable >= rv);
   mAvailable -= rv;
   return rv;
 }
 
 size_t SimpleBuffer::Available()
 {
-  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+  NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
   return NS_SUCCEEDED(mStatus) ? mAvailable : 0;
 }
 
 void SimpleBuffer::Clear()
 {
-  MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
+  NS_ASSERT_OWNINGTHREAD(SimpleBuffer);
   SimpleBufferPage *p;
   while ((p = mBufferList.popFirst())) {
     delete p;
   }
   mAvailable = 0;
 }
 
 } // namespace net
--- a/netwerk/base/SimpleBuffer.h
+++ b/netwerk/base/SimpleBuffer.h
@@ -40,17 +40,18 @@ public:
   ~SimpleBuffer() {}
 
   nsresult Write(char *stc, size_t len); // return OK or OUT_OF_MEMORY
   size_t Read(char *dest, size_t maxLen); // return bytes read
   size_t Available();
   void Clear();
 
 private:
-  PRThread *mOwningThread;
+  NS_DECL_OWNINGTHREAD
+
   nsresult mStatus;
   AutoCleanLinkedList<SimpleBufferPage> mBufferList;
   size_t mAvailable;
 };
 
 } // namespace net
 } // namespace mozilla
 
--- a/netwerk/base/nsBufferedStreams.cpp
+++ b/netwerk/base/nsBufferedStreams.cpp
@@ -274,20 +274,22 @@ nsBufferedStream::SetEOF()
 
 NS_IMPL_ADDREF_INHERITED(nsBufferedInputStream, nsBufferedStream)
 NS_IMPL_RELEASE_INHERITED(nsBufferedInputStream, nsBufferedStream)
 
 NS_IMPL_CLASSINFO(nsBufferedInputStream, nullptr, nsIClassInfo::THREADSAFE,
                   NS_BUFFEREDINPUTSTREAM_CID)
 
 NS_INTERFACE_MAP_BEGIN(nsBufferedInputStream)
-    NS_INTERFACE_MAP_ENTRY(nsIInputStream)
+    NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIBufferedInputStream)
     NS_INTERFACE_MAP_ENTRY(nsIBufferedInputStream)
     NS_INTERFACE_MAP_ENTRY(nsIStreamBufferAccess)
     NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, IsIPCSerializable())
+    NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, IsAsyncInputStream())
+    NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, IsAsyncInputStream())
     NS_IMPL_QUERY_CLASSINFO(nsBufferedInputStream)
 NS_INTERFACE_MAP_END_INHERITING(nsBufferedStream)
 
 NS_IMPL_CI_INTERFACE_GETTER(nsBufferedInputStream,
                             nsIInputStream,
                             nsIBufferedInputStream,
                             nsISeekableStream,
                             nsIStreamBufferAccess)
@@ -330,16 +332,18 @@ nsBufferedInputStream::Close()
     rv2 = nsBufferedStream::Close();
 
 #ifdef DEBUG
     if (NS_FAILED(rv2)) {
         NS_WARNING("(debug) Error: nsBufferedStream::Close() returned error (rv2) within nsBufferedInputStream::Close().");
     };
 #endif
 
+    mAsyncWaitCallback = nullptr;
+
     if (NS_FAILED(rv1)) {
         return rv1;
     }
     return rv2;
 }
 
 NS_IMETHODIMP
 nsBufferedInputStream::Available(uint64_t *result)
@@ -384,17 +388,18 @@ nsBufferedInputStream::ReadSegments(nsWr
         return NS_OK;
     }
 
     nsresult rv = NS_OK;
     while (count > 0) {
         uint32_t amt = std::min(count, mFillPoint - mCursor);
         if (amt > 0) {
             uint32_t read = 0;
-            rv = writer(this, closure, mBuffer + mCursor, *result, amt, &read);
+            rv = writer(static_cast<nsIBufferedInputStream*>(this), closure,
+                        mBuffer + mCursor, *result, amt, &read);
             if (NS_FAILED(rv)) {
                 // errors returned from the writer end here!
                 rv = NS_OK;
                 break;
             }
             *result += read;
             count -= read;
             mCursor += read;
@@ -618,16 +623,67 @@ nsBufferedInputStream::IsIPCSerializable
     if (!mStream) {
       return true;
     }
 
     nsCOMPtr<nsIIPCSerializableInputStream> stream = do_QueryInterface(mStream);
     return !!stream;
 }
 
+bool
+nsBufferedInputStream::IsAsyncInputStream() const
+{
+    nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+    return !!stream;
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::CloseWithStatus(nsresult aStatus)
+{
+    return Close();
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
+                                uint32_t aFlags,
+                                uint32_t aRequestedCount,
+                                nsIEventTarget* aEventTarget)
+{
+    nsCOMPtr<nsIAsyncInputStream> stream = do_QueryInterface(mStream);
+    if (!stream) {
+        return NS_ERROR_FAILURE;
+    }
+
+    if (mAsyncWaitCallback && aCallback) {
+        return NS_ERROR_FAILURE;
+    }
+
+    mAsyncWaitCallback = aCallback;
+
+    if (!mAsyncWaitCallback) {
+        return NS_OK;
+    }
+
+    return stream->AsyncWait(this, aFlags, aRequestedCount, aEventTarget);
+}
+
+NS_IMETHODIMP
+nsBufferedInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream)
+{
+    // We have been canceled in the meanwhile.
+    if (!mAsyncWaitCallback) {
+        return NS_OK;
+    }
+
+    nsCOMPtr<nsIInputStreamCallback> callback;
+    callback.swap(mAsyncWaitCallback);
+
+    return callback->OnInputStreamReady(this);
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsBufferedOutputStream
 
 NS_IMPL_ADDREF_INHERITED(nsBufferedOutputStream, nsBufferedStream)
 NS_IMPL_RELEASE_INHERITED(nsBufferedOutputStream, nsBufferedStream)
 // This QI uses NS_INTERFACE_MAP_ENTRY_CONDITIONAL to check for
 // non-nullness of mSafeStream.
 NS_INTERFACE_MAP_BEGIN(nsBufferedOutputStream)
--- a/netwerk/base/nsBufferedStreams.h
+++ b/netwerk/base/nsBufferedStreams.h
@@ -9,16 +9,17 @@
 #include "nsIBufferedStreams.h"
 #include "nsIInputStream.h"
 #include "nsIOutputStream.h"
 #include "nsISafeOutputStream.h"
 #include "nsISeekableStream.h"
 #include "nsIStreamBufferAccess.h"
 #include "nsCOMPtr.h"
 #include "nsIIPCSerializableInputStream.h"
+#include "nsIAsyncInputStream.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsBufferedStream : public nsISeekableStream
 {
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSISEEKABLESTREAM
@@ -56,41 +57,48 @@ protected:
     uint8_t                     mGetBufferCount;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsBufferedInputStream : public nsBufferedStream,
                               public nsIBufferedInputStream,
                               public nsIStreamBufferAccess,
-                              public nsIIPCSerializableInputStream
+                              public nsIIPCSerializableInputStream,
+                              public nsIAsyncInputStream,
+                              public nsIInputStreamCallback
 {
 public:
     NS_DECL_ISUPPORTS_INHERITED
     NS_DECL_NSIINPUTSTREAM
     NS_DECL_NSIBUFFEREDINPUTSTREAM
     NS_DECL_NSISTREAMBUFFERACCESS
     NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
+    NS_DECL_NSIASYNCINPUTSTREAM
+    NS_DECL_NSIINPUTSTREAMCALLBACK
 
     nsBufferedInputStream() : nsBufferedStream() {}
 
     static nsresult
     Create(nsISupports *aOuter, REFNSIID aIID, void **aResult);
 
     nsIInputStream* Source() { 
         return (nsIInputStream*)mStream;
     }
 
 protected:
     virtual ~nsBufferedInputStream() {}
 
     bool IsIPCSerializable() const;
+    bool IsAsyncInputStream() const;
 
     NS_IMETHOD Fill() override;
     NS_IMETHOD Flush() override { return NS_OK; } // no-op for input streams
+
+    nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback;
 };
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsBufferedOutputStream final : public nsBufferedStream,
                                      public nsISafeOutputStream,
                                      public nsIBufferedOutputStream,
                                      public nsIStreamBufferAccess
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -93,16 +93,22 @@ XPCOMUtils.defineLazyGetter(this, "IconD
   return ExtensionUtils.IconDetails;
 });
 
 Cu.importGlobalProperties(["URL"]);
 
 const nsIFile = Components.Constructor("@mozilla.org/file/local;1", "nsIFile",
                                        "initWithPath");
 
+function getFile(descriptor) {
+  let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+  file.persistentDescriptor = descriptor;
+  return file;
+}
+
 const PREF_DB_SCHEMA                  = "extensions.databaseSchema";
 const PREF_INSTALL_CACHE              = "extensions.installCache";
 const PREF_XPI_STATE                  = "extensions.xpiState";
 const PREF_BOOTSTRAP_ADDONS           = "extensions.bootstrappedAddons";
 const PREF_PENDING_OPERATIONS         = "extensions.pendingOperations";
 const PREF_SKIN_SWITCHPENDING         = "extensions.dss.switchPending";
 const PREF_SKIN_TO_SELECT             = "extensions.lastSelectedSkin";
 const PREF_GENERAL_SKINS_SELECTEDSKIN = "general.skins.selectedSkin";
@@ -2258,18 +2264,18 @@ XPIState.prototype = {
         XPIDatabase.saveChanges();
       }
     }
   },
 };
 
 // Constructor for an ES6 Map that knows how to convert itself into a
 // regular object for toJSON().
-function SerializableMap() {
-  let m = new Map();
+function SerializableMap(arg) {
+  let m = new Map(arg);
   m.toJSON = function() {
     let out = {}
     for (let [key, val] of m) {
       out[key] = val;
     }
     return out;
   };
   return m;
@@ -2445,17 +2451,20 @@ this.XPIStates = {
     XPIProvider.setTelemetry(aAddon.id, "location", aAddon.location);
   },
 
   /**
    * Save the current state of installed add-ons.
    * XXX this *totally* should be a .json file using DeferredSave...
    */
   save() {
-    let cache = JSON.stringify(this.db);
+    let db = new SerializableMap(this.db);
+    db.delete(TemporaryInstallLocation.name);
+
+    let cache = JSON.stringify(db);
     Services.prefs.setCharPref(PREF_XPI_STATE, cache);
   },
 
   /**
    * Remove the XPIState for an add-on and save the new state.
    * @param aLocation  The name of the add-on location.
    * @param aId        The ID of the add-on.
    */
@@ -2908,18 +2917,17 @@ this.XPIProvider = {
         this.addAddonsToCrashReporter();
       }
 
       try {
         AddonManagerPrivate.recordTimestamp("XPI_bootstrap_addons_begin");
 
         for (let addon of this.sortBootstrappedAddons()) {
           try {
-            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-            file.persistentDescriptor = addon.descriptor;
+            let file = getFile(addon.descriptor);
             let reason = BOOTSTRAP_REASONS.APP_STARTUP;
             // Eventually set INSTALLED reason when a bootstrap addon
             // is dropped in profile folder and automatically installed
             if (AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED)
                             .indexOf(addon.id) !== -1)
               reason = BOOTSTRAP_REASONS.ADDON_INSTALL;
             this.callBootstrapMethod(createAddonDetails(addon.id, addon),
                                      file, "startup", reason);
@@ -2941,18 +2949,17 @@ this.XPIProvider = {
           XPIProvider._closing = true;
           for (let addon of XPIProvider.sortBootstrappedAddons().reverse()) {
             // If no scope has been loaded for this add-on then there is no need
             // to shut it down (should only happen when a bootstrapped add-on is
             // pending enable)
             if (!XPIProvider.activeAddons.has(addon.id))
               continue;
 
-            let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-            file.persistentDescriptor = addon.descriptor;
+            let file = getFile(addon.descriptor);
             let addonDetails = createAddonDetails(addon.id, addon);
 
             // If the add-on was pending disable then shut it down and remove it
             // from the persisted data.
             if (addon.disable) {
               XPIProvider.callBootstrapMethod(addonDetails, file, "shutdown",
                                               BOOTSTRAP_REASONS.ADDON_DISABLE);
               delete XPIProvider.bootstrappedAddons[addon.id];
@@ -2997,16 +3004,43 @@ this.XPIProvider = {
    *                          0 otherwise.
    */
   shutdown() {
     logger.debug("shutdown");
 
     // Stop anything we were doing asynchronously
     this.cancelAll();
 
+    // Uninstall any temporary add-ons.
+    let tempLocation = XPIStates.getLocation(TemporaryInstallLocation.name);
+    if (tempLocation) {
+      for (let [id, addon] of tempLocation.entries()) {
+        tempLocation.delete(id);
+
+        let file = getFile(addon.descriptor);
+
+        this.callBootstrapMethod(createAddonDetails(id, this.bootstrappedAddons[id]),
+                                 file, "uninstall",
+                                 BOOTSTRAP_REASONS.ADDON_UNINSTALL);
+        this.unloadBootstrapScope(id);
+        TemporaryInstallLocation.uninstallAddon(id);
+
+        let [locationName, ] = XPIStates.findAddon(id);
+        if (locationName) {
+          let newAddon = XPIDatabase.makeAddonLocationVisible(id, locationName);
+
+          let file = getFile(newAddon.descriptor);
+
+          this.callBootstrapMethod(createAddonDetails(id, newAddon),
+                                   file, "install",
+                                   BOOTSTRAP_REASONS.ADDON_INSTALL);
+        }
+      }
+    }
+
     this.bootstrappedAddons = {};
     this.activeAddons.clear();
     this.enabledAddons = null;
     this.allAppGlobal = true;
 
     // If there are pending operations then we must update the list of active
     // add-ons
     if (Preferences.get(PREF_PENDING_OPERATIONS, false)) {
@@ -3511,18 +3545,17 @@ this.XPIProvider = {
         seenFiles.push(jsonfile.leafName);
 
         existingAddonID = addon.existingAddonID || id;
 
         var oldBootstrap = null;
         logger.debug("Processing install of " + id + " in " + location.name);
         if (existingAddonID in this.bootstrappedAddons) {
           try {
-            var existingAddon = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-            existingAddon.persistentDescriptor = this.bootstrappedAddons[existingAddonID].descriptor;
+            var existingAddon = getFile(this.bootstrappedAddons[existingAddonID].descriptor);
             if (existingAddon.exists()) {
               oldBootstrap = this.bootstrappedAddons[existingAddonID];
 
               // We'll be replacing a currently active bootstrapped add-on so
               // call its uninstall method
               let newVersion = addon.version;
               let oldVersion = oldBootstrap.version;
               let uninstallReason = Services.vc.compare(oldVersion, newVersion) < 0 ?
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -62,16 +62,17 @@ const PREF_EM_AUTO_DISABLED_SCOPES    = 
 const PREF_E10S_BLOCKED_BY_ADDONS     = "extensions.e10sBlockedByAddons";
 const PREF_E10S_MULTI_BLOCKED_BY_ADDONS = "extensions.e10sMultiBlockedByAddons";
 const PREF_E10S_HAS_NONEXEMPT_ADDON   = "extensions.e10s.rollout.hasAddon";
 
 const KEY_APP_PROFILE                 = "app-profile";
 const KEY_APP_SYSTEM_ADDONS           = "app-system-addons";
 const KEY_APP_SYSTEM_DEFAULTS         = "app-system-defaults";
 const KEY_APP_GLOBAL                  = "app-global";
+const KEY_APP_TEMPORARY               = "app-temporary";
 
 // Properties that only exist in the database
 const DB_METADATA        = ["syncGUID",
                             "installDate",
                             "updateDate",
                             "size",
                             "sourceURI",
                             "releaseNotesURI",
@@ -486,17 +487,18 @@ this.XPIDatabase = {
   toJSON() {
     if (!this.addonDB) {
       // We never loaded the database?
       throw new Error("Attempt to save database without loading it first");
     }
 
     let toSave = {
       schemaVersion: DB_SCHEMA,
-      addons: [...this.addonDB.values()]
+      addons: Array.from(this.addonDB.values())
+                   .filter(addon => addon.location != KEY_APP_TEMPORARY),
     };
     return toSave;
   },
 
   /**
    * Pull upgrade information from an existing SQLITE database
    *
    * @return false if there is no SQLITE database
@@ -1306,16 +1308,45 @@ this.XPIDatabase = {
         otherAddon.active = false;
       }
     }
     aAddon.visible = true;
     this.saveChanges();
   },
 
   /**
+   * Synchronously marks a given add-on ID visible in a given location,
+   * instances with the same ID as not visible.
+   *
+   * @param  aAddon
+   *         The DBAddonInternal to make visible
+   */
+  makeAddonLocationVisible(aId, aLocation) {
+    logger.debug(`Make addon ${aId} visible in location ${aLocation}`);
+    let result;
+    for (let [, addon] of this.addonDB) {
+      if (addon.id != aId) {
+        continue;
+      }
+      if (addon.location == aLocation) {
+        logger.debug("Reveal addon " + addon._key);
+        addon.visible = true;
+        addon.active = true;
+        result = addon;
+      } else {
+        logger.debug("Hide addon " + addon._key);
+        addon.visible = false;
+        addon.active = false;
+      }
+    }
+    this.saveChanges();
+    return result;
+  },
+
+  /**
    * Synchronously sets properties for an add-on.
    *
    * @param  aAddon
    *         The DBAddonInternal being updated
    * @param  aProperties
    *         A dictionary of properties to set
    */
   setAddonProperties(aAddon, aProperties) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -43,17 +43,17 @@ const BOOTSTRAP_REASONS = {
 function waitForBootstrapEvent(expectedEvent, addonId) {
   return new Promise(resolve => {
     const observer = {
       observe: (subject, topic, data) => {
         const info = JSON.parse(data);
         const targetAddonId = info.data.id;
         if (targetAddonId === addonId && info.event === expectedEvent) {
           resolve(info);
-          Services.obs.removeObserver(observer);
+          Services.obs.removeObserver(observer, "bootstrapmonitor-event");
         } else {
           do_print(
             `Ignoring bootstrap event: ${info.event} for ${targetAddonId}`);
         }
       },
     };
     Services.obs.addObserver(observer, "bootstrapmonitor-event");
   });