Merge mozilla-central to mozilla-inbound.
authorCosmin Sabou <csabou@mozilla.com>
Fri, 19 Apr 2019 19:36:21 +0300
changeset 470282 590157f23b85a1cfcd6d3e910fbf4baefec5f3c6
parent 470281 c06f27cbfe40d9f05272813d63680b8e73b1e370 (current diff)
parent 470190 0b1de782bd32ee277ad609c2bbe7e35b8a003ee7 (diff)
child 470283 8c09cf1ee1e5321cb042554c088bef563b328d12
push id35892
push userrgurzau@mozilla.com
push dateSat, 20 Apr 2019 09:55:32 +0000
treeherdermozilla-central@a092972b53f0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone68.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 mozilla-central to mozilla-inbound.
devtools/client/locales/en-US/netmonitor.properties
toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -1289,16 +1289,18 @@ class UrlbarInput {
         this.startQuery({ allowEmptyInput: false });
       }
     }
   }
 
   _on_input() {
     let value = this.textValue;
     this.valueIsTyped = true;
+    let valueIsPasted = this._valueIsPasted;
+    this._valueIsPasted = false;
     this._untrimmedValue = value;
     this.window.gBrowser.userTypedValue = value;
 
     let deletedEndOfAutofillPlaceholder = this._deletedEndOfAutofillPlaceholder;
     this._deletedEndOfAutofillPlaceholder = false;
 
     let compositionState = this._compositionState;
     let compositionClosedPopup = this._compositionClosedPopup;
@@ -1346,17 +1348,17 @@ class UrlbarInput {
     // because we canceled the previous search on composition start.
     if (sameSearchStrings &&
         !deletedAutofilledSubstring &&
         compositionState == UrlbarUtils.COMPOSITION.NONE &&
         value.length > 0) {
       return;
     }
 
-    let allowAutofill =
+    let allowAutofill = !valueIsPasted &&
       this._maybeAutofillOnInput(value, deletedAutofilledSubstring);
 
     this.startQuery({
       searchString: value,
       allowAutofill,
       resetSearchState: false,
     });
   }
@@ -1412,17 +1414,17 @@ class UrlbarInput {
     this._updateUrlTooltip();
   }
 
   _on_paste(event) {
     let originalPasteData = event.clipboardData.getData("text/plain");
     if (!originalPasteData) {
       return;
     }
-
+    this._valueIsPasted = true;
     let oldValue = this.inputField.value;
     let oldStart = oldValue.substring(0, this.selectionStart);
     // If there is already non-whitespace content in the URL bar
     // preceding the pasted content, it's not necessary to check
     // protocols used by the pasted content:
     if (oldStart.trim()) {
       return;
     }
--- a/browser/components/urlbar/tests/browser/browser.ini
+++ b/browser/components/urlbar/tests/browser/browser.ini
@@ -22,16 +22,17 @@ skip-if = true # Bug 1524539 - need to f
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_readline_navigation.js]
 skip-if = os != "mac" # Mac only feature
 [browser_autocomplete_tag_star_visibility.js]
 [browser_autoFill_backspaced.js]
 [browser_autoFill_canonize.js]
 [browser_autoFill_caretNotAtEnd.js]
 [browser_autoFill_firstResult.js]
+[browser_autoFill_paste.js]
 [browser_autoFill_placeholder.js]
 [browser_autoFill_preserve.js]
 [browser_autoFill_trimURLs.js]
 [browser_autoFill_typed.js]
 [browser_autoFill_undo.js]
 [browser_canonizeURL.js]
 support-files =
   searchSuggestionEngine.xml
new file mode 100644
--- /dev/null
+++ b/browser/components/urlbar/tests/browser/browser_autoFill_paste.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// This test checks we don't autofill on paste.
+
+"use strict";
+
+async function paste(str) {
+  await SimpleTest.promiseClipboardChange(str, () => {
+    Cc["@mozilla.org/widget/clipboardhelper;1"]
+      .getService(Ci.nsIClipboardHelper)
+      .copyString(str);
+  });
+  gURLBar.select();
+  document.commandDispatcher.getControllerForCommand("cmd_paste")
+                            .doCommand("cmd_paste");
+}
+
+add_task(async function test() {
+  await PlacesUtils.bookmarks.eraseEverything();
+  await PlacesUtils.history.clear();
+  await PlacesTestUtils.addVisits([
+    "http://example.com/",
+  ]);
+  registerCleanupFunction(async () => {
+    await PlacesUtils.history.clear();
+  });
+
+  // Search for "e".  It should autofill to example.com/.
+  await UrlbarTestUtils.promiseAutocompleteResultPopup({
+    window,
+    waitForFocus,
+    value: "e",
+    fireInputEvent: true,
+  });
+  let details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.ok(details.autofill);
+  Assert.equal(gURLBar.value, "example.com/");
+  Assert.equal(gURLBar.selectionStart, "e".length);
+  Assert.equal(gURLBar.selectionEnd, "example.com/".length);
+
+  // Now paste.
+  await paste("ex");
+
+  // Nothing should have been autofilled.
+  await UrlbarTestUtils.promiseSearchComplete(window);
+  details = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
+  Assert.ok(!details.autofill);
+  Assert.equal(gURLBar.value, "ex");
+  Assert.equal(gURLBar.selectionStart, "ex".length);
+  Assert.equal(gURLBar.selectionEnd, "ex".length);
+});
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -52,21 +52,22 @@
 /*
   This is a workaround for Bug 1482157
   -moz-appearance: toolbox; makes the macOS sheets attached to the element's
   bottom border. We cannot put this property on the toolbox itself as it
   cancels all backgrounds that are there, so we set it on the toolbox bottom
   border.
 */
 #navigator-toolbox::after {
+  content: "";
+  display: -moz-box;
   -moz-appearance: toolbox;
   height: 1px;
-  /* use inset box-shadow instead of border because -moz-appearance hides the border */
-  border: none;
-  box-shadow: inset 0 -1px var(--chrome-content-separator-color);
+  margin-top: -1px;
+  opacity: 0;
 }
 
 #tabbrowser-tabs {
   --tab-line-color: #0a84ff;
 }
 
 #navigator-toolbox toolbarbutton:-moz-lwtheme {
   color: inherit;
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -34,23 +34,21 @@
 /* Increase contrast of UI links on dark themes */
 
 :root[lwt-popup-brighttext] panel .text-link {
   color: @lwtPopupBrighttextLinkColor@;
 }
 
 /* Toolbar / content area border */
 
-#navigator-toolbox::after {
-  content: "";
-  display: -moz-box;
+#navigator-toolbox {
   border-bottom: 1px solid var(--chrome-content-separator-color);
 }
 
-:root[customizing] #navigator-toolbox::after {
+:root[customizing] #navigator-toolbox {
   border-bottom-style: none;
 }
 
 :root[sessionrestored] #nav-bar:-moz-lwtheme {
   transition: @themeTransition@;
 }
 
 /* Bookmark toolbar */
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -303,17 +303,28 @@
     /* Vertical toolbar border */
     #main-window[sizemode=normal] .browser-toolbar:not(.titlebar-color):not(:-moz-lwtheme),
     #main-window[sizemode=normal] .browser-toolbar:-moz-lwtheme {
       border-left: 1px solid @glassShadowColor@;
       border-right: 1px solid @glassShadowColor@;
       background-clip: padding-box;
     }
 
-    #main-window[sizemode=normal] #navigator-toolbox::after {
+    #navigator-toolbox::after {
+      content: "";
+      display: -moz-box;
+      border-bottom: 1px solid var(--chrome-content-separator-color);
+    }
+
+    #navigator-toolbox,
+    :root[customizing] #navigator-toolbox::after {
+      border-bottom-style: none;
+    }
+
+    :root[sizemode=normal] #navigator-toolbox::after {
       box-shadow: 1px 0 0 @glassShadowColor@, -1px 0 0 @glassShadowColor@;
       margin-left: 1px;
       margin-right: 1px;
     }
 
     #main-window[sizemode=normal] #browser-border-start,
     #main-window[sizemode=normal] #browser-border-end {
       display: -moz-box;
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -954,16 +954,24 @@ netmonitor.context.copyRequestData.acces
 # The capitalization is part of the official name and should be used throughout all languages.
 # http://en.wikipedia.org/wiki/CURL
 netmonitor.context.copyAsCurl=Copy as cURL
 
 # LOCALIZATION NOTE (netmonitor.context.copyAsCurl.accesskey): This is the access key
 # for the Copy as cURL menu item displayed in the context menu for a request
 netmonitor.context.copyAsCurl.accesskey=C
 
+# LOCALIZATION NOTE (netmonitor.context.copyAsFetch): This is the label displayed
+# on the context menu that copies the selected request as a fetch request.
+netmonitor.context.copyAsFetch=Copy as Fetch
+
+# LOCALIZATION NOTE (netmonitor.context.copyAsFetch.accesskey): This is the access key
+# for the Copy as fetch menu item displayed in the context menu for a request
+netmonitor.context.copyAsFetch.accesskey=F
+
 # LOCALIZATION NOTE (netmonitor.context.copyRequestHeaders): This is the label displayed
 # on the context menu that copies the selected item's request headers
 netmonitor.context.copyRequestHeaders=Copy Request Headers
 
 # LOCALIZATION NOTE (netmonitor.context.copyRequestHeaders.accesskey): This is the access key
 # for the Copy Request Headers menu item displayed in the context menu for a request
 netmonitor.context.copyRequestHeaders.accesskey=q
 
@@ -986,16 +994,24 @@ netmonitor.context.copyResponse.accesske
 # LOCALIZATION NOTE (netmonitor.context.copyImageAsDataUri): This is the label displayed
 # on the context menu that copies the selected image as data uri
 netmonitor.context.copyImageAsDataUri=Copy Image as Data URI
 
 # LOCALIZATION NOTE (netmonitor.context.copyImageAsDataUri.accesskey): This is the access key
 # for the Copy Image As Data URI menu item displayed in the context menu for a request
 netmonitor.context.copyImageAsDataUri.accesskey=I
 
+# LOCALIZATION NOTE (netmonitor.context.useAsFetch): This is the label displayed
+# on the context menu that copies the selected request as a fetch command.
+netmonitor.context.useAsFetch=Use as Fetch in Console
+
+# LOCALIZATION NOTE (netmonitor.context.useAsFetch.accesskey): This is the access key
+# for the Copy as fetch menu item displayed in the context menu for a request
+netmonitor.context.useAsFetch.accesskey=F
+
 # LOCALIZATION NOTE (netmonitor.context.saveImageAs): This is the label displayed
 # on the context menu that save the Image
 netmonitor.context.saveImageAs=Save Image As
 
 # LOCALIZATION NOTE (netmonitor.context.saveImageAs.accesskey): This is the access key
 # for the Copy Image As Data URI menu item displayed in the context menu for a request
 netmonitor.context.saveImageAs.accesskey=v
 
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -86,16 +86,25 @@ class RequestListContextMenu {
       // Menu item will be visible even if data hasn't arrived, so we need to check
       // *Available property and then fetch data lazily once user triggers the action.
       visible: !!selectedRequest,
       click: () =>
         this.copyAsCurl(id, url, method, httpVersion, requestHeaders, requestPostData),
     });
 
     copySubmenu.push({
+      id: "request-list-context-copy-as-fetch",
+      label: L10N.getStr("netmonitor.context.copyAsFetch"),
+      accesskey: L10N.getStr("netmonitor.context.copyAsFetch.accesskey"),
+      visible: !!selectedRequest,
+      click: () =>
+        this.copyAsFetch(id, url, method, requestHeaders, requestPostData),
+    });
+
+    copySubmenu.push({
       type: "separator",
       visible: copySubmenu.slice(0, 4).some((subMenu) => subMenu.visible),
     });
 
     copySubmenu.push({
       id: "request-list-context-copy-request-headers",
       label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
       accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
@@ -226,16 +235,29 @@ class RequestListContextMenu {
     menu.push({
       id: "request-list-context-perf",
       label: L10N.getStr("netmonitor.context.perfTools"),
       accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
       visible: requests.size > 0,
       click: () => openStatistics(true),
     });
 
+    menu.push({
+      type: "separator",
+    });
+
+    menu.push({
+      id: "request-list-context-use-as-fetch",
+      label: L10N.getStr("netmonitor.context.useAsFetch"),
+      accesskey: L10N.getStr("netmonitor.context.useAsFetch.accesskey"),
+      visible: !!selectedRequest,
+      click: () =>
+        this.useAsFetch(id, url, method, requestHeaders, requestPostData),
+    });
+
     showMenu(menu, {
       screenX: event.screenX,
       screenY: event.screenY,
     });
   }
 
   /**
    * Opens selected item in the debugger
@@ -320,16 +342,117 @@ class RequestListContextMenu {
       headers: requestHeaders.headers,
       httpVersion,
       postDataText: requestPostData ? requestPostData.postData.text : "",
     };
     copyString(Curl.generateCommand(data));
   }
 
   /**
+   * Generate fetch string
+   */
+  async generateFetchString(id, url, method, requestHeaders, requestPostData) {
+    requestHeaders = requestHeaders ||
+      await this.props.connector.requestData(id, "requestHeaders");
+
+    requestPostData = requestPostData ||
+      await this.props.connector.requestData(id, "requestPostData");
+
+    // https://fetch.spec.whatwg.org/#forbidden-header-name
+    const forbiddenHeaders = {
+      "accept-charset": 1,
+      "accept-encoding": 1,
+      "access-control-request-headers": 1,
+      "access-control-request-method": 1,
+      "connection": 1,
+      "content-length": 1,
+      "cookie": 1,
+      "cookie2": 1,
+      "date": 1,
+      "dnt": 1,
+      "expect": 1,
+      "host": 1,
+      "keep-alive": 1,
+      "origin": 1,
+      "referer": 1,
+      "te": 1,
+      "trailer": 1,
+      "transfer-encoding": 1,
+      "upgrade": 1,
+      "via": 1,
+    };
+    const credentialHeaders = {"cookie": 1, "authorization": 1};
+
+    const headers = {};
+    for (const {name, value} of requestHeaders.headers) {
+      if (!forbiddenHeaders[name.toLowerCase()]) {
+        headers[name] = value;
+      }
+    }
+
+    const referrerHeader = requestHeaders.headers.find(
+      ({ name }) => name.toLowerCase() === "referer"
+    );
+
+    const referrerPolicy = requestHeaders.headers.find(
+      ({ name }) => name.toLowerCase() === "referrer-policy"
+    );
+
+    const referrer = referrerHeader ? referrerHeader.value : undefined;
+    const credentials = requestHeaders.headers.some(
+      ({name}) => credentialHeaders[name.toLowerCase()]
+    ) ? "include" : "omit";
+
+    const fetchOptions = {
+      credentials,
+      headers,
+      referrer,
+      referrerPolicy,
+      body: requestPostData.postData.text,
+      method: method,
+      mode: "cors",
+    };
+
+    const options = JSON.stringify(fetchOptions, null, 4);
+    const fetchString = `await fetch("${url}", ${options});`;
+    return fetchString;
+  }
+
+  /**
+   * Copy the currently selected item as fetch request.
+   */
+  async copyAsFetch(id, url, method, requestHeaders, requestPostData) {
+    const fetchString = await this.generateFetchString(
+      id,
+      url,
+      method,
+      requestHeaders,
+      requestPostData
+    );
+    copyString(fetchString);
+  }
+
+  /**
+   * Open split console and fill it with fetch command for selected item
+   */
+  async useAsFetch(id, url, method, requestHeaders, requestPostData) {
+    const fetchString = await this.generateFetchString(
+      id,
+      url,
+      method,
+      requestHeaders,
+      requestPostData
+    );
+    const toolbox = gDevTools.getToolbox(this.props.connector.getTabTarget());
+    await toolbox.openSplitConsole();
+    const { hud } = await toolbox.getPanel("webconsole");
+    hud.setInputValue(fetchString);
+  }
+
+  /**
    * Copy the raw request headers from the currently selected item.
    */
   async copyRequestHeaders(id, requestHeaders) {
     requestHeaders = requestHeaders ||
       await this.props.connector.requestData(id, "requestHeaders");
 
     let rawHeaders = requestHeaders.rawHeaders.trim();
 
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -111,16 +111,19 @@ skip-if = (verify && !debug && (os == 'm
 [browser_net_copy_response.js]
 subsuite = clipboard
 [browser_net_copy_headers.js]
 subsuite = clipboard
 [browser_net_cookies_sorted.js]
 skip-if = (verify && debug && os == 'win')
 [browser_net_copy_as_curl.js]
 subsuite = clipboard
+[browser_net_copy_as_fetch.js]
+subsuite = clipboard
+[browser_net_use_as_fetch.js]
 [browser_net_cors_requests.js]
 [browser_net_cyrillic-01.js]
 [browser_net_cyrillic-02.js]
 [browser_net_frame.js]
 skip-if = (os == 'mac') || (os == 'win' && os_version == '10.0') # Bug 1479782
 [browser_net_header-docs.js]
 [browser_net_edit_resend_cancel.js]
 [browser_net_edit_resend_caret.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_fetch.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Copy as Fetch works.
+ */
+
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(CURL_URL);
+  info("Starting test... ");
+
+  // GET request, no cookies (first request)
+  await performRequest("GET");
+  await testClipboardContent(`await fetch("http://example.com/browser/devtools/client/netmonitor/test/sjs_simple-test-server.sjs", {
+    "credentials": "omit",
+    "headers": {
+        "User-Agent": "${navigator.userAgent}",
+        "Accept": "*/*",
+        "Accept-Language": "en-US",
+        "X-Custom-Header-1": "Custom value",
+        "X-Custom-Header-2": "8.8.8.8",
+        "X-Custom-Header-3": "Mon, 3 Mar 2014 11:11:11 GMT",
+        "Pragma": "no-cache",
+        "Cache-Control": "no-cache"
+    },
+    "referrer": "http://example.com/browser/devtools/client/netmonitor/test/html_copy-as-curl.html",
+    "method": "GET",
+    "mode": "cors"
+});`);
+
+  await teardown(monitor);
+
+  async function performRequest(method, payload) {
+    const waitRequest = waitForNetworkEvents(monitor, 1);
+    await ContentTask.spawn(tab.linkedBrowser, {
+      url: SIMPLE_SJS,
+      method_: method,
+      payload_: payload,
+    }, async function({url, method_, payload_}) {
+      content.wrappedJSObject.performRequest(url, method_, payload_);
+    });
+    await waitRequest;
+  }
+
+  async function testClipboardContent(expectedResult) {
+    const { document } = monitor.panelWin;
+
+    const items = document.querySelectorAll(".request-list-item");
+    EventUtils.sendMouseEvent({ type: "mousedown" }, items[items.length - 1]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[0]);
+
+    /* Ensure that the copy as fetch option is always visible */
+    const copyAsFetchNode = monitor.panelWin.parent.document
+      .querySelector("#request-list-context-copy-as-fetch");
+    is(!!copyAsFetchNode, true,
+      "The \"Copy as Fetch\" context menu item should not be hidden.");
+
+    await waitForClipboardPromise(function setup() {
+      copyAsFetchNode.click();
+    }, function validate(result) {
+      if (typeof result !== "string") {
+        return false;
+      }
+
+      return expectedResult === result;
+    });
+
+    info("Clipboard contains a fetch command for item " + (items.length - 1));
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_use_as_fetch.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if Use as Fetch works.
+ */
+
+add_task(async function() {
+  const { tab, monitor, toolbox } = await initNetMonitor(CURL_URL);
+  info("Starting test... ");
+
+  // GET request, no cookies (first request)
+  await performRequest("GET");
+  await testConsoleInput(`await fetch("http://example.com/browser/devtools/client/netmonitor/test/sjs_simple-test-server.sjs", {
+    "credentials": "omit",
+    "headers": {
+        "User-Agent": "${navigator.userAgent}",
+        "Accept": "*/*",
+        "Accept-Language": "en-US",
+        "X-Custom-Header-1": "Custom value",
+        "X-Custom-Header-2": "8.8.8.8",
+        "X-Custom-Header-3": "Mon, 3 Mar 2014 11:11:11 GMT",
+        "Pragma": "no-cache",
+        "Cache-Control": "no-cache"
+    },
+    "referrer": "http://example.com/browser/devtools/client/netmonitor/test/html_copy-as-curl.html",
+    "method": "GET",
+    "mode": "cors"
+});`);
+
+  await teardown(monitor);
+
+  async function performRequest(method, payload) {
+    const waitRequest = waitForNetworkEvents(monitor, 1);
+    await ContentTask.spawn(tab.linkedBrowser, {
+      url: SIMPLE_SJS,
+      method_: method,
+      payload_: payload,
+    }, async function({url, method_, payload_}) {
+      content.wrappedJSObject.performRequest(url, method_, payload_);
+    });
+    await waitRequest;
+  }
+
+  async function testConsoleInput(expectedResult) {
+    const { document } = monitor.panelWin;
+
+    const items = document.querySelectorAll(".request-list-item");
+    EventUtils.sendMouseEvent({ type: "mousedown" }, items[items.length - 1]);
+    EventUtils.sendMouseEvent({ type: "contextmenu" },
+      document.querySelectorAll(".request-list-item")[0]);
+
+    /* Ensure that the use as fetch option is always visible */
+    const useAsFetchNode = monitor.panelWin.parent.document
+      .querySelector("#request-list-context-use-as-fetch");
+    is(!!useAsFetchNode, true,
+      "The \"Use as Fetch\" context menu item should not be hidden.");
+
+    useAsFetchNode.click();
+    await toolbox.once("split-console");
+    const hud = toolbox.getPanel("webconsole").hud;
+    await hud.jsterm.once("set-input-value");
+
+    is(hud.getInputValue(), expectedResult,
+      "Console input contains fetch request for item " + (items.length - 1));
+  }
+});
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -414,18 +414,19 @@ TimeDuration OfflineClockDriver::WaitInt
 }
 
 AsyncCubebTask::AsyncCubebTask(AudioCallbackDriver* aDriver,
                                AsyncCubebOperation aOperation)
     : Runnable("AsyncCubebTask"),
       mDriver(aDriver),
       mOperation(aOperation),
       mShutdownGrip(aDriver->GraphImpl()) {
-  NS_WARNING_ASSERTION(mDriver->mAudioStream || aOperation == INIT,
-                       "No audio stream!");
+  NS_WARNING_ASSERTION(
+      mDriver->mAudioStream || aOperation == AsyncCubebOperation::INIT,
+      "No audio stream!");
 }
 
 AsyncCubebTask::~AsyncCubebTask() {}
 
 NS_IMETHODIMP
 AsyncCubebTask::Run() {
   MOZ_ASSERT(mDriver);
 
@@ -436,16 +437,32 @@ AsyncCubebTask::Run() {
       if (!mDriver->Init()) {
         LOG(LogLevel::Warning,
             ("AsyncCubebOperation::INIT failed for driver=%p", mDriver.get()));
         return NS_ERROR_FAILURE;
       }
       mDriver->CompleteAudioContextOperations(mOperation);
       break;
     }
+    case AsyncCubebOperation::START: {
+      LOG(LogLevel::Debug, ("%p: AsyncCubebOperation::START driver=%p",
+                            mDriver->GraphImpl(), mDriver.get()));
+      if (!mDriver->StartStream()) {
+        LOG(LogLevel::Warning,
+            ("%p: AsyncCubebOperation couldn't start the driver=%p.",
+             mDriver->GraphImpl(), mDriver.get()));
+      }
+      break;
+    }
+    case AsyncCubebOperation::STOP: {
+      LOG(LogLevel::Debug, ("%p: AsyncCubebOperation::STOP driver=%p",
+                            mDriver->GraphImpl(), mDriver.get()));
+      mDriver->Stop();
+      break;
+    }
     case AsyncCubebOperation::SHUTDOWN: {
       LOG(LogLevel::Debug, ("%p: AsyncCubebOperation::SHUTDOWN driver=%p",
                             mDriver->GraphImpl(), mDriver.get()));
       mDriver->Stop();
 
       mDriver->CompleteAudioContextOperations(mOperation);
 
       mDriver = nullptr;
@@ -708,34 +725,42 @@ bool AudioCallbackDriver::StartStream() 
   return true;
 }
 
 void AudioCallbackDriver::Stop() {
   MOZ_ASSERT(OnCubebOperationThread());
   if (cubeb_stream_stop(mAudioStream) != CUBEB_OK) {
     NS_WARNING("Could not stop cubeb stream for MSG.");
   }
+  mStarted = false;
 }
 
 void AudioCallbackDriver::Revive() {
   MOZ_ASSERT(NS_IsMainThread() && !ThreadRunning());
   // Note: only called on MainThread, without monitor
   // We know were weren't in a running state
   LOG(LogLevel::Debug, ("%p: AudioCallbackDriver reviving.", GraphImpl()));
   // If we were switching, switch now. Otherwise, start the audio thread again.
   MonitorAutoLock mon(GraphImpl()->GetMonitor());
   if (NextDriver()) {
     SwitchToNextDriver();
   } else {
     LOG(LogLevel::Debug,
         ("Starting audio threads for MediaStreamGraph %p from a new thread.",
          mGraphImpl.get()));
-    RefPtr<AsyncCubebTask> initEvent =
-        new AsyncCubebTask(this, AsyncCubebOperation::INIT);
-    initEvent->Dispatch();
+    if (IsStarted()) {
+      RefPtr<AsyncCubebTask> stopEvent =
+          new AsyncCubebTask(this, AsyncCubebOperation::STOP);
+      // This dispatches to a thread pool with a maximum of one thread thus it
+      // is guaranteed to be executed before the start event, right below.
+      stopEvent->Dispatch();
+    }
+    RefPtr<AsyncCubebTask> startEvent =
+        new AsyncCubebTask(this, AsyncCubebOperation::START);
+    startEvent->Dispatch();
   }
 }
 
 void AudioCallbackDriver::RemoveMixerCallback() {
   MOZ_ASSERT(OnGraphThread() || !ThreadRunning());
 
   if (mAddedMixer) {
     GraphImpl()->mMixer.RemoveCallback(this);
--- a/dom/media/GraphDriver.h
+++ b/dom/media/GraphDriver.h
@@ -317,17 +317,17 @@ struct StreamAndPromiseForOperation {
                                dom::AudioContextOperation aOperation,
                                dom::AudioContextOperationFlags aFlags);
   RefPtr<MediaStream> mStream;
   void* mPromise;
   dom::AudioContextOperation mOperation;
   dom::AudioContextOperationFlags mFlags;
 };
 
-enum AsyncCubebOperation { INIT, SHUTDOWN };
+enum class AsyncCubebOperation { INIT, START, STOP, SHUTDOWN  };
 enum class AudioInputType { Unknown, Voice };
 
 /**
  * This is a graph driver that is based on callback functions called by the
  * audio api. This ensures minimal audio latency, because it means there is no
  * buffering happening: the audio is generated inside the callback.
  *
  * This design is less flexible than running our own thread:
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -756,24 +756,17 @@ void MediaStreamGraphImpl::OpenAudioInpu
       LOG(LogLevel::Error, ("OpenAudioInput in shutdown!"));
       MOZ_ASSERT_UNREACHABLE("Can't open cubeb inputs in shutdown");
     }
   }
 }
 
 nsresult MediaStreamGraphImpl::OpenAudioInput(CubebUtils::AudioDeviceID aID,
                                               AudioDataListener* aListener) {
-  // So, so, so annoying.  Can't AppendMessage except on Mainthread
-  if (!NS_IsMainThread()) {
-    RefPtr<nsIRunnable> runnable =
-        WrapRunnable(this, &MediaStreamGraphImpl::OpenAudioInput, aID,
-                     RefPtr<AudioDataListener>(aListener));
-    mAbstractMainThread->Dispatch(runnable.forget());
-    return NS_OK;
-  }
+  MOZ_ASSERT(NS_IsMainThread());
   class Message : public ControlMessage {
    public:
     Message(MediaStreamGraphImpl* aGraph, CubebUtils::AudioDeviceID aID,
             AudioDataListener* aListener)
         : ControlMessage(nullptr),
           mGraph(aGraph),
           mID(aID),
           mListener(aListener) {}
@@ -840,24 +833,17 @@ void MediaStreamGraphImpl::CloseAudioInp
       driver = new SystemClockDriver(this);
       CurrentDriver()->SwitchAtNextIteration(driver);
     }  // else SystemClockDriver->SystemClockDriver, no switch
   }
 }
 
 void MediaStreamGraphImpl::CloseAudioInput(
     Maybe<CubebUtils::AudioDeviceID>& aID, AudioDataListener* aListener) {
-  // So, so, so annoying.  Can't AppendMessage except on Mainthread
-  if (!NS_IsMainThread()) {
-    RefPtr<nsIRunnable> runnable =
-        WrapRunnable(this, &MediaStreamGraphImpl::CloseAudioInput, aID,
-                     RefPtr<AudioDataListener>(aListener));
-    mAbstractMainThread->Dispatch(runnable.forget());
-    return;
-  }
+  MOZ_ASSERT(NS_IsMainThread());
   class Message : public ControlMessage {
    public:
     Message(MediaStreamGraphImpl* aGraph, Maybe<CubebUtils::AudioDeviceID>& aID,
             AudioDataListener* aListener)
         : ControlMessage(nullptr),
           mGraph(aGraph),
           mID(aID),
           mListener(aListener) {}
@@ -2416,35 +2402,42 @@ void MediaStream::AddMainThreadListener(
 
 SourceMediaStream::SourceMediaStream()
     : MediaStream(),
       mMutex("mozilla::media::SourceMediaStream"),
       mFinishPending(false) {}
 
 nsresult SourceMediaStream::OpenAudioInput(CubebUtils::AudioDeviceID aID,
                                            AudioDataListener* aListener) {
+  MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(GraphImpl());
+  MOZ_ASSERT(!mInputListener);
   mInputListener = aListener;
   return GraphImpl()->OpenAudioInput(aID, aListener);
 }
 
-void SourceMediaStream::CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID,
-                                        AudioDataListener* aListener) {
-  MOZ_ASSERT(mInputListener == aListener);
-  // Destroy() may have run already and cleared this
-  if (GraphImpl() && mInputListener) {
-    GraphImpl()->CloseAudioInput(aID, aListener);
+void SourceMediaStream::CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID) {
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(GraphImpl());
+  if (!mInputListener) {
+    return;
   }
+  GraphImpl()->CloseAudioInput(aID, mInputListener);
   mInputListener = nullptr;
 }
 
+void SourceMediaStream::Destroy() {
+  MOZ_ASSERT(NS_IsMainThread());
+  Maybe<CubebUtils::AudioDeviceID> id = Nothing();
+  CloseAudioInput(id);
+
+  MediaStream::Destroy();
+}
+
 void SourceMediaStream::DestroyImpl() {
-  Maybe<CubebUtils::AudioDeviceID> id = Nothing();
-  CloseAudioInput(id, mInputListener);
-
   GraphImpl()->AssertOnGraphThreadOrNotRunning();
   for (int32_t i = mConsumers.Length() - 1; i >= 0; --i) {
     // Disconnect before we come under mMutex's lock since it can call back
     // through RemoveDirectTrackListenerImpl() and deadlock.
     mConsumers[i]->Disconnect();
   }
 
   // Hold mMutex while mGraph is reset so that other threads holding mMutex
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -650,24 +650,24 @@ class SourceMediaStream : public MediaSt
    * control loop. Pulling is initially disabled for all tracks. Due to
    * unavoidable race conditions, after a call to SetPullingEnabled(false)
    * it is still possible for a NotifyPull to occur.
    */
   void SetPullingEnabled(TrackID aTrackID, bool aEnabled);
 
   // Users of audio inputs go through the stream so it can track when the
   // last stream referencing an input goes away, so it can close the cubeb
-  // input.  Also note: callable on any thread (though it bounces through
-  // MainThread to set the command if needed).
+  // input. Main thread only.
   nsresult OpenAudioInput(CubebUtils::AudioDeviceID aID,
                           AudioDataListener* aListener);
-  // Note: also implied when Destroy() happens
-  void CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID,
-                       AudioDataListener* aListener);
+  // Main thread only.
+  void CloseAudioInput(Maybe<CubebUtils::AudioDeviceID>& aID);
 
+  // Main thread only.
+  void Destroy() override;
   // MediaStreamGraph thread only
   void DestroyImpl() override;
 
   // Call these on any thread.
   /**
    * Call all MediaStreamTrackListeners to request new data via the NotifyPull
    * API (if enabled).
    * aDesiredUpToTime (in): end time of new data requested.
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -596,17 +596,17 @@ nsresult MediaEngineWebRTCMicrophoneSour
         if (stream->IsDestroyed()) {
           return;
         }
 
         stream->GraphImpl()->AppendMessage(MakeUnique<StartStopMessage>(
             that->mInputProcessing, StartStopMessage::Stop));
         CubebUtils::AudioDeviceID deviceID = that->mDeviceInfo->DeviceID();
         Maybe<CubebUtils::AudioDeviceID> id = Some(deviceID);
-        stream->CloseAudioInput(id, that->mInputProcessing);
+        stream->CloseAudioInput(id);
       }));
 
   MOZ_ASSERT(mState == kStarted, "Should be started when stopping");
   mState = kStopped;
 
   return NS_OK;
 }
 
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -207,17 +207,17 @@ pref("extensions.strictCompatibility", f
 pref("extensions.minCompatibleAppVersion", "11.0");
 
 pref("extensions.update.url", "https://versioncheck.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 pref("extensions.update.background.url", "https://versioncheck-bg.addons.mozilla.org/update/VersionCheck.php?reqVersion=%REQ_VERSION%&id=%ITEM_ID%&version=%ITEM_VERSION%&maxAppVersion=%ITEM_MAXAPPVERSION%&status=%ITEM_STATUS%&appID=%APP_ID%&appVersion=%APP_VERSION%&appOS=%APP_OS%&appABI=%APP_ABI%&locale=%APP_LOCALE%&currentAppVersion=%CURRENT_APP_VERSION%&updateType=%UPDATE_TYPE%&compatMode=%COMPATIBILITY_MODE%");
 
 /* preferences for the Get Add-ons pane */
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.search.browseURL", "https://addons.mozilla.org/%LOCALE%/android/search?q=%TERMS%&platform=%OS%&appver=%VERSION%");
-pref("extensions.getAddons.browseAddons", "https://addons.mozilla.org/%LOCALE%/android/");
+pref("extensions.getAddons.browseAddons", "https://addons.mozilla.org/%LOCALE%/firefox/collections/4757633/mob/");
 pref("extensions.getAddons.get.url", "https://services.addons.mozilla.org/api/v3/addons/search/?guid=%IDS%&lang=%LOCALE%");
 pref("extensions.getAddons.compatOverides.url", "https://services.addons.mozilla.org/api/v3/addons/compat-override/?guid=%IDS%&lang=%LOCALE%");
 pref("extensions.getAddons.langpacks.url", "https://services.addons.mozilla.org/api/v3/addons/language-tools/?app=android&type=language&appversion=%VERSION%");
 
 /* preference for the locale picker */
 pref("extensions.getLocales.get.url", "");
 pref("extensions.compatability.locales.buildid", "0");
 
--- a/mobile/android/chrome/content/aboutAddons.js
+++ b/mobile/android/chrome/content/aboutAddons.js
@@ -4,16 +4,17 @@
 
 "use strict";
 
 /* globals gChromeWin */
 
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {AddonManager} = ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {EventDispatcher} = ChromeUtils.import("resource://gre/modules/Messaging.jsm");
 
 const AMO_ICON = "chrome://browser/skin/images/amo-logo.png";
 const UPDATE_INDICATOR = "chrome://browser/skin/images/extension-update.svg";
 
 var gStringBundle = Services.strings.createBundle("chrome://browser/locale/aboutAddons.properties");
 
 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function() {
   return window.docShell.rootTreeItem.domWindow
@@ -256,23 +257,44 @@ var Addons = {
     outer.appendChild(img);
 
     let inner = document.createElement("div");
     inner.className = "inner";
 
     let title = document.createElement("div");
     title.id = "browse-title";
     title.className = "title";
-    title.textContent = gStringBundle.GetStringFromName("addons.browseAll");
+    title.textContent = this._getAmoTitle();
     inner.appendChild(title);
 
     outer.appendChild(inner);
     return outer;
   },
 
+  // Ensure we get a localized string by using the previous title as a fallback
+  // if the new one has not yet been translated.
+  _getAmoTitle: function _getAmoTitle() {
+    const initialTitleUS = "Browse all Firefox Add-ons";
+    const updatedTitleUS = "Browse Firefox’s Recommended Extensions";
+    const initialTitleLocalized = gStringBundle.GetStringFromName("addons.browseAll");
+    const updatedTitleLocalized = gStringBundle.GetStringFromName("addons.browseRecommended");
+    let title = initialTitleLocalized;
+
+    const titleWasLocalized = updatedTitleLocalized !== updatedTitleUS;
+    const localeIsDefaultUS = updatedTitleLocalized === updatedTitleUS &&
+                              initialTitleLocalized === initialTitleUS;
+
+    if (titleWasLocalized || localeIsDefaultUS) {
+        title = updatedTitleLocalized;
+    }
+
+    EventDispatcher.instance.dispatch("about:addons", {amoTitle: title} );
+    return title;
+  },
+
   _createItemForAddon: function _createItemForAddon(aAddon) {
     let opType = this._getOpTypeForOperations(aAddon.pendingOperations);
     let hasUpdate = this._addonHasUpdate(aAddon);
 
     let optionsURL = aAddon.optionsURL || "";
 
     let blocked = "";
     switch (aAddon.blocklistState) {
--- a/mobile/android/locales/en-US/chrome/aboutAddons.properties
+++ b/mobile/android/locales/en-US/chrome/aboutAddons.properties
@@ -3,11 +3,13 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 addonType.extension=Extension
 addonType.theme=Theme
 addonType.locale=Locale
 
 addonStatus.uninstalled=%S will be uninstalled after restart.
 
+# Will keep both strings and at runtime will fallback on the old one if the new one is not yet localized
 addons.browseAll=Browse all Firefox Add-ons
+addons.browseRecommended=Browse Firefox’s Recommended Extensions
 
-addon.options=Options
\ No newline at end of file
+addon.options=Options
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/StringHelper.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/StringHelper.java
@@ -29,16 +29,19 @@ public class StringHelper {
     public final String ABOUT_FIREFOX_URL;
     public final String ABOUT_HOME_URL = "about:home";
     public final String ABOUT_ADDONS_URL = "about:addons";
     public final String ABOUT_SCHEME = "about:";
 
     // About pages' titles
     public final String ABOUT_HOME_TITLE = "";
 
+    // To be kept in sync with 'addons.browseRecommended' from 'aboutAddons.properties'
+    public final String ABOUT_ADDONS_AMO_TITLE = "Browse Firefox’s Recommended Extensions";
+
     // Context Menu item strings
     public final String CONTEXT_MENU_BOOKMARK_LINK = "Bookmark Link";
     public final String CONTEXT_MENU_OPEN_LINK_IN_NEW_TAB = "Open Link in New Tab";
     public final String CONTEXT_MENU_OPEN_IN_NEW_TAB;
     public final String CONTEXT_MENU_OPEN_LINK_IN_PRIVATE_TAB = "Open Link in Private Tab";
     public final String CONTEXT_MENU_OPEN_IN_PRIVATE_TAB;
     public final String CONTEXT_MENU_COPY_LINK = "Copy Link";
     public final String CONTEXT_MENU_SHARE_LINK = "Share Link";
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAddonManager.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAddonManager.java
@@ -1,48 +1,58 @@
 /* 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/. */
 
 package org.mozilla.gecko.tests;
 
 import org.json.JSONObject;
 import org.mozilla.gecko.Actions;
+import org.mozilla.gecko.util.GeckoBundle;
 
 import android.util.DisplayMetrics;
 
 /**
  * This test performs the following steps to check the behavior of the Add-on Manager:
  *
  * 1) Open the Add-on Manager from the Add-ons menu item, and then close it.
  * 2) Open the Add-on Manager by visiting about:addons in the URL bar.
  * 3) Open a new tab, select the Add-ons menu item, then verify that the existing
  *    Add-on Manager tab was selected, instead of opening a new tab.
  */
 public class testAddonManager extends PixelTest  {
     public void testAddonManager() {
         Actions.EventExpecter tabEventExpecter;
         Actions.EventExpecter contentEventExpecter;
+        Actions.EventExpecter amoTitleExpecter;
         final String aboutAddonsURL = mStringHelper.ABOUT_ADDONS_URL;
+        final String amoTitle = mStringHelper.ABOUT_ADDONS_AMO_TITLE;
 
         blockForGeckoReady();
 
         // Use the menu to open the Addon Manger
         selectMenuItem(mStringHelper.ADDONS_LABEL);
 
         // Set up listeners to catch the page load we're about to do
         tabEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Tab:Added");
         contentEventExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "Content:DOMContentLoaded");
+        amoTitleExpecter = mActions.expectGlobalEvent(Actions.EventType.UI, "about:addons");
 
         // Wait for the new tab and page to load
         tabEventExpecter.blockForEvent();
         contentEventExpecter.blockForEvent();
+        GeckoBundle addonsPageBundle = amoTitleExpecter.blockForBundle();
 
         tabEventExpecter.unregisterListener();
         contentEventExpecter.unregisterListener();
+        amoTitleExpecter.unregisterListener();
+
+        // Verify the AMO title
+        final String actualAmoTitle = addonsPageBundle.getString("amoTitle");
+        mAsserter.is(actualAmoTitle, amoTitle, "Incorrect AMO title");
 
         // Verify the url
         verifyUrlBarTitle(aboutAddonsURL);
 
         // Close the Add-on Manager
         mSolo.goBack();
 
         // Load the about:addons page and verify it was loaded
--- a/netwerk/protocol/http/nsHttpConnection.cpp
+++ b/netwerk/protocol/http/nsHttpConnection.cpp
@@ -115,16 +115,18 @@ nsHttpConnection::nsHttpConnection()
   LOG(("Creating nsHttpConnection @%p\n", this));
 
   // the default timeout is for when this connection has not yet processed a
   // transaction
   static const PRIntervalTime k5Sec = PR_SecondsToInterval(5);
   mIdleTimeout = (k5Sec < gHttpHandler->IdleTimeout())
                      ? k5Sec
                      : gHttpHandler->IdleTimeout();
+
+ mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
 }
 
 nsHttpConnection::~nsHttpConnection() {
   LOG(("Destroying nsHttpConnection @%p\n", this));
 
   if (!mEverUsedSpdy) {
     LOG(("nsHttpConnection %p performed %d HTTP/1.x transactions\n", this,
          mHttp1xTransactionCount));
@@ -148,16 +150,30 @@ nsHttpConnection::~nsHttpConnection() {
   if (mTotalBytesRead) {
     uint32_t totalKBRead = static_cast<uint32_t>(mTotalBytesRead >> 10);
     LOG(("nsHttpConnection %p read %dkb on connection spdy=%d\n", this,
          totalKBRead, mEverUsedSpdy));
     Telemetry::Accumulate(mEverUsedSpdy ? Telemetry::SPDY_KBREAD_PER_CONN2
                                         : Telemetry::HTTP_KBREAD_PER_CONN2,
                           totalKBRead);
   }
+
+  if (mThroughCaptivePortal) {
+    if (mTotalBytesRead || mTotalBytesWritten) {
+      auto total = Clamp<uint32_t>(
+          (mTotalBytesRead >> 10) + (mTotalBytesWritten >> 10), 0,
+          std::numeric_limits<uint32_t>::max());
+      Telemetry::ScalarAdd(
+          Telemetry::ScalarID::NETWORKING_DATA_TRANSFERRED_CAPTIVE_PORTAL, total);
+    }
+
+    Telemetry::ScalarAdd(
+        Telemetry::ScalarID::NETWORKING_HTTP_CONNECTIONS_CAPTIVE_PORTAL, 1);
+  }
+
   if (mForceSendTimer) {
     mForceSendTimer->Cancel();
     mForceSendTimer = nullptr;
   }
 
   if ((mFastOpenStatus != TFO_FAILED) && (mFastOpenStatus != TFO_HTTP) &&
       (((mFastOpenStatus > TFO_DISABLED_CONNECT) &&
         (mFastOpenStatus < TFO_BACKUP_CONN)) ||
--- a/netwerk/protocol/http/nsHttpConnection.h
+++ b/netwerk/protocol/http/nsHttpConnection.h
@@ -436,16 +436,17 @@ class nsHttpConnection final : public ns
  public:
   void BootstrapTimings(TimingStruct times);
 
  private:
   TimingStruct mBootstrappedTimings;
   bool mBootstrappedTimingsSet;
 
   nsTArray<HttpTrafficCategory> mTrafficCategory;
+  bool mThroughCaptivePortal;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpConnection, NS_HTTPCONNECTION_IID)
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpConnection_h__
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -302,17 +302,18 @@ nsHttpHandler::nsHttpHandler()
       mFastOpenStallsLimit(3),
       mFastOpenStallsCounter(0),
       mFastOpenStallsIdleTime(10),
       mFastOpenStallsTimeout(20),
       mActiveTabPriority(true),
       mProcessId(0),
       mNextChannelId(1),
       mLastActiveTabLoadOptimizationLock(
-          "nsHttpConnectionMgr::LastActiveTabLoadOptimization") {
+          "nsHttpConnectionMgr::LastActiveTabLoadOptimization"),
+      mThroughCaptivePortal(false) {
   LOG(("Creating nsHttpHandler [this=%p].\n", this));
 
   mUserAgentOverride.SetIsVoid(true);
 
   MOZ_ASSERT(!gHttpHandler, "HTTP handler already created!");
 
   nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
   if (runtime) {
@@ -562,16 +563,17 @@ nsresult nsHttpHandler::Init() {
     obsService->AddObserver(this, "last-pb-context-exited", true);
     obsService->AddObserver(this, "browser:purge-session-history", true);
     obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
     obsService->AddObserver(this, "application-background", true);
     obsService->AddObserver(this, "psm:user-certificate-added", true);
     obsService->AddObserver(this, "psm:user-certificate-deleted", true);
     obsService->AddObserver(this, "intl:app-locales-changed", true);
     obsService->AddObserver(this, "browser-delayed-startup-finished", true);
+    obsService->AddObserver(this, "network:captive-portal-connectivity", true);
 
     if (!IsNeckoChild()) {
       obsService->AddObserver(
           this, "net:current-toplevel-outer-content-windowid", true);
     }
 
     if (mFastOpenSupported) {
       obsService->AddObserver(this, "captive-portal-login", true);
@@ -2333,16 +2335,19 @@ nsHttpHandler::Observe(nsISupports *subj
     // If a user certificate has been removed, we need to check if there
     // are others installed
     MaybeEnableSpeculativeConnect();
   } else if (!strcmp(topic, "intl:app-locales-changed")) {
     // If the locale changed, there's a chance the accept language did too
     mAcceptLanguagesIsDirty = true;
   } else if (!strcmp(topic, "browser-delayed-startup-finished")) {
     MaybeEnableSpeculativeConnect();
+  } else if (!strcmp(topic, "network:captive-portal-connectivity")) {
+    nsAutoCString data8 = NS_ConvertUTF16toUTF8(data);
+    mThroughCaptivePortal = data8.EqualsLiteral("captive");
   }
 
   return NS_OK;
 }
 
 // nsISpeculativeConnect
 
 static bool CanEnableSpeculativeConnect() {
--- a/netwerk/protocol/http/nsHttpHandler.h
+++ b/netwerk/protocol/http/nsHttpHandler.h
@@ -417,16 +417,17 @@ class nsHttpHandler final : public nsIHt
   TimeStamp const GetLastActiveTabLoadOptimizationHit();
   void SetLastActiveTabLoadOptimizationHit(TimeStamp const &when);
   bool IsBeforeLastActiveTabLoadOptimization(TimeStamp const &when);
 
   bool DumpHpackTables() { return mDumpHpackTables; }
 
   HttpTrafficAnalyzer *GetHttpTrafficAnalyzer();
 
+  bool GetThroughCaptivePortal() { return mThroughCaptivePortal; }
  private:
   nsHttpHandler();
 
   virtual ~nsHttpHandler();
 
   MOZ_MUST_USE nsresult Init();
 
   //
@@ -732,16 +733,18 @@ class nsHttpHandler final : public nsIHt
  public:
   MOZ_MUST_USE nsresult NewChannelId(uint64_t &channelId);
 
   void BlacklistSpdy(const nsHttpConnectionInfo *ci);
   MOZ_MUST_USE bool IsSpdyBlacklisted(const nsHttpConnectionInfo *ci);
 
  private:
   nsTHashtable<nsCStringHashKey> mBlacklistedSpdyOrigins;
+
+  bool mThroughCaptivePortal;
 };
 
 extern StaticRefPtr<nsHttpHandler> gHttpHandler;
 
 //-----------------------------------------------------------------------------
 // nsHttpsHandler - thin wrapper to distinguish the HTTP handler from the
 //                  HTTPS handler (even though they share the same impl).
 //-----------------------------------------------------------------------------
--- a/netwerk/protocol/http/nsHttpTransaction.cpp
+++ b/netwerk/protocol/http/nsHttpTransaction.cpp
@@ -153,16 +153,18 @@ nsHttpTransaction::nsHttpTransaction()
   LOG(("Creating nsHttpTransaction @%p\n", this));
 
 #ifdef MOZ_VALGRIND
   memset(&mSelfAddr, 0, sizeof(NetAddr));
   memset(&mPeerAddr, 0, sizeof(NetAddr));
 #endif
   mSelfAddr.raw.family = PR_AF_UNSPEC;
   mPeerAddr.raw.family = PR_AF_UNSPEC;
+
+  mThroughCaptivePortal = gHttpHandler->GetThroughCaptivePortal();
 }
 
 void nsHttpTransaction::ResumeReading() {
   MOZ_ASSERT(OnSocketThread(), "not on socket thread");
 
   if (!mReadingStopped) {
     return;
   }
@@ -1191,16 +1193,21 @@ void nsHttpTransaction::Close(nsresult r
   if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
     HttpTrafficAnalyzer *hta = gHttpHandler->GetHttpTrafficAnalyzer();
     if (hta) {
       hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
                                          mContentRead);
     }
   }
 
+  if (mThroughCaptivePortal) {
+    Telemetry::ScalarAdd(
+        Telemetry::ScalarID::NETWORKING_HTTP_TRANSACTIONS_CAPTIVE_PORTAL, 1);
+  }
+
   if (relConn && mConnection) {
     MutexAutoLock lock(mLock);
     mConnection = nullptr;
   }
 
   mStatus = reason;
   mTransactionDone = true;  // forcibly flag the transaction as complete
   mClosed = true;
--- a/netwerk/protocol/http/nsHttpTransaction.h
+++ b/netwerk/protocol/http/nsHttpTransaction.h
@@ -479,14 +479,15 @@ class nsHttpTransaction final : public n
   } mEarlyDataDisposition;
 
   uint8_t mFastOpenStatus;
 
   // H2 websocket support
   RefPtr<SpdyConnectTransaction> mH2WSTransaction;
 
   HttpTrafficCategory mTrafficCategory;
+  bool mThroughCaptivePortal;
 };
 
 }  // namespace net
 }  // namespace mozilla
 
 #endif  // nsHttpTransaction_h__
--- a/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini
+++ b/testing/web-platform/meta/webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html.ini
@@ -1,11 +1,13 @@
 [sub-sample-buffer-stitching.html]
   disabled:
     if (os == 'win' and processor == 'aarch64'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533911
+    if (os == 'win' and version == '6.1.7601'): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
+    if (os == 'linux' and bits == 32): https://bugzilla.mozilla.org/show_bug.cgi?id=1533762
   [# AUDIT TASK RUNNER FINISHED: 2 out of 2 tasks were failed.]
     expected: FAIL
 
   [X Stitched sine-wave buffers at sample rate 44100 does not equal [0,0.06264832615852356,0.12505052983760834,0.18696144223213196,0.24813786149024963,0.308339387178421,0.36732956767082214,0.4248766303062439,0.4807544946670532,0.5347436666488647,0.5866319537162781,0.6362155675888062,0.683299720287323,0.7276993989944458,0.7692402005195618,0.8077588677406311...\] with an element-wise tolerance of {"absoluteThreshold":0.000090957,"relativeThreshold":0}.\n\tIndex\tActual\t\t\tExpected\t\tAbsError\t\tRelError\t\tTest threshold\n\t[2003\]\t-9.6732087433338165e-2\t-9.6823699772357941e-2\t9.1612339019775391e-5\t9.4617680624852212e-4\t9.0957000000000003e-5\n\t[2004\]\t-3.4187544137239456e-2\t-3.4279607236385345e-2\t9.2063099145889282e-5\t2.6856520995424621e-3\t9.0957000000000003e-5\n\t[2005\]\t2.8491314500570297e-2\t2.8398986905813217e-2\t9.2327594757080078e-5\t3.2510876202481997e-3\t9.0957000000000003e-5\n\t[2006\]\t9.1058239340782166e-2\t9.0966261923313141e-2\t9.1977417469024658e-5\t1.0111157205356415e-3\t9.0957000000000003e-5\n\t[2007\]\t1.5326742827892303e-1\t1.5317615866661072e-1\t9.1269612312316895e-5\t5.9584737668585898e-4\t9.0957000000000003e-5\n\t...and 38045 more errors.\n\tMax AbsError of 2.0274701528251171e-3 at index of 44050.\n\t[44050\]\t-7.1237324737012386e-3\t-5.0962623208761215e-3\t2.0274701528251171e-3\t3.9783473164634225e-1\t9.0957000000000003e-5\n\tMax RelError of 5.5714977262789269e+1 at index of 30419.\n\t[30419\]\t-1.4247581129893661e-3\t-2.5121373255387880e-5\t1.3996367397339782e-3\t5.5714977262789269e+1\t9.0957000000000003e-5\n]
     expected: FAIL
 
   [< [buffer-stitching-2\] 1 out of 3 assertions were failed.]
     expected: FAIL
--- a/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_separators.js
@@ -51,24 +51,16 @@ add_task(async function test_support_sep
   Assert.ok(
     window.getComputedStyle(panelUIButton)
           .getPropertyValue("border-image-source")
           .includes(`rgb(${hexToRGB(SEPARATOR_VERTICAL_COLOR).join(", ")})`),
     "Vertical separator color properly set"
   );
 
   let toolbox = document.querySelector("#navigator-toolbox");
-  if (AppConstants.platform == "macosx") {
-    Assert.ok(
-      window.getComputedStyle(toolbox, "::after").boxShadow
-            .includes(`rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`),
-      "Bottom separator color properly set"
-    );
-  } else {
-    Assert.equal(
-      window.getComputedStyle(toolbox, "::after").borderBottomColor,
-      `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`,
-      "Bottom separator color properly set"
-    );
-  }
+  Assert.equal(
+    window.getComputedStyle(toolbox).borderBottomColor,
+    `rgb(${hexToRGB(SEPARATOR_BOTTOM_COLOR).join(", ")})`,
+    "Bottom separator color properly set"
+  );
 
   await extension.unload();
 });
--- a/toolkit/components/search/SearchService.jsm
+++ b/toolkit/components/search/SearchService.jsm
@@ -2857,16 +2857,18 @@ SearchService.prototype = {
     let val = this.getGlobalAttr(name);
     if (val && this.getGlobalAttr(name + "Hash") != getVerificationHash(val)) {
       LOG("getVerifiedGlobalAttr, invalid hash for " + name);
       return "";
     }
     return val;
   },
 
+  _listJSONURL: ((AppConstants.platform == "android") ? APP_SEARCH_PREFIX : EXT_SEARCH_PREFIX) + "list.json",
+
   _engines: { },
   __sortedEngines: null,
   _visibleDefaultEngines: [],
   _searchDefault: null,
   _searchOrder: [],
   // Stores a map of the built in engines installed and their params so
   // they can be reconstructed in restarts.
   _extensions: new Map(),
@@ -3462,38 +3464,36 @@ SearchService.prototype = {
    * Loads jar engines asynchronously.
    *
    * @returns {Promise} A promise, resolved successfully if finding jar engines
    * succeeds.
    */
   async _findEngines() {
     LOG("_findEngines: looking for engines in JARs");
 
-    let prefix = AppConstants.platform == "android" ? APP_SEARCH_PREFIX : EXT_SEARCH_PREFIX;
-    let listURL = prefix + "list.json";
-    let chan = makeChannel(listURL);
+    let chan = makeChannel(this._listJSONURL);
     if (!chan) {
-      LOG("_findEngines: " + prefix + " isn't registered");
+      LOG("_findEngines: " + this._listJSONURL + " isn't registered");
       return [];
     }
 
     let uris = [];
 
     // Read list.json to find the engines we need to load.
     let request = new XMLHttpRequest();
     request.overrideMimeType("text/plain");
     let list = await new Promise(resolve => {
       request.onload = function(event) {
         resolve(event.target.responseText);
       };
       request.onerror = function(event) {
-        LOG("_findEngines: failed to read " + listURL);
+        LOG("_findEngines: failed to read " + this._listJSONURL);
         resolve();
       };
-      request.open("GET", Services.io.newURI(listURL).spec, true);
+      request.open("GET", Services.io.newURI(this._listJSONURL).spec, true);
       request.send();
     });
 
     this._parseListJSON(list, uris);
     return uris;
   },
 
   _parseListJSON(list, uris) {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_validate_engines.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Ensure all the engines defined in list.json are valid by
+// creating a new list.json that contains every engine and
+// loading them all.
+
+"use strict";
+
+Cu.importGlobalProperties(["fetch"]);
+
+const {SearchService} = ChromeUtils.import("resource://gre/modules/SearchService.jsm");
+const LIST_JSON_URL = "resource://search-extensions/list.json";
+
+function traverse(obj, fun) {
+  for (var i in obj) {
+    fun.apply(this, [i, obj[i]]);
+    if (obj[i] !== null && typeof(obj[i]) == "object") {
+      traverse(obj[i], fun);
+    }
+  }
+}
+
+const ss = new SearchService();
+
+add_task(async function test_validate_engines() {
+  let engines = await fetch(LIST_JSON_URL).then(req => req.json());
+
+  let visibleDefaultEngines = new Set();
+  traverse(engines, (key, val) => {
+    if (key === "visibleDefaultEngines") {
+      val.forEach(engine => visibleDefaultEngines.add(engine));
+    }
+  });
+
+  let listjson = {default: {
+    visibleDefaultEngines: Array.from(visibleDefaultEngines),
+  }};
+  ss._listJSONURL = "data:application/json," + JSON.stringify(listjson);
+
+  await AddonTestUtils.promiseStartupManager();
+  await ss.init();
+});
--- a/toolkit/components/search/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell.ini
@@ -102,11 +102,12 @@ skip-if = (verify && !debug && (os == 'l
 [test_addEngineWithDetails.js]
 [test_addEngineWithDetailsObject.js]
 [test_addEngineWithExtensionID.js]
 [test_chromeresource_icon2.js]
 [test_engineUpdate.js]
 [test_paramSubstitution.js]
 [test_migrateWebExtensionEngine.js]
 [test_sendSubmissionURL.js]
+[test_validate_engines.js]
 [test_validate_manifests.js]
 [test_webextensions_install.js]
 [test_purpose.js]
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -2687,16 +2687,782 @@ update:
       - application-update-telemetry-alerts@mozilla.com
       - rstrong@mozilla.com
     release_channel_collection: opt-out
     record_in_processes:
       - main
     operating_systems:
       - windows
 
+update.startup:
+  from_app_version:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the previous application version that the update was applied to
+      when the update makes it to the last phase where the application has
+      exited and started.
+    expires: "72"
+    kind: string
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
+  mar_partial_size_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes of a partial update MAR file when
+      the update makes it to the last phase where the application has exited and
+      started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  mar_complete_size_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes of a complete update MAR file when
+      the update makes it to the last phase where the application has exited and
+      started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+update.startup.intervals:
+  check:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the check phase of the update process
+      when an update makes it to the last phase where the application has exited
+      and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  download_bits_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the BITS downloader for a partial update MAR file when the
+      update makes it to the last phase where the application has exited and
+      and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  download_bits_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the BITS downloader for a complete update MAR file when the
+      update makes it to the last phase where the application has exited and
+      started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  download_internal_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the internal application downloader for a partial update MAR
+      file when the update makes it to the last phase where the application has
+      exited and and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  download_internal_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the internal application downloader for a complete update
+      MAR file when the update makes it to the last phase where the application
+      has exited and and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  stage_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the stage phase of the update process
+      for a partial update MAR file when the update makes it to the last phase
+      where the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  stage_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the stage phase of the update process
+      for a complete update MAR file when the update makes it to the last phase
+      where the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  apply_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the apply phase of the update process
+      for a partial update MAR file when the update makes it to the last phase
+      where the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  apply_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the apply phase of the update process
+      for a complete update MAR file when the update makes it to the last phase
+      where the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+update.startup.downloads:
+  bits_partial_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the BITS downloader for
+      a partial update MAR file when the update makes it to the last phase where
+      the application has exited and started. This value can be different than
+      the size of the update MAR file because the value is only monitored
+      during the initial download while the application is running and not if
+      the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_partial_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the BITS downloader for a
+      partial update MAR file when the update makes it to the last phase where
+      the application has exited and started. This value can be used with the
+      value of update.startup.downloads.bits_partial_bytes to estimate the bytes
+      per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_complete_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the BITS downloader for
+      a complete update MAR file when the update makes it to the last phase
+      where the application has exited and started. This value can be different
+      than the size of the update MAR file because the value is only monitored
+      during the initial download while the application is running and not if
+      the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_complete_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the BITS downloader for a
+      complete update MAR file when the update makes it to the last phase where
+      the application has exited and started. This value can be used with the
+      value of update.startup.downloads.bits_complete_bytes to estimate the
+      bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  internal_partial_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the internal application
+      downloader for a partial update MAR file when the update makes it to the
+      last phase where the application has exited and started. This value can be
+      different than the size of the update MAR file because the value is only
+      monitored during the initial download while the application is running and
+      not if the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_partial_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the internal application
+      downloader for a partial update MAR file when the update makes it to the
+      last phase where the application has exited and started. This value can be
+      used with the value of update.startup.downloads.internal_partial_bytes to
+      estimate the bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_complete_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded by the internal
+      application downloader for a complete update MAR file when an update makes
+      it to the last phase where the application has exited and started. This
+      value can be different than the size of the update MAR file because the
+      value is only monitored during the initial download while the
+      application is running and not if the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_complete_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the internal application
+      downloader for a complete update MAR file when the update makes it to the
+      last phase where the application has exited and started. This value can be
+      used with the value of update.startup.downloads.internal_complete_bytes to
+      estimate the bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+update.session:
+  from_app_version:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the previous application version that the update was applied to
+      when the update has finished due to a failure before the application has
+      exited and started.
+    expires: "72"
+    kind: string
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
+
+  mar_partial_size_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes of a partial update MAR file when
+      the update has finished due to a failure before the application has exited
+      and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  mar_complete_size_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes of a complete update MAR file when
+      the update has finished due to a failure before the application has exited
+      and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+update.session.intervals:
+  check:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the check phase of the update process
+      when an update has finished due to a failure before the application has
+      exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  download_bits_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the BITS downloader for a partial update MAR file when the
+      update has finished due to a failure before the application has exited
+      and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  download_bits_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the BITS downloader for a complete update MAR file when the
+      update has finished due to a failure before the application has exited and
+      started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  download_internal_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the internal application downloader for a partial update MAR
+      file when the update has finished due to a failure before the application
+      has exited and and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  download_internal_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the download phase of the update
+      process using the internal application downloader for a complete update
+      MAR file when the update has finished due to a failure before the
+      application has exited and and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  stage_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the stage phase of the update process
+      for a partial update MAR file when the update has finished due to a
+      failure before the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  stage_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the stage phase of the update process
+      for a complete update MAR file when the update has finished due to a
+      failure before the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  apply_partial:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the apply phase of the update process
+      for a partial update MAR file when the update has finished due to a
+      failure before the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  apply_complete:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the interval in seconds of the apply phase of the update process
+      for a complete update MAR file when the update has finished due to a
+      failure before the application has exited and started.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+update.session.downloads:
+  bits_partial_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the BITS downloader for
+      a partial update MAR file when the update has finished due to a failure
+      before the application has exited and started. This value can be
+      different than the size of the update MAR file because the value is only
+      monitored during the initial download while the application is running and
+      not if the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_partial_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the BITS downloader for a
+      partial update MAR file when the update has finished due to a failure
+      before the application has exited and started. This value can be used with
+      the value of update.session.downloads.bits_partial_bytes to estimate the
+      bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_complete_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the BITS downloader for
+      a complete update MAR file when the update has finished due to a failure
+      before the application has exited and started. This value can be different
+      than the size of the update MAR file because the value is only monitored
+      during the initial download while the application is running and not if
+      the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  bits_complete_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the BITS downloader for a
+      complete update MAR file when the update has finished due to a failure
+      before the application has exited and started. This value can be used with
+      the value of update.session.downloads.bits_complete_bytes to estimate the
+      bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+    operating_systems:
+      - "windows"
+
+  internal_partial_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded using the internal
+      application downloader for a partial update MAR file when the update has
+      finished due to a failure before the application has exited and started.
+      This value can be different than the size of the update MAR file because
+      the value is only monitored during the initial download while the
+      application is running and not if the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_partial_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the internal application
+      downloader for a partial update MAR file when the update has finished due
+      to a failure before the application has exited and started. This value can
+      be used with the value of update.session.downloads.internal_partial_bytes
+      to estimate the bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_complete_bytes:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of bytes downloaded by the internal
+      application downloader for a complete update MAR file when an update has
+      finished due to a failure before the application has exited and started.
+      This value can be different than the size of the update MAR file because
+      the value is only monitored during the initial download while the
+      application is running and not if the download is resumed.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
+  internal_complete_seconds:
+    bug_numbers:
+      - 1539154
+    description: >
+      Records the total number of seconds spent downloading during the initial
+      download without exiting the application using the internal application
+      downloader for a complete update MAR file when the update has finished due
+      to a failure before the application has exited and started. This value can
+      be used with the value of update.session.downloads.internal_complete_bytes
+      to estimate the bytes per second for the download.
+    expires: "72"
+    kind: uint
+    keyed: false
+    notification_emails:
+      - application-update-telemetry-alerts@mozilla.com
+      - rstrong@mozilla.com
+    release_channel_collection: opt-out
+    record_in_processes:
+      - main
+
 # The following section contains search counters.
 browser.search:
   with_ads:
     bug_numbers:
       - 1495548
       - 1505411
     description: >
       Records counts of SERP pages with adverts displayed. The key format is ‘<engine-name>’.
@@ -2843,16 +3609,55 @@ networking:
     expires: "73"
     keyed: true
     kind: uint
     notification_emails:
       - gachen@mozilla.com
     record_in_processes:
       - 'main'
 
+  data_transferred_captive_portal:
+    bug_numbers:
+      - 1543005
+    description: >
+      Hom many KB has been transfer over a captive portal during a subsession.
+    expires: "73"
+    keyed: false
+    kind: uint
+    notification_emails:
+      - ddamjanovic@mozilla.com
+    record_in_processes:
+      - 'main'
+
+  http_transactions_captive_portal:
+    bug_numbers:
+      - 1543005
+    description: >
+      Number of http transactions transfer over a captive portal during a subsession.
+    expires: "73"
+    keyed: false
+    kind: uint
+    notification_emails:
+      - ddamjanovic@mozilla.com
+    record_in_processes:
+      - 'main'
+
+  http_connections_captive_portal:
+    bug_numbers:
+      - 1543005
+    description: >
+      Number of http connections transfer over a captive portal during a subsession.
+    expires: "73"
+    keyed: false
+    kind: uint
+    notification_emails:
+      - ddamjanovic@mozilla.com
+    record_in_processes:
+      - 'main'
+
 # The following section is for probes testing the Telemetry system. They will not be
 # submitted in pings and are only used for testing.
 telemetry.test:
   unsigned_int_kind:
     bug_numbers:
       - 1276190
     description: >
       This is a test uint type with a really long description, maybe spanning even multiple
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -57,18 +57,16 @@ support-files =
 
 [test_about_networking.html]
 [test_arrowpanel.xul]
 skip-if = (verify && (os == 'win'))
 [test_autocomplete2.xul]
 [test_autocomplete3.xul]
 [test_autocomplete4.xul]
 [test_autocomplete5.xul]
-[test_autocomplete_delayOnPaste.xul]
-subsuite = clipboard
 [test_autocomplete_emphasis.xul]
 [test_autocomplete_with_composition_on_input.html]
 [test_autocomplete_with_composition_on_textbox.xul]
 [test_autocomplete_placehold_last_complete.xul]
 [test_browser_drop.xul]
 [test_bug253481.xul]
 subsuite = clipboard
 [test_bug263683.xul]
deleted file mode 100644
--- a/toolkit/content/tests/chrome/test_autocomplete_delayOnPaste.xul
+++ /dev/null
@@ -1,126 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
-
-<window title="Autocomplete Widget Test 4"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onload="runTest();">
-
-  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
-  <script type="application/javascript"
-          src="chrome://global/content/globalOverlay.js"/>
-
-<textbox id="autocomplete"
-         type="autocomplete"
-         completedefaultindex="true"
-         onsearchcomplete="searchComplete();"
-         timeout="0"
-         autocompletesearch="simple"/>
-
-<script class="testbody" type="application/javascript">
-<![CDATA[
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-function autoCompleteSimpleResult(aString) {
-  this.searchString = aString;
-  this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
-  this.matchCount = 1;
-  this._param = "Result";
-}
-autoCompleteSimpleResult.prototype = {
- _param: "",
- searchString: null,
- searchResult: Ci.nsIAutoCompleteResult.RESULT_FAILURE,
- defaultIndex: 0,
- errorDescription: null,
- matchCount: 0,
- getValueAt: function() { return this._param; },
- getCommentAt: function() { return null; },
- getStyleAt: function() { return null; },
- getImageAt: function() { return null; },
- getFinalCompleteValueAt: function() { return this.getValueAt(); },
- getLabelAt: function() { return null; },
- removeValueAt: function() {}
-};
-
-// A basic autocomplete implementation that returns one result.
-let autoCompleteSimple = {
-  classID: Components.ID("0a2afbdb-f30e-47d1-9cb1-0cd160240aca"),
-  contractID: "@mozilla.org/autocomplete/search;1?name=simple",
-  QueryInterface: ChromeUtils.generateQI([
-    Ci.nsIFactory,
-    Ci.nsIAutoCompleteSearch
-  ]),
-  createInstance: function (outer, iid) {
-    return this.QueryInterface(iid);
-  },
-
-  registerFactory: function () {
-    let registrar =
-      Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.registerFactory(this.classID, "Test Simple Autocomplete",
-                              this.contractID, this);
-  },
-  unregisterFactory: function () {
-    let registrar =
-      Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-    registrar.unregisterFactory(this.classID, this);
-  },
-
-  startSearch: function (aString, aParam, aResult, aListener) {
-    let result = new autoCompleteSimpleResult(aString);
-    aListener.onSearchResult(this, result);
-  },
-  stopSearch: function () {}
-};
-
-SimpleTest.waitForExplicitFinish();
-
-let gACTimer;
-let gAutoComplete;
-
-function searchComplete() {
-  is(gAutoComplete.value, "result", "Value should be autocompleted now");
-  ok(Date.now() - gACTimer  > 500, "There should be a delay before autocomplete");
-
-  // Unregister the factory so that we don't get in the way of other tests
-  autoCompleteSimple.unregisterFactory();
-  SimpleTest.finish();
-}
-
-function runTest() {
-
-  autoCompleteSimple.registerFactory();
-  gAutoComplete = $("autocomplete");
-
-  const SEARCH_STRING = "res";
-
-  function cbCallback() {
-    gAutoComplete.focus();
-    synthesizeKey("v", { accelKey: true });
-    is(gAutoComplete.value, SEARCH_STRING, "Value should not be autocompleted immediately");
-  }
-
-  SimpleTest.waitForClipboard(SEARCH_STRING, function () {
-    gACTimer = Date.now();
-    Cc["@mozilla.org/widget/clipboardhelper;1"]
-      .getService(Ci.nsIClipboardHelper)
-      .copyStringToClipboard(SEARCH_STRING, Ci.nsIClipboard.kGlobalClipboard);
-  }, cbCallback, cbCallback);
-}
-]]>
-</script>
-
-<body xmlns="http://www.w3.org/1999/xhtml">
-<p id="display">
-</p>
-<div id="content" style="display: none">
-</div>
-<pre id="test">
-</pre>
-</body>
-
-</window>
--- a/toolkit/content/widgets/autocomplete.xml
+++ b/toolkit/content/widgets/autocomplete.xml
@@ -40,25 +40,18 @@
       <constructor><![CDATA[
         this.mController = Cc["@mozilla.org/autocomplete/controller;1"].
           getService(Ci.nsIAutoCompleteController);
 
         this._searchBeginHandler = this.initEventHandler("searchbegin");
         this._searchCompleteHandler = this.initEventHandler("searchcomplete");
         this._textEnteredHandler = this.initEventHandler("textentered");
         this._textRevertedHandler = this.initEventHandler("textreverted");
-
-        // For security reasons delay searches on pasted values.
-        this.inputField.controllers.insertControllerAt(0, this._pasteController);
       ]]></constructor>
 
-      <destructor><![CDATA[
-        this.inputField.controllers.removeController(this._pasteController);
-      ]]></destructor>
-
       <!-- =================== nsIAutoCompleteInput =================== -->
 
       <field name="_popup">null</field>
       <property name="popup" readonly="true">
         <getter><![CDATA[
           // Memoize the result in a field rather than replacing this property,
           // so that it can be reset along with the binding.
           if (this._popup) {
@@ -108,28 +101,18 @@
                 onset="this.setAttribute('forcecomplete', val); return val;"
                 onget="return this.getAttribute('forcecomplete') == 'true';"/>
 
       <property name="minResultsForPopup"
                 onset="this.setAttribute('minresultsforpopup', val); return val;"
                 onget="var m = parseInt(this.getAttribute('minresultsforpopup')); return isNaN(m) ? 1 : m;"/>
 
       <property name="timeout"
-                onset="this.setAttribute('timeout', val); return val;">
-        <getter><![CDATA[
-          // For security reasons delay searches on pasted values.
-          if (this._valueIsPasted) {
-            let t = parseInt(this.getAttribute("pastetimeout"));
-            return isNaN(t) ? 1000 : t;
-          }
-
-          let t = parseInt(this.getAttribute("timeout"));
-          return isNaN(t) ? 50 : t;
-        ]]></getter>
-      </property>
+                onset="this.setAttribute('timeout', val); return val;"
+                onget="var t = parseInt(this.getAttribute('timeout')); return isNaN(t) ? 50 : t;"/>
 
       <property name="searchParam"
                 onget="return this.getAttribute('autocompletesearchparam') || '';"
                 onset="this.setAttribute('autocompletesearchparam', val); return val;"/>
 
       <property name="searchCount" readonly="true"
                 onget="this.initSearchNames(); return this.mSearchNames.length;"/>
 
@@ -549,35 +532,16 @@
       <method name="resetActionType">
         <body><![CDATA[
           if (this.mIgnoreInput)
             return;
           this.removeAttribute("actiontype");
         ]]></body>
       </method>
 
-      <field name="_valueIsPasted">false</field>
-      <field name="_pasteController"><![CDATA[
-        ({
-          _autocomplete: this,
-          _kGlobalClipboard: Ci.nsIClipboard.kGlobalClipboard,
-          supportsCommand: aCommand => aCommand == "cmd_paste",
-          doCommand(aCommand) {
-            this._autocomplete._valueIsPasted = true;
-            this._autocomplete.editor.paste(this._kGlobalClipboard);
-            this._autocomplete._valueIsPasted = false;
-          },
-          isCommandEnabled(aCommand) {
-            return this._autocomplete.editor.isSelectionEditable &&
-                   this._autocomplete.editor.canPaste(this._kGlobalClipboard);
-          },
-          onEvent() {},
-        })
-      ]]></field>
-
       <method name="_setValueInternal">
         <parameter name="aValue"/>
         <parameter name="aIsUserInput"/>
         <body><![CDATA[
           this.mIgnoreInput = true;
 
           if (typeof this.onBeforeValueSet == "function")
             aValue = this.onBeforeValueSet(aValue);
--- a/toolkit/mozapps/update/UpdateService.jsm
+++ b/toolkit/mozapps/update/UpdateService.jsm
@@ -228,16 +228,18 @@ var gUpdateMutexHandle = null;
 var gUpdateDirPermissionFixAttempted = false;
 // This is used for serializing writes to the update log file
 var gLogfileWritePromise;
 // This value will be set to true if it appears that BITS is being used by
 // another user to download updates. We don't really want two users using BITS
 // at once. Computers with many users (ex: a school computer), should not end
 // up with dozens of BITS jobs.
 var gBITSInUseByAnotherUser = false;
+// The start time in milliseconds of the update check.
+var gCheckStartMs;
 
 XPCOMUtils.defineLazyGetter(this, "gLogEnabled", function aus_gLogEnabled() {
   return Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG, false) ||
          Services.prefs.getBoolPref(PREF_APP_UPDATE_LOG_FILE, false);
 });
 
 XPCOMUtils.defineLazyGetter(this, "gLogfileEnabled",
                             function aus_gLogfileEnabled() {
@@ -1928,18 +1930,26 @@ UpdateService.prototype = {
         }
 
         // When downloading an update with nsIIncrementalDownload the download
         // is stopped when the quit-application observer notification is
         // received and networking hasn't started to shutdown. The download will
         // be resumed the next time the application starts. Downloads using
         // Windows BITS are not stopped since they don't require Firefox to be
         // running to perform the download.
-        if (this._downloader && !this._downloader.usingBits) {
-          this.stopDownload();
+        if (this._downloader) {
+          if (!this._downloader.usingBits) {
+            this.stopDownload();
+          } else {
+            // The BITS downloader isn't stopped on exit so the
+            // active-update.xml needs to be saved for the values sent to
+            // telemetry to be saved to disk.
+            Cc["@mozilla.org/updates/update-manager;1"].
+              getService(Ci.nsIUpdateManager).saveUpdates();
+          }
         }
         // Prevent leaking the downloader (bug 454964)
         this._downloader = null;
         // In case an update check is in progress.
         Cc["@mozilla.org/updates/update-checker;1"].
           createInstance(Ci.nsIUpdateChecker).stopCurrentCheck();
 
         if (gLogfileWritePromise) {
@@ -2122,16 +2132,20 @@ UpdateService.prototype = {
     }
 
     let parts = status.split(":");
     update.state = parts[0];
     if (update.state == STATE_FAILED && parts[1]) {
       update.errorCode = parseInt(parts[1]);
     }
 
+    if (update.state == STATE_SUCCEEDED || update.patchCount == 1 ||
+        (update.selectedPatch && update.selectedPatch.type == "complete")) {
+      AUSTLMY.pingUpdatePhases(update, true);
+    }
 
     if (status != STATE_SUCCEEDED) {
       // Rotate the update logs so the update log isn't removed. By passing
       // false the patch directory won't be removed.
       cleanUpUpdatesDir(false);
     }
 
     if (status == STATE_SUCCEEDED) {
@@ -3170,16 +3184,20 @@ UpdateManager.prototype = {
   /**
    * See nsIUpdateService.idl
    */
   refreshUpdateStatus: function UM_refreshUpdateStatus() {
     var update = this._activeUpdate;
     if (!update) {
       return;
     }
+
+    let patch = update.selectedPatch.QueryInterface(Ci.nsIWritablePropertyBag);
+    patch.setProperty("stageFinished", Math.ceil(Date.now() / 1000));
+
     var status = readStatusFile(getUpdatesDir());
     pingStateAndStatusCodes(update, false, status);
     var parts = status.split(":");
     update.state = parts[0];
     if (update.state == STATE_FAILED && parts[1]) {
       update.errorCode = parseInt(parts[1]);
     }
 
@@ -3195,16 +3213,20 @@ UpdateManager.prototype = {
 
       update.QueryInterface(Ci.nsIWritablePropertyBag);
       update.setProperty("stagingFailed", "true");
     }
     if (update.state == STATE_APPLIED && shouldUseService()) {
       writeStatusFile(getUpdatesDir(), update.state = STATE_APPLIED_SERVICE);
     }
 
+    if (update.state == STATE_FAILED) {
+      AUSTLMY.pingUpdatePhases(update, false);
+    }
+
     // Now that the active update's properties have been updated write the
     // active-update.xml to disk. Since there have been no changes to the update
     // history the updates.xml will not be written to disk.
     this.saveUpdates();
 
     // Send an observer notification which the app update doorhanger uses to
     // display a restart notification
     LOG("UpdateManager:refreshUpdateStatus - Notifying observers that " +
@@ -3218,16 +3240,17 @@ UpdateManager.prototype = {
       return;
     }
 
     if (update.state == STATE_APPLIED ||
         update.state == STATE_APPLIED_SERVICE ||
         update.state == STATE_PENDING ||
         update.state == STATE_PENDING_SERVICE ||
         update.state == STATE_PENDING_ELEVATE) {
+      patch.setProperty("applyStart", Math.floor(Date.now() / 1000));
       // Notify the user that an update has been staged and is ready for
       // installation (i.e. that they should restart the application).
       let prompter = Cc["@mozilla.org/updates/update-prompt;1"].
                      createInstance(Ci.nsIUpdatePrompt);
       prompter.showUpdateDownloaded(update, true);
     }
   },
 
@@ -3373,16 +3396,17 @@ Checker.prototype = {
    */
   checkForUpdates: function UC_checkForUpdates(listener, force) {
     LOG("Checker: checkForUpdates, force: " + force);
     gUpdateFileWriteInfo = {phase: "check", failure: false};
     if (!listener) {
       throw Cr.NS_ERROR_NULL_POINTER;
     }
 
+    gCheckStartMs = Date.now();
     let UpdateServiceInstance = UpdateServiceFactory.createInstance();
     // |force| can override |canCheckForUpdates| since |force| indicates a
     // manual update check. But nothing should override enterprise policies.
     if (UpdateServiceInstance.disabledByPolicy) {
       LOG("Checker: checkForUpdates, disabled by policy");
       return;
     }
     if (!UpdateServiceInstance.canCheckForUpdates && !force) {
@@ -3646,16 +3670,27 @@ Downloader.prototype = {
    * observer registers itself. Without this variable, there is no way of
    * knowing whether the download was started as Active or Idle and, therefore,
    * we don't know if we need to start Active mode when _pendingRequest
    * resolves.
    */
   _bitsActiveNotifications: false,
 
   /**
+   * The start time of the first download attempt in milliseconds for telemetry.
+   */
+  _startDownloadMs: null,
+
+  /**
+   * The name of the downloader being used to download the update. This is used
+   * when setting property names on the update patch for telemetry.
+   */
+  _downloaderName: "bits",
+
+  /**
    * Cancels the active download.
    *
    * For a BITS download, this will cancel and remove the download job. For
    * an nsIIncrementalDownload, this will stop the download, but leaves the
    * data around to allow the transfer to be resumed later.
    */
   cancel: async function Downloader_cancel(cancelError) {
     LOG("Downloader: cancel");
@@ -3873,23 +3908,38 @@ Downloader.prototype = {
     // This function may return null, which indicates that there are no patches
     // to download.
     this._patch = this._selectPatch(update, updateDir);
     if (!this._patch) {
       LOG("Downloader:downloadUpdate - no patch to download");
       AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE_PATCH);
       return readStatusFile(updateDir);
     }
+    // QI the update and the patch to nsIWritablePropertyBag so it isn't
+    // necessary later in the download code.
+    this._update.QueryInterface(Ci.nsIWritablePropertyBag);
+    if (gCheckStartMs && !this._update.getProperty("checkInterval")) {
+      let interval = Math.max(Math.ceil((Date.now() - gCheckStartMs) / 1000), 1);
+      this._update.setProperty("checkInterval", interval);
+    }
     // this._patch implements nsIWritablePropertyBag. Expose that interface
     // immediately after a patch is assigned so that this._patch.getProperty
     // and this._patch.setProperty can always safely be called.
     this._patch.QueryInterface(Ci.nsIWritablePropertyBag);
     this.isCompleteUpdate = this._patch.type == "complete";
 
-    if (!this._canUseBits(this._patch)) {
+    let canUseBits = this._canUseBits(this._patch);
+    if (!canUseBits) {
+      this._downloaderName = "internal";
+    }
+    if (!this._patch.getProperty(this._downloaderName + "DownloadStart")) {
+      this._patch.setProperty(this._downloaderName + "DownloadStart", Math.floor(Date.now() / 1000));
+    }
+
+    if (!canUseBits) {
       let patchFile = getUpdatesDir().clone();
       patchFile.append(FILE_UPDATE_MAR);
 
       // The interval is 0 since there is no need to throttle downloads.
       let interval = 0;
 
       LOG("Downloader:downloadUpdate - Starting nsIIncrementalDownload with " +
           "url: " + this._patch.URL + ", path: " + patchFile.path +
@@ -4103,16 +4153,27 @@ Downloader.prototype = {
           ", final URI spec: " + request.finalURI.spec);
       // Set finalURL in onStartRequest if it is different.
       if (this._patch.finalURL != request.finalURI.spec) {
         this._patch.finalURL = request.finalURI.spec;
         Cc["@mozilla.org/updates/update-manager;1"].
           getService(Ci.nsIUpdateManager).saveUpdates();
       }
     }
+    // Only record the download bytes per second when there isn't already a
+    // value for the bytes per second so downloads that are already in progess
+    // don't have their records overwritten. When the Update Agent is
+    // implemented this should be reworked so that telemetry receives the bytes
+    // and seconds it took to complete for the entire update download instead of
+    // just the sample that is currently recorded. Note: this._patch has already
+    // been QI'd to nsIWritablePropertyBag.
+    if (!this._patch.getProperty("internalBytes") &&
+        !this._patch.getProperty("bitsBytes")) {
+      this._startDownloadMs = Date.now();
+    }
 
     // Make shallow copy in case listeners remove themselves when called.
     let listeners = this._listeners.concat();
     let listenerCount = listeners.length;
     for (let i = 0; i < listenerCount; ++i) {
       listeners[i].onStartRequest(request);
     }
   },
@@ -4126,16 +4187,21 @@ Downloader.prototype = {
    * @param   progress
    *          The current number of bytes transferred
    * @param   maxProgress
    *          The total number of bytes that must be transferred
    */
   onProgress: function Downloader_onProgress(request, context, progress,
                                              maxProgress) {
     LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
+    if (this._startDownloadMs) {
+      let seconds = Math.round((Date.now() - this._startDownloadMs) / 1000);
+      this._patch.setProperty(this._downloaderName + "Seconds", seconds);
+      this._patch.setProperty(this._downloaderName + "Bytes", progress);
+    }
 
     if (progress > this._patch.size) {
       LOG("Downloader:onProgress - progress: " + progress +
           " is higher than patch size: " + this._patch.size);
       AUSTLMY.pingDownloadCode(this.isCompleteUpdate,
                                AUSTLMY.DWNLD_ERR_PATCH_SIZE_LARGER);
       this.cancel(Cr.NS_ERROR_UNEXPECTED);
       return;
@@ -4248,16 +4314,18 @@ Downloader.prototype = {
                                             DEFAULT_SOCKET_MAX_ERRORS);
     // Prevent the preference from setting a value greater than 20.
     maxFail = Math.min(maxFail, 20);
     let permissionFixingInProgress = false;
     LOG("Downloader:onStopRequest - status: " + status + ", " +
         "current fail: " + this.updateService._consecutiveSocketErrors + ", " +
         "max fail: " + maxFail + ", " +
         "retryTimeout: " + retryTimeout);
+    this._patch.setProperty(this._downloaderName + "DownloadFinished",
+                            Math.floor(Date.now() / 1000));
     if (Components.isSuccessCode(status)) {
       if (this._verifyDownload()) {
         if (shouldUseService()) {
           state = STATE_PENDING_SERVICE;
         } else if (getElevationRequired()) {
           state = STATE_PENDING_ELEVATE;
         } else {
           state = STATE_PENDING;
@@ -4451,16 +4519,17 @@ Downloader.prototype = {
 
       if (allFailed && !permissionFixingInProgress) {
         if (Services.prefs.getBoolPref(PREF_APP_UPDATE_DOORHANGER, false)) {
           let downloadAttempts = Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0);
           downloadAttempts++;
           Services.prefs.setIntPref(PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, downloadAttempts);
           let maxAttempts = Math.min(Services.prefs.getIntPref(PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2), 10);
 
+          AUSTLMY.pingUpdatePhases(this._update, false);
           if (downloadAttempts > maxAttempts) {
             LOG("Downloader:onStopRequest - notifying observers of error. " +
                 "topic: update-error, status: download-attempts-exceeded, " +
                 "downloadAttempts: " + downloadAttempts + " " +
                 "maxAttempts: " + maxAttempts);
             Services.obs.notifyObservers(this._update, "update-error", "download-attempts-exceeded");
           } else {
             this._update.selectedPatch.selected = false;
@@ -4491,29 +4560,32 @@ Downloader.prototype = {
     }
 
     if (state == STATE_PENDING || state == STATE_PENDING_SERVICE ||
         state == STATE_PENDING_ELEVATE) {
       if (getCanStageUpdates()) {
         LOG("Downloader:onStopRequest - attempting to stage update: " +
             this._update.name);
         gUpdateFileWriteInfo = {phase: "stage", failure: false};
+        this._patch.setProperty("stageStart", Math.floor(Date.now() / 1000));
         // Stage the update
         try {
           Cc["@mozilla.org/updates/update-processor;1"].
             createInstance(Ci.nsIUpdateProcessor).processUpdate();
         } catch (e) {
           // Fail gracefully in case the application does not support the update
           // processor service.
           LOG("Downloader:onStopRequest - failed to stage update. Exception: " +
               e);
           if (this.background) {
             shouldShowPrompt = true;
           }
         }
+      } else {
+        this._patch.setProperty("applyStart", Math.floor(Date.now() / 1000));
       }
     }
 
     // Do this after *everything* else, since it will likely cause the app
     // to shut down.
     if (shouldShowPrompt) {
       // Notify the user that an update has been downloaded and is ready for
       // installation (i.e. that they should restart the application). We do
--- a/toolkit/mozapps/update/UpdateTelemetry.jsm
+++ b/toolkit/mozapps/update/UpdateTelemetry.jsm
@@ -8,16 +8,24 @@ var EXPORTED_SYMBOLS = [
   "AUSTLMY",
 ];
 
 const {AppConstants} = ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 const {BitsError, BitsUnknownError} =
   ChromeUtils.import("resource://gre/modules/Bits.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm", this);
 
+// It is possible for the update.session telemetry to be set more than once
+// which must be prevented since they are scalars and setting them more than
+// once could lead to values set in the first ping not being present in the
+// next ping which would make the values incomprehensible in relation to the
+// other values. This isn't needed for update.startup since this will only be
+// set once during startup.
+var gUpdatePhasesSetForSession = false;
+
 var AUSTLMY = {
   // Telemetry for the application update background update check occurs when
   // the background update timer fires after the update interval which is
   // determined by the app.update.interval preference and its telemetry
   // histogram IDs have the suffix '_NOTIFY'.
   // Telemetry for the externally initiated background update check occurs when
   // a call is made to |checkForBackgroundUpdates| which is typically initiated
   // by an application when it has determined that the application should have
@@ -428,16 +436,121 @@ var AUSTLMY = {
             Cu.reportError(e);
           }
         }
       }
     }
   },
 
   /**
+   * Submit the update phase telemetry. These are scalars and must only be
+   * submitted once per sesssion. The update.startup is only submitted once
+   * once per session due to it only being submitted during startup and only the
+   * first call to pingUpdatePhases for update.session will be submitted.
+   *
+   * @param  aUpdate
+   *         The update object which contains the values to submit to telemetry.
+   * @param  aIsStartup
+   *         If true the telemetry will be set under update.startup and if false
+   *         the telemetry will be set under update.session. When false
+   *         subsequent calls will return early and not submit telemetry.
+   */
+  pingUpdatePhases: function UT_pingUpdatePhases(aUpdate, aIsStartup) {
+    if (!aIsStartup && !Cu.isInAutomation) {
+      if (gUpdatePhasesSetForSession) {
+        return;
+      }
+      gUpdatePhasesSetForSession = true;
+    }
+    let basePrefix = aIsStartup ? "update.startup." : "update.session.";
+    // None of the calls to getProperty should fail.
+    try {
+      let update = aUpdate.QueryInterface(Ci.nsIWritablePropertyBag);
+      let scalarSet = Services.telemetry.scalarSet;
+
+      // Though it is possible that the previous app version that was updated
+      // from could change the record is for the app version that initiated the
+      // update.
+      scalarSet(basePrefix + "from_app_version", aUpdate.previousAppVersion);
+
+      // The check interval only happens once even if the partial patch fails
+      // to apply on restart and the complete patch is downloaded.
+      scalarSet(basePrefix + "intervals.check",
+                update.getProperty("checkInterval"));
+
+      for (let i = 0; i < aUpdate.patchCount; ++i) {
+        let patch =
+          aUpdate.getPatchAt(i).QueryInterface(Ci.nsIWritablePropertyBag);
+        let type = patch.type;
+
+        scalarSet(basePrefix + "mar_" + type + "_size_bytes", patch.size);
+
+        let prefix = basePrefix + "intervals.";
+        let internalDownloadStart = patch.getProperty("internalDownloadStart");
+        let internalDownloadFinished =
+          patch.getProperty("internalDownloadFinished");
+        if (internalDownloadStart !== null && internalDownloadFinished !== null) {
+          scalarSet(prefix + "download_internal_" + type,
+                    Math.max((internalDownloadFinished - internalDownloadStart), 1));
+        }
+
+        let bitsDownloadStart = patch.getProperty("bitsDownloadStart");
+        let bitsDownloadFinished = patch.getProperty("bitsDownloadFinished");
+        if (bitsDownloadStart !== null && bitsDownloadFinished !== null) {
+          scalarSet(prefix + "download_bits_" + type,
+                    Math.max((bitsDownloadFinished - bitsDownloadStart), 1));
+        }
+
+
+        let stageStart = patch.getProperty("stageStart");
+        let stageFinished = patch.getProperty("stageFinished");
+        if (stageStart !== null && stageFinished !== null) {
+          scalarSet(prefix + "stage_" + type,
+                    Math.max((stageFinished - stageStart), 1));
+        }
+
+        // Both the partial and the complete patch are recorded for the apply
+        // interval because it is possible for a partial patch to fail when it
+        // is applied during a restart and then to try the complete patch.
+        let applyStart = patch.getProperty("applyStart");
+        if (applyStart !== null) {
+          let applyFinished = Date.now() / 1000;
+          scalarSet(prefix + "apply_" + type,
+                    Math.max((applyFinished - applyStart), 1));
+        }
+
+        prefix = basePrefix + "downloads.";
+        let internalBytes = patch.getProperty("internalBytes");
+        if (internalBytes !== null) {
+          scalarSet(prefix + "internal_" + type + "_bytes",
+                    Math.max(internalBytes, 1));
+        }
+        let internalSeconds = patch.getProperty("internalSeconds");
+        if (internalSeconds !== null) {
+          scalarSet(prefix + "internal_" + type + "_seconds",
+                    Math.max(internalSeconds, 1));
+        }
+
+        let bitsBytes = patch.getProperty("bitsBytes");
+        if (bitsBytes !== null) {
+          scalarSet(prefix + "bits_" + type + "_bytes",
+                    Math.max(bitsBytes, 1));
+        }
+        let bitsSeconds = patch.getProperty("bitsSeconds");
+        if (bitsSeconds !== null) {
+          scalarSet(prefix + "bits_" + type + "_seconds",
+                    Math.max(bitsSeconds, 1));
+        }
+      }
+    } catch (e) {
+      Cu.reportError(e);
+    }
+  },
+
+  /**
    * Submit a telemetry ping for the last page displayed by the update wizard.
    *
    * @param  aPageID
    *         The page id for the last page displayed.
    */
   pingWizLastPageCode: function UT_pingWizLastPageCode(aPageID) {
     let pageMap = { invalid: 0,
                     dummy: 1,
--- a/toolkit/mozapps/update/tests/browser/browser.ini
+++ b/toolkit/mozapps/update/tests/browser/browser.ini
@@ -69,9 +69,31 @@ reason = test must be able to prevent fi
 [browser_doorhanger_bc_patch_partialBadSize.js]
 [browser_doorhanger_bc_patch_partialBadSize_complete.js]
 [browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js]
 [browser_doorhanger_sp_patch_completeApplyFailure.js]
 [browser_doorhanger_sp_patch_partialApplyFailure.js]
 [browser_doorhanger_sp_patch_partialApplyFailure_complete.js]
 [browser_doorhanger_sp_patch_partialApplyFailure_complete_staging.js]
 [browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js]
+
+# Telemetry Application Update Tests
+[browser_telemetry_completeBadSize.js]
+[browser_telemetry_partialBadSize_completeBadSize.js]
+[browser_telemetry_complete_stageFailure.js]
+skip-if = asan
+reason = Bug 1545712
+[browser_telemetry_partial_stageFailure_complete_stageFailure.js]
+skip-if = asan
+reason = Bug 1545712
+[browser_telemetry_complete_applyFailure.js]
+[browser_telemetry_partial_applyFailure_complete_applyFailure.js]
+[browser_telemetry_partial_applyFailure_complete_stageFailure.js]
+skip-if = asan
+reason = Bug 1545712
+[browser_telemetry_partial_applyFailure_complete_applied.js]
+[browser_telemetry_partial_applyFailure_complete_staged_applied.js]
+[browser_telemetry_partialBadSize_complete_staged_applied.js]
+[browser_telemetry_complete_applied.js]
+[browser_telemetry_partial_applied.js]
+[browser_telemetry_partial_staged_applied.js]
+[browser_telemetry_complete_staged_applied.js]
 [browser_TelemetryUpdatePing.js]
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures.js
@@ -1,13 +1,12 @@
 add_task(async function testDownloadFailures() {
   const maxBackgroundErrors = 5;
   SpecialPowers.pushPrefEnv({set: [
     [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 2],
   ]});
   let updateParams = "badURL=1";
 
   await runUpdateTest(updateParams, 1, [
     {
       // if we fail maxBackgroundErrors download attempts, then we want to
       // first show the user an update available prompt.
       notificationId: "update-available",
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloadAutoFailures_bgWin.js
@@ -1,13 +1,12 @@
 add_task(async function testBackgroundWindowFailures() {
   const maxBackgroundErrors = 5;
   SpecialPowers.pushPrefEnv({set: [
     [PREF_APP_UPDATE_BACKGROUNDMAXERRORS, maxBackgroundErrors],
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 2],
   ]});
 
   let updateParams = "badURL=1";
   let extraWindow = await BrowserTestUtils.openNewBrowserWindow();
   await SimpleTest.promiseFocus(extraWindow);
 
   function getBackgroundWindowHandler(destroyWindow) {
     return async function() {
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_downloaded_staged.js
@@ -1,15 +1,14 @@
 add_task(async function testCompleteAndPartialPatchesWithBadCompleteSize() {
   SpecialPowers.pushPrefEnv({set: [
     [PREF_APP_UPDATE_STAGING_ENABLED, true],
   ]});
 
   let updateParams = "invalidCompleteSize=1&promptWaitTime=0";
-
   await runUpdateTest(updateParams, 1, [
     {
       notificationId: "update-restart",
       button: "secondaryButton",
       cleanup() {
         AppMenuNotifications.removeNotification(/.*/);
       },
     },
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_completeBadSize.js
@@ -1,15 +1,10 @@
 add_task(async function testCompletePatchWithBadCompleteSize() {
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 2],
-  ]});
-
   let updateParams = "completePatchOnly=1&invalidCompleteSize=1";
-
   await runUpdateTest(updateParams, 1, [
     {
       // if we fail maxBackgroundErrors download attempts, then we want to
       // first show the user an update available prompt.
       notificationId: "update-available",
       button: "button",
     },
     {
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize.js
@@ -1,14 +1,10 @@
 add_task(async function testPartialPatchWithBadPartialSize() {
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 2],
-  ]});
   let updateParams = "partialPatchOnly=1&invalidPartialSize=1";
-
   await runUpdateTest(updateParams, 1, [
     {
       // if we fail maxBackgroundErrors download attempts, then we want to
       // first show the user an update available prompt.
       notificationId: "update-available",
       button: "button",
     },
     {
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_bc_patch_partialBadSize_completeBadSize.js
@@ -1,14 +1,10 @@
 add_task(async function testCompleteAndPartialPatchesWithBadSizes() {
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 2],
-  ]});
   let updateParams = "invalidPartialSize=1&invalidCompleteSize=1";
-
   await runUpdateTest(updateParams, 1, [
     {
       // if we fail maxBackgroundErrors download attempts, then we want to
       // first show the user an update available prompt.
       notificationId: "update-available",
       button: "button",
     },
     {
--- a/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js
+++ b/toolkit/mozapps/update/tests/browser/browser_doorhanger_sp_patch_partialApplyFailure_completeBadSize.js
@@ -1,14 +1,16 @@
 add_task(async function testPartialPatchApplyFailureWithCompleteValidationFailure() {
   // because of the way we're simulating failure, we have to just pretend we've already
   // retried.
-  SpecialPowers.pushPrefEnv({set: [
-    [PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS, 0],
-  ]});
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 0],
+    ],
+  });
 
   let patchProps = {type: "partial",
                     state: STATE_PENDING};
   let patches = getLocalPatchString(patchProps);
   patchProps = {size: "1234",
                 selected: "false"};
   patches += getLocalPatchString(patchProps);
   let updateProps = {isCompleteUpdate: "false"};
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_completeBadSize.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.session
+// Complete patch only
+// Complete patch download failure
+add_task(async function telemetry_completeBadSize() {
+  let updateParams = "&completePatchOnly=1&invalidCompleteSize=1";
+  await runTelemetryUpdateTest(updateParams, "update-error");
+
+  let expected = getTelemetryUpdatePhaseValues({
+    forSession: true,
+    noPartialPatch: true,
+    completeBadSize: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_complete_applied.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Complete patch only
+// Complete patch download
+// Complete patch applied
+add_task(async function telemetry_complete_applied() {
+  let updateParams = "&completePatchOnly=1";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noPartialPatch: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_complete_applyFailure.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Complete patch only
+// Complete patch download
+// Complete patch apply failure
+add_task(async function telemetry_complete_applyFailure() {
+  let updateParams = "&completePatchOnly=1";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noPartialPatch: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_complete_stageFailure.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.session
+// Complete patch only
+// Complete patch download
+// Complete patch stage failure
+add_task(async function telemetry_complete_stageFailure() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  let updateParams = "&completePatchOnly=1";
+  await runTelemetryUpdateTest(updateParams, "update-staged", true);
+
+  let expected = getTelemetryUpdatePhaseValues({
+    forSession: true,
+    noPartialPatch: true,
+    noApplyComplete: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_complete_staged_applied.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Complete patch only
+// Complete patch download
+// Complete patch staged
+// Complete patch applied
+add_task(async function telemetry_complete_staged_applied() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  let updateParams = "&completePatchOnly=1";
+  await runTelemetryUpdateTest(updateParams, "update-staged");
+
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noPartialPatch: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partialBadSize_completeBadSize.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.session
+// Partial and complete patches
+// Partial patch download failure
+// Complete patch download failure
+add_task(async function telemetry_partialBadSize_completeBadSize() {
+  let updateParams = "&invalidPartialSize=1&invalidCompleteSize=1";
+  await runTelemetryUpdateTest(updateParams, "update-error");
+
+  let expected = getTelemetryUpdatePhaseValues({
+    forSession: true,
+    partialBadSize: true,
+    completeBadSize: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partialBadSize_complete_staged_applied.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download failure
+// Complete patch download
+// Complete patch staged
+// Complete patch applied
+add_task(async function telemetry_partialBadSize_complete_staged_applied() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  let updateParams = "&invalidPartialSize=1";
+  await runTelemetryUpdateTest(updateParams, "update-staged");
+
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    partialBadSize: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_applied.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download
+// Partial patch applied
+add_task(async function telemetry_partial_applied() {
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noInternalComplete: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_applyFailure_complete_applied.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download
+// Partial patch apply failure
+// Complete patch download
+// Complete patch applied
+add_task(async function telemetry_sp_partialBadSize_complete_staged_applied() {
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+
+  // The download of the complete patch will happen automatically.
+  await waitForEvent("update-downloaded");
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({});
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_applyFailure_complete_applyFailure.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download
+// Partial patch apply failure
+// Complete patch download
+// Complete patch apply failure
+add_task(async function telemetry_partial_applyFailure_complete_applyFailure() {
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+
+  // The download of the complete patch will happen automatically.
+  await waitForEvent("update-downloaded");
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({});
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_applyFailure_complete_stageFailure.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.session
+// Partial and complete patches
+// Partial patch download
+// Partial patch apply failure
+// Complete patch download
+// Complete patch stage failure
+add_task(async function telemetry_partial_applyFailure_complete_stageFailure() {
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+  // Now that staging is enabled setup the test updater.
+  await setupTestUpdater();
+  // Remove the update-settings.ini file so staging fails.
+  removeUpdateSettingsIni();
+  // Fail applying the partial.
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry wasn't set.
+  checkTelemetryUpdatePhaseEmpty(true);
+
+  // The download and staging of the complete patch will happen automatically.
+  await waitForEvent("update-staged");
+
+  let expected = getTelemetryUpdatePhaseValues({
+    forSession: true,
+    noStagePartial: true,
+    noApplyComplete: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_applyFailure_complete_staged_applied.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download
+// Partial patch apply failure
+// Complete patch download
+// Complete patch staged
+// Complete patch applied
+add_task(async function telemetry_partial_applyFailure_complete_staged_applied() {
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-downloaded");
+
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+  // Now that staging is enabled setup the test updater.
+  await setupTestUpdater();
+  // Fail applying the partial.
+  writeStatusFile(STATE_FAILED_CRC_ERROR);
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+
+  // The download and staging of the complete will happen automatically.
+  await waitForEvent("update-staged");
+  // Succeed applying the complete patch.
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noStagePartial: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_stageFailure_complete_stageFailure.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.session
+// Partial and complete patches
+// Partial patch download
+// Partial patch stage failure
+// Complete patch download
+// Complete patch stage failure
+add_task(async function telemetry_partial_stageFailure_complete_stageFailure() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-staged", true);
+
+  await waitForEvent("update-staged");
+
+  let expected = getTelemetryUpdatePhaseValues({
+    forSession: true,
+    noApplyPartial: true,
+    noApplyComplete: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  testPostUpdateProcessing();
+  // Verify that update phase startup telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(true);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/update/tests/browser/browser_telemetry_partial_staged_applied.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Telemetry test for Application Update phases.
+// Telemetry update.startup
+// Partial and complete patches
+// Partial patch download
+// Partial patch staged
+// Partial patch applied
+add_task(async function telemetry_partial_staged_applied() {
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      [PREF_APP_UPDATE_STAGING_ENABLED, true],
+    ],
+  });
+
+  let updateParams = "";
+  await runTelemetryUpdateTest(updateParams, "update-staged");
+
+  writeStatusFile(STATE_SUCCEEDED);
+  testPostUpdateProcessing();
+
+  let expected = getTelemetryUpdatePhaseValues({
+    noInternalComplete: true,
+  });
+  checkTelemetryUpdatePhases(expected);
+
+  // Verify that update phase session telemetry is empty.
+  checkTelemetryUpdatePhaseEmpty(false);
+});
--- a/toolkit/mozapps/update/tests/browser/head.js
+++ b/toolkit/mozapps/update/tests/browser/head.js
@@ -41,31 +41,36 @@ gDebugTest = true;
 requestLongerTimeout(10);
 
 /**
  * Common tasks to perform for all tests before each one has started.
  */
 add_task(async function setupTestCommon() {
   await SpecialPowers.pushPrefEnv({
     set: [
+      [PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS, 0],
+      [PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS, 2],
       [PREF_APP_UPDATE_LOG, gDebugTest],
+      [PREF_APP_UPDATE_SERVICE_ENABLED, false],
     ],
   });
 
   setUpdateTimerPrefs();
   removeUpdateFiles(true);
+  AppMenuNotifications.removeNotification(/.*/);
   // Most app update mochitest-browser-chrome tests expect auto update to be
   // enabled. Those that don't will explicitly change this.
   await setAppUpdateAutoEnabledHelper(true);
 });
 
 /**
  * Common tasks to perform for all tests after each one has finished.
  */
 registerCleanupFunction(async () => {
+  AppMenuNotifications.removeNotification(/.*/);
   gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "");
   gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "");
   UpdateListener.reset();
   reloadUpdateManagerData(true);
   // Pass false when the log files are needed for troubleshooting the tests.
   removeUpdateFiles(true);
   // Always try to restore the original updater files. If none of the updater
   // backup files are present then this is just a no-op.
@@ -115,18 +120,19 @@ async function continueFileHandler(leafN
     continueFile = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
     let continuePath = REL_PATH_DATA + leafName;
     let continuePathParts = continuePath.split("/");
     for (let i = 0; i < continuePathParts.length; ++i) {
       continueFile.append(continuePathParts[i]);
     }
   }
   if (continueFile.exists()) {
-    throw new Error("The continue file should not exist, path: " +
-                    continueFile.path);
+    logTestInfo("The continue file should not exist, path: " +
+                continueFile.path);
+    continueFile.remove(false);
   }
   debugDump("Creating continue file, path: " + continueFile.path);
   continueFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, PERMS_FILE);
   // If for whatever reason the continue file hasn't been removed when a test
   // has finished remove it during cleanup so it doesn't affect tests that run
   // after the test that created it.
   registerCleanupFunction(() => {
     if (continueFile.exists()) {
@@ -253,17 +259,16 @@ async function setAppUpdateAutoEnabledHe
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateTest(updateParams, checkAttempts, steps) {
   return (async function() {
     gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     await SpecialPowers.pushPrefEnv({
       set: [
-        [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
         [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
         [PREF_APP_UPDATE_IDLETIME, 0],
         [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
       ],
     });
 
     await setupTestUpdater();
 
@@ -300,17 +305,16 @@ function runUpdateTest(updateParams, che
  * @return A promise which will resolve once all of the steps have been run
  *         and cleanup has been performed.
  */
 function runUpdateProcessingTest(updates, steps) {
   return (async function() {
     gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
     await SpecialPowers.pushPrefEnv({
       set: [
-        [PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS, 0],
         [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
         [PREF_APP_UPDATE_IDLETIME, 0],
         [PREF_APP_UPDATE_URL_MANUAL, URL_MANUAL_UPDATE],
       ],
     });
 
     await setupTestUpdater();
 
@@ -671,17 +675,16 @@ function runAboutDialogUpdateTest(update
       }
     })();
   }
 
   return (async function() {
     gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
     await SpecialPowers.pushPrefEnv({
       set: [
-        [PREF_APP_UPDATE_SERVICE_ENABLED, false],
         [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
         [PREF_APP_UPDATE_URL_MANUAL, detailsURL],
       ],
     });
 
     await setupTestUpdater();
 
     let updateURL = URL_HTTP_UPDATE_SJS + "?detailsURL=" + detailsURL +
@@ -800,17 +803,16 @@ function runAboutPrefsUpdateTest(updateP
       });
     })();
   }
 
   return (async function() {
     gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
     await SpecialPowers.pushPrefEnv({
       set: [
-        [PREF_APP_UPDATE_SERVICE_ENABLED, false],
         [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
         [PREF_APP_UPDATE_URL_MANUAL, detailsURL],
       ],
     });
 
     await setupTestUpdater();
 
     let updateURL = URL_HTTP_UPDATE_SJS + "?detailsURL=" + detailsURL +
@@ -836,8 +838,279 @@ function runAboutPrefsUpdateTest(updateP
       await BrowserTestUtils.removeTab(tab);
     });
 
     for (let step of steps) {
       await processAboutPrefsStep(step);
     }
   })();
 }
+
+
+/**
+ * Removes the modified update-settings.ini file so the updater will fail to
+ * stage an update.
+ */
+function removeUpdateSettingsIni() {
+  if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+    let greDir = getGREDir();
+    let updateSettingsIniBak = greDir.clone();
+    updateSettingsIniBak.append(FILE_UPDATE_SETTINGS_INI_BAK);
+    if (updateSettingsIniBak.exists()) {
+      let updateSettingsIni = greDir.clone();
+      updateSettingsIni.append(FILE_UPDATE_SETTINGS_INI);
+      updateSettingsIni.remove(false);
+    }
+  }
+}
+
+/**
+ * Runs a telemetry update test. This will set various common prefs for
+ * updating, checks for an update, and waits for the specified observer
+ * notification.
+ *
+ * @param  updateParams
+ *         Params which will be sent to app_update.sjs.
+ * @param  event
+ *         The observer notification to wait for before proceeding.
+ * @param  stageFailure (optional)
+ *         Whether to force a staging failure by removing the modified
+ *         update-settings.ini file.
+ * @return A promise which will resolve after the .
+ */
+function runTelemetryUpdateTest(updateParams, event, stageFailure = false) {
+  // Some elements append a trailing /. After the chrome tests are removed this
+  // code can be changed so URL_HOST already has a trailing /.
+  let detailsURL = URL_HOST + "/";
+  return (async function() {
+    Services.telemetry.clearScalars();
+    gEnv.set("MOZ_TEST_SLOW_SKIP_UPDATE_STAGE", "1");
+    await SpecialPowers.pushPrefEnv({
+      set: [
+        [PREF_APP_UPDATE_DISABLEDFORTESTING, false],
+      ],
+    });
+
+    await setupTestUpdater();
+
+    if (stageFailure) {
+      removeUpdateSettingsIni();
+    }
+
+    let updateURL = URL_HTTP_UPDATE_SJS + "?detailsURL=" + detailsURL +
+                    updateParams + getVersionParams();
+    setUpdateURL(updateURL);
+    if (Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+      // Since MOZ_TEST_SKIP_UPDATE_STAGE is checked before
+      // MOZ_TEST_SLOW_SKIP_UPDATE_STAGE in updater.cpp this removes the need
+      // for the continue file to continue staging the update.
+      gEnv.set("MOZ_TEST_SKIP_UPDATE_STAGE", "1");
+    }
+    gAUS.checkForBackgroundUpdates();
+    await waitForEvent(event);
+  })();
+}
+
+/**
+ * Gets an object with the expected update phase values that can be passed to
+ * checkTelemetryUpdatePhases for update phase telemetry tests.
+ *
+ * @param  overrides
+ *         Params which can override the default values.
+ * @return An object that can be passed to checkTelemetryUpdatePhases for update
+ *         phase telemetry tests.
+ */
+function getTelemetryUpdatePhaseValues(overrides) {
+  // Set values that could never be recorded due to values that would prevent
+  // them from occurring. This makes it so callers only have to specify a couple
+  // of values.
+  if (overrides.noPartialPatch) {
+    if (!overrides.noInternalPartial) {
+      overrides.noInternalPartial = true;
+    }
+  }
+
+  if (overrides.noCompletePatch) {
+    if (!overrides.noInternalComplete) {
+      overrides.noInternalComplete = true;
+    }
+  }
+
+  if (overrides.noPartialPatch || overrides.partialBadSize ||
+      overrides.noInternalPartial) {
+    if (!overrides.noStagePartial) {
+      overrides.noStagePartial = true;
+    }
+    if (!overrides.noApplyPartial) {
+      overrides.noApplyPartial = true;
+    }
+  }
+
+  if (overrides.noCompletePatch || overrides.completeBadSize ||
+      overrides.noInternalComplete) {
+    if (!overrides.noStageComplete) {
+      overrides.noStageComplete = true;
+    }
+    if (!overrides.noApplyComplete) {
+      overrides.noApplyComplete = true;
+    }
+  }
+
+  if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_STAGING_ENABLED)) {
+    if (!overrides.noStagePartial) {
+      overrides.noStagePartial = true;
+    }
+    if (!overrides.noStageComplete) {
+      overrides.noStageComplete = true;
+    }
+  }
+
+  let marSize = parseInt(SIZE_SIMPLE_MAR);
+  let partialSize =
+    overrides.partialBadSize ? parseInt(SIZE_SIMPLE_MAR + "1") : marSize;
+  let completeSize =
+    overrides.completeBadSize ? parseInt(SIZE_SIMPLE_MAR + "1") : marSize;
+
+  let partialDownloadBytes = overrides.partialBadSize ? 1 : marSize;
+  let completeDownloadBytes = overrides.completeBadSize ? 1 : marSize;
+
+  let obj = {};
+  obj.basePrefix =
+    overrides.forSession ? "update.session." : "update.startup.";
+  obj.from_app_version = Services.appinfo.version;
+
+  obj.mars = {};
+  obj.mars.mar_partial_size_bytes =
+    overrides.noPartialPatch ? null : partialSize;
+  obj.mars.mar_complete_size_bytes =
+    overrides.noCompletePatch ? null : completeSize;
+
+  obj.intervals = {};
+  obj.intervals.check = 1;
+  obj.intervals.download_bits_partial = null;
+  obj.intervals.download_bits_complete = null;
+  obj.intervals.download_internal_partial =
+    overrides.noInternalPartial ? null : 1;
+  obj.intervals.download_internal_complete =
+    overrides.noInternalComplete ? null : 1;
+  obj.intervals.stage_partial = overrides.noStagePartial ? null : 1;
+  obj.intervals.stage_complete = overrides.noStageComplete ? null : 1;
+  obj.intervals.apply_partial = overrides.noApplyPartial ? null : 1;
+  obj.intervals.apply_complete = overrides.noApplyComplete ? null : 1;
+
+  obj.downloads = {};
+  obj.downloads.bits_partial_ = {};
+  obj.downloads.bits_partial_.bytes = null;
+  obj.downloads.bits_partial_.seconds = null;
+  obj.downloads.bits_complete_ = {};
+  obj.downloads.bits_complete_.bytes = null;
+  obj.downloads.bits_complete_.seconds = null;
+  obj.downloads.internal_partial_ = {};
+  obj.downloads.internal_partial_.bytes =
+    overrides.noInternalPartial ? null : partialDownloadBytes;
+  obj.downloads.internal_partial_.seconds =
+    overrides.noInternalPartial ? null : 1;
+  obj.downloads.internal_complete_ = {};
+  obj.downloads.internal_complete_.bytes =
+    overrides.noInternalComplete ? null : completeDownloadBytes;
+  obj.downloads.internal_complete_.seconds =
+    overrides.noInternalComplete ? null : 1;
+
+  return obj;
+}
+
+/**
+ * Checks the telemetry values for app update phases under either update.startup
+ * or update.session based on the object passed to this function.
+ *
+ * @param  expected
+ *         An object containing the expected results to compare against the
+ *         actual results.
+ */
+function checkTelemetryUpdatePhases(expected) {
+  let scalars = TelemetryTestUtils.getProcessScalars("parent");
+  let basePrefix = expected.basePrefix;
+  let namePrefix = basePrefix;
+  {
+    let name = namePrefix + "from_app_version";
+    if (expected.from_app_version) {
+      Assert.ok(!!scalars[name],
+                "The " + name + " value should exist.");
+      Assert.equal(scalars[name],
+                   expected.from_app_version,
+                   "The " + name + " value should equal the expected value.");
+    } else {
+      Assert.ok(!scalars[name],
+                "The " + name + " value should not exist.");
+    }
+  }
+
+  for (let [nameSuffix, value] of Object.entries(expected.mars)) {
+    let name = namePrefix + nameSuffix;
+    if (value) {
+      Assert.ok(!!scalars[name],
+                "The " + name + " value should exist.");
+      Assert.equal(scalars[name], value,
+                   "The " + name + " value should equal the expected value.");
+    } else {
+      Assert.ok(!scalars[name],
+                "The " + name + " value should not exist.");
+    }
+  }
+
+  namePrefix = basePrefix + "intervals.";
+  for (let [suffix, value] of Object.entries(expected.intervals)) {
+    let name = namePrefix + suffix;
+    if (value) {
+      Assert.ok(!!scalars[name],
+                "The " + name + " value should exist.");
+      Assert.greaterOrEqual(scalars[name], value,
+                           "The " + name + " value should be equal to or " +
+                           "greater than " + value + ".");
+    } else {
+      Assert.ok(!scalars[name],
+                "The " + name + " value should not exist.");
+    }
+  }
+
+  namePrefix = basePrefix + "downloads.";
+  for (let [nameMid, values] of Object.entries(expected.downloads)) {
+    let name = namePrefix + nameMid + "bytes";
+    if (values.bytes) {
+      Assert.ok(!!scalars[name],
+                "The " + name + " value should exist.");
+      Assert.greaterOrEqual(scalars[name], values.bytes,
+                           "The " + name + " value should be equal to or " +
+                           "greater than " + values.bytes + ".");
+    } else {
+      Assert.ok(!scalars[name],
+                "The " + name + " value should not exist.");
+    }
+
+    name = namePrefix + nameMid + "seconds";
+    if (values.seconds) {
+      Assert.ok(!!scalars[name],
+              "The " + name + " value should exist.");
+      Assert.greaterOrEqual(scalars[name], values.seconds,
+                           "The " + name + " value should be equal to or " +
+                           "greater than " + values.seconds + ".");
+    } else {
+      Assert.ok(!scalars[name],
+              "The " + name + " value should not exist.");
+    }
+  }
+}
+
+/**
+ * Checks whether telemetry for update.startup or update.session is set by
+ * checking if there is a value for the from_app_version scalar.
+ *
+ * @param  isStartup
+ *         When true update.startup.from_app_version will be checked and when
+ *         false update.session.from_app_version will be checked.
+ */
+function checkTelemetryUpdatePhaseEmpty(isStartup) {
+  let scalars = TelemetryTestUtils.getProcessScalars("parent");
+  let name =
+    "update." + (isStartup ? "startup" : "session") + ".from_app_version";
+  Assert.ok(!scalars[name],
+            "The " + name + " value should not exist.");
+}
--- a/toolkit/mozapps/update/tests/data/shared.js
+++ b/toolkit/mozapps/update/tests/data/shared.js
@@ -5,46 +5,48 @@
 /* Shared code for xpcshell, mochitests-chrome, and mochitest-browser-chrome. */
 
 // Definitions needed to run eslint on this file.
 /* global AppConstants, DATA_URI_SPEC, LOG_FUNCTION */
 /* global Services, URL_HOST */
 
 const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+const {TelemetryTestUtils} =
+  ChromeUtils.import("resource://testing-common/TelemetryTestUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ctypes",
                                "resource://gre/modules/ctypes.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 
-const PREF_APP_UPDATE_AUTO                       = "app.update.auto";
-const PREF_APP_UPDATE_BACKGROUNDERRORS           = "app.update.backgroundErrors";
-const PREF_APP_UPDATE_BACKGROUNDMAXERRORS        = "app.update.backgroundMaxErrors";
-const PREF_APP_UPDATE_BITS_ENABLED               = "app.update.BITS.enabled";
-const PREF_APP_UPDATE_CANCELATIONS               = "app.update.cancelations";
-const PREF_APP_UPDATE_CHANNEL                    = "app.update.channel";
-const PREF_APP_UPDATE_DOORHANGER                 = "app.update.doorhanger";
-const PREF_APP_UPDATE_DOWNLOADPROMPTATTEMPTS     = "app.update.download.attempts";
-const PREF_APP_UPDATE_DOWNLOADPROMPT_MAXATTEMPTS = "app.update.download.maxAttempts";
-const PREF_APP_UPDATE_DISABLEDFORTESTING         = "app.update.disabledForTesting";
-const PREF_APP_UPDATE_IDLETIME                   = "app.update.idletime";
-const PREF_APP_UPDATE_INTERVAL                   = "app.update.interval";
-const PREF_APP_UPDATE_LASTUPDATETIME             = "app.update.lastUpdateTime.background-update-timer";
-const PREF_APP_UPDATE_LOG                        = "app.update.log";
-const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED        = "app.update.notifiedUnsupported";
-const PREF_APP_UPDATE_PROMPTWAITTIME             = "app.update.promptWaitTime";
-const PREF_APP_UPDATE_RETRYTIMEOUT               = "app.update.socket.retryTimeout";
-const PREF_APP_UPDATE_SERVICE_ENABLED            = "app.update.service.enabled";
-const PREF_APP_UPDATE_SILENT                     = "app.update.silent";
-const PREF_APP_UPDATE_SOCKET_MAXERRORS           = "app.update.socket.maxErrors";
-const PREF_APP_UPDATE_STAGING_ENABLED            = "app.update.staging.enabled";
-const PREF_APP_UPDATE_URL                        = "app.update.url";
-const PREF_APP_UPDATE_URL_DETAILS                = "app.update.url.details";
-const PREF_APP_UPDATE_URL_MANUAL                 = "app.update.url.manual";
+const PREF_APP_UPDATE_AUTO                 = "app.update.auto";
+const PREF_APP_UPDATE_BACKGROUNDERRORS     = "app.update.backgroundErrors";
+const PREF_APP_UPDATE_BACKGROUNDMAXERRORS  = "app.update.backgroundMaxErrors";
+const PREF_APP_UPDATE_BITS_ENABLED         = "app.update.BITS.enabled";
+const PREF_APP_UPDATE_CANCELATIONS         = "app.update.cancelations";
+const PREF_APP_UPDATE_CHANNEL              = "app.update.channel";
+const PREF_APP_UPDATE_DOORHANGER           = "app.update.doorhanger";
+const PREF_APP_UPDATE_DOWNLOAD_MAXATTEMPTS = "app.update.download.maxAttempts";
+const PREF_APP_UPDATE_DOWNLOAD_ATTEMPTS    = "app.update.download.attempts";
+const PREF_APP_UPDATE_DISABLEDFORTESTING   = "app.update.disabledForTesting";
+const PREF_APP_UPDATE_IDLETIME             = "app.update.idletime";
+const PREF_APP_UPDATE_INTERVAL             = "app.update.interval";
+const PREF_APP_UPDATE_LASTUPDATETIME       = "app.update.lastUpdateTime.background-update-timer";
+const PREF_APP_UPDATE_LOG                  = "app.update.log";
+const PREF_APP_UPDATE_NOTIFIEDUNSUPPORTED  = "app.update.notifiedUnsupported";
+const PREF_APP_UPDATE_PROMPTWAITTIME       = "app.update.promptWaitTime";
+const PREF_APP_UPDATE_RETRYTIMEOUT         = "app.update.socket.retryTimeout";
+const PREF_APP_UPDATE_SERVICE_ENABLED      = "app.update.service.enabled";
+const PREF_APP_UPDATE_SILENT               = "app.update.silent";
+const PREF_APP_UPDATE_SOCKET_MAXERRORS     = "app.update.socket.maxErrors";
+const PREF_APP_UPDATE_STAGING_ENABLED      = "app.update.staging.enabled";
+const PREF_APP_UPDATE_URL                  = "app.update.url";
+const PREF_APP_UPDATE_URL_DETAILS          = "app.update.url.details";
+const PREF_APP_UPDATE_URL_MANUAL           = "app.update.url.manual";
 
 const PREFBRANCH_APP_PARTNER         = "app.partner.";
 const PREF_DISTRIBUTION_ID           = "distribution.id";
 const PREF_DISTRIBUTION_VERSION      = "distribution.version";
 
 const CONFIG_APP_UPDATE_AUTO         = "app.update.auto";
 
 const NS_APP_PROFILE_DIR_STARTUP   = "ProfDS";