Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 18 Sep 2014 14:59:59 -0700
changeset 205951 d6f78faaefcc1dddf3a3f0e484bfa7dcb5997501
parent 205927 426497473505bccdad6e3b55c7cc464cc0d06ce6 (current diff)
parent 205950 26fede342996e0459ec99cb9b648fa256181fa42 (diff)
child 205996 c19df62989e1bf6831695302dbeec7fa2c1cdf46
push id27509
push userkwierso@gmail.com
push dateThu, 18 Sep 2014 22:00:06 +0000
treeherdermozilla-central@d6f78faaefcc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.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 fx-team to m-c a=merge
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -341,21 +341,21 @@
     <key id="goForwardKb" keycode="VK_RIGHT" command="Browser:Forward" modifiers="accel" />
 #endif
 #ifdef XP_UNIX
     <key id="goBackKb2" key="&goBackCmd.commandKey;" command="Browser:Back" modifiers="accel"/>
     <key id="goForwardKb2" key="&goForwardCmd.commandKey;" command="Browser:Forward" modifiers="accel"/>
 #endif
     <key id="goHome" keycode="VK_HOME" command="Browser:Home" modifiers="alt"/>
     <key keycode="VK_F5" command="Browser:Reload"/>
+    <key keycode="VK_F6" command="Browser:FocusNextFrame"/>
+    <key keycode="VK_F6" command="Browser:FocusNextFrame" modifiers="shift"/>
 #ifndef XP_MACOSX
     <key id="showAllHistoryKb" key="&showAllHistoryCmd.commandkey;" command="Browser:ShowAllHistory" modifiers="accel,shift"/>
     <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/>
-    <key keycode="VK_F6" command="Browser:FocusNextFrame"/>
-    <key keycode="VK_F6" command="Browser:FocusNextFrame" modifiers="shift"/>
     <key id="key_fullScreen" keycode="VK_F11" command="View:FullScreen"/>
 #else
     <key id="key_fullScreen" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,control"/>
     <key id="key_fullScreen_old" key="&fullScreenCmd.macCommandKey;" command="View:FullScreen" modifiers="accel,shift"/>
     <key keycode="VK_F11" command="View:FullScreen"/>
 #endif
     <key key="&reloadCmd.commandkey;" command="Browser:Reload" modifiers="accel" id="key_reload"/>
     <key key="&reloadCmd.commandkey;" command="Browser:ReloadSkipCache" modifiers="accel,shift"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7259,17 +7259,19 @@ var MousePosTracker = {
     }
   }
 };
 
 function focusNextFrame(event) {
   let fm = Services.focus;
   let dir = event.shiftKey ? fm.MOVEFOCUS_BACKWARDDOC : fm.MOVEFOCUS_FORWARDDOC;
   let element = fm.moveFocus(window, null, dir, fm.FLAG_BYKEY);
-  if (element.ownerDocument == document)
+  let panelOrNotificationSelector = "popupnotification " + element.localName + ", " +
+                                    "panel " + element.localName;
+  if (element.ownerDocument == document && !element.matches(panelOrNotificationSelector))
     focusAndSelectUrlBar();
 }
 
 function BrowserOpenNewTabOrWindow(event) {
   if (event.shiftKey) {
     OpenBrowserWindow();
   } else {
     BrowserOpenTab();
--- a/browser/base/content/searchSuggestionUI.js
+++ b/browser/base/content/searchSuggestionUI.js
@@ -91,46 +91,46 @@ SearchSuggestionUIController.prototype =
     // Update the table's rows, and the input when there is a selection.
     this._table.removeAttribute("aria-activedescendant");
     for (let i = 0; i < this._table.children.length; i++) {
       let row = this._table.children[i];
       if (i == idx) {
         row.classList.add("selected");
         row.firstChild.setAttribute("aria-selected", "true");
         this._table.setAttribute("aria-activedescendant", row.firstChild.id);
-        this.input.value = this.suggestionAtIndex(i);
       }
       else {
         row.classList.remove("selected");
         row.firstChild.setAttribute("aria-selected", "false");
       }
     }
-
-    // Update the input when there is no selection.
-    if (idx < 0) {
-      this.input.value = this._stickyInputValue;
-    }
   },
 
   get numSuggestions() {
     return this._table.children.length;
   },
 
+  selectAndUpdateInput: function (idx) {
+    this.selectedIndex = idx;
+    this.input.value = idx >= 0 ? this.suggestionAtIndex(idx) :
+                       this._stickyInputValue;
+  },
+
   suggestionAtIndex: function (idx) {
     let row = this._table.children[idx];
     return row ? row.textContent : null;
   },
 
   deleteSuggestionAtIndex: function (idx) {
     // Only form history suggestions can be deleted.
     if (this.isFormHistorySuggestionAtIndex(idx)) {
       let suggestionStr = this.suggestionAtIndex(idx);
       this._sendMsg("RemoveFormHistoryEntry", suggestionStr);
       this._table.children[idx].remove();
-      this.selectedIndex = -1;
+      this.selectAndUpdateInput(-1);
     }
   },
 
   isFormHistorySuggestionAtIndex: function (idx) {
     let row = this._table.children[idx];
     return row && row.classList.contains("formHistory");
   },
 
@@ -145,17 +145,17 @@ SearchSuggestionUIController.prototype =
   _onInput: function () {
     if (this.input.value) {
       this._getSuggestions();
     }
     else {
       this._stickyInputValue = "";
       this._hideSuggestions();
     }
-    this.selectedIndex = -1;
+    this.selectAndUpdateInput(-1);
   },
 
   _onKeypress: function (event) {
     let selectedIndexDelta = 0;
     switch (event.keyCode) {
     case event.DOM_VK_UP:
       if (this.numSuggestions) {
         selectedIndexDelta = -1;
@@ -203,31 +203,35 @@ SearchSuggestionUIController.prototype =
       // Update the selection.
       let newSelectedIndex = this.selectedIndex + selectedIndexDelta;
       if (newSelectedIndex < -1) {
         newSelectedIndex = this.numSuggestions - 1;
       }
       else if (this.numSuggestions <= newSelectedIndex) {
         newSelectedIndex = -1;
       }
-      this.selectedIndex = newSelectedIndex;
+      this.selectAndUpdateInput(newSelectedIndex);
 
       // Prevent the input's caret from moving.
       event.preventDefault();
     }
   },
 
   _onFocus: function () {
     this._speculativeConnect();
   },
 
   _onBlur: function () {
     this._hideSuggestions();
   },
 
+  _onMousemove: function (event) {
+    this.selectedIndex = this._indexOfTableRowOrDescendent(event.target);
+  },
+
   _onMousedown: function (event) {
     let idx = this._indexOfTableRowOrDescendent(event.target);
     let suggestion = this.suggestionAtIndex(idx);
     this._stickyInputValue = suggestion;
     this.input.value = suggestion;
     this.input.setAttribute("selection-index", idx);
     this.input.setAttribute("selection-kind", "mouse");
     this._hideSuggestions();
@@ -295,16 +299,17 @@ SearchSuggestionUIController.prototype =
     }
   },
 
   _makeTableRow: function (type, suggestionStr, currentRow, searchWords) {
     let row = document.createElementNS(HTML_NS, "tr");
     row.classList.add("searchSuggestionRow");
     row.classList.add(type);
     row.setAttribute("role", "presentation");
+    row.addEventListener("mousemove", this);
     row.addEventListener("mousedown", this);
 
     let entry = document.createElementNS(HTML_NS, "td");
     entry.classList.add("searchSuggestionEntry");
     entry.setAttribute("role", "option");
     entry.id = this._idPrefix + SUGGESTION_ID_PREFIX + currentRow;
     entry.setAttribute("aria-selected", "false");
 
@@ -338,17 +343,17 @@ SearchSuggestionUIController.prototype =
   },
 
   _hideSuggestions: function () {
     this.input.setAttribute("aria-expanded", "false");
     this._table.hidden = true;
     while (this._table.firstElementChild) {
       this._table.firstElementChild.remove();
     }
-    this.selectedIndex = -1;
+    this.selectAndUpdateInput(-1);
   },
 
   _indexOfTableRowOrDescendent: function (row) {
     while (row && row.localName != "tr") {
       row = row.parentNode;
     }
     if (!row) {
       throw new Error("Element is not a row");
--- a/browser/base/content/test/general/browser_mixedcontent_securityflags.js
+++ b/browser/base/content/test/general/browser_mixedcontent_securityflags.js
@@ -38,24 +38,37 @@ function blockMixedContentTest()
 function overrideMCB()
 {
   // test mixed content flags on load (reload)
   gTestBrowser.addEventListener("load", mixedContentOverrideTest, true);
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   ok(notification, "Mixed Content Doorhanger should appear");
   notification.reshow();
   ok(PopupNotifications.panel.firstChild.isMixedContentBlocked, "OK: Mixed Content is being blocked");
+
+  // Make sure the notification has no mixedblockdisabled attribute
+  ok(!PopupNotifications.panel.firstChild.hasAttribute("mixedblockdisabled"),
+    "Doorhanger must have no mixedblockdisabled attribute");
   // Click on the doorhanger to allow mixed content (and reload page)
   PopupNotifications.panel.firstChild.disableMixedContentProtection();
   notification.remove();
 }
 
 function mixedContentOverrideTest()
 {
   gTestBrowser.removeEventListener("load", mixedContentOverrideTest, true);
 
   is(gTestBrowser.docShell.hasMixedDisplayContentLoaded, true, "hasMixedDisplayContentLoaded flag has not been set");
   is(gTestBrowser.docShell.hasMixedActiveContentLoaded, true, "hasMixedActiveContentLoaded flag has not been set");
   is(gTestBrowser.docShell.hasMixedDisplayContentBlocked, false, "second hasMixedDisplayContentBlocked flag has been set");
   is(gTestBrowser.docShell.hasMixedActiveContentBlocked, false, "second hasMixedActiveContentBlocked flag has been set");
+
+  let notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
+  ok(notification, "Mixed Content Doorhanger should appear");
+  notification.reshow();
+
+  // Make sure the notification has the mixedblockdisabled attribute set to true
+  is(PopupNotifications.panel.firstChild.getAttribute("mixedblockdisabled"), "true",
+    "Doorhanger must have [mixedblockdisabled='true'] attribute");
+
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/base/content/test/general/browser_searchSuggestionUI.js
+++ b/browser/base/content/test/general/browser_searchSuggestionUI.js
@@ -97,16 +97,24 @@ add_task(rightArrowOrReturn("VK_RIGHT"))
 add_task(rightArrowOrReturn("VK_RETURN"));
 
 add_task(function* mouse() {
   yield setUp();
 
   let state = yield msg("key", { key: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
+  // Mouse over the first suggestion.
+  state = yield msg("mousemove", 0);
+  checkState(state, "x", ["xfoo", "xbar"], 0);
+
+  // Mouse over the second suggestion.
+  state = yield msg("mousemove", 1);
+  checkState(state, "x", ["xfoo", "xbar"], 1);
+
   // Click the second suggestion.  This should make it sticky.  To make sure it
   // sticks, trigger suggestions again and cycle through them by pressing Down
   // until nothing is selected again.
   state = yield msg("mousedown", 1);
   checkState(state, "xbar", [], -1);
 
   state = yield msg("key", { key: "VK_DOWN", waitForSuggestions: true });
   checkState(state, "xbar", ["xbarfoo", "xbarbar"], -1);
--- a/browser/base/content/test/general/browser_trackingUI.js
+++ b/browser/base/content/test/general/browser_trackingUI.js
@@ -63,29 +63,37 @@ function testTrackingPage(gTestBrowser)
   // Make sure the doorhanger appears
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present");
   notification.reshow();
   // Make sure the state of the doorhanger includes blocking tracking elements
   isnot(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
     "Tracking Content is being blocked");
 
+  // Make sure the notification has no trackingblockdisabled attribute
+  ok(!PopupNotifications.panel.firstChild.hasAttribute("trackingblockdisabled"),
+    "Doorhanger must have no trackingblockdisabled attribute");
+
   // Disable Tracking Content Protection for the page (which reloads the page)
   PopupNotifications.panel.firstChild.disableTrackingContentProtection();
 }
 
 function testTrackingPageWhitelisted(gTestBrowser)
 {
   // Make sure the doorhanger appears
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   isnot(notification, null, "Tracking Content Doorhanger did appear when protection was ON and tracking was present but white-listed");
   notification.reshow();
   // Make sure the state of the doorhanger does NOT include blocking tracking elements
   is(PopupNotifications.panel.firstChild.isTrackingContentBlocked, 0,
     "Tracking Content is NOT being blocked");
+
+  // Make sure the notification has the trackingblockdisabled attribute set to true
+  is(PopupNotifications.panel.firstChild.getAttribute("trackingblockdisabled"), "true",
+    "Doorhanger must have [trackingblockdisabled='true'] attribute");
 }
 
 function testTrackingPageOFF(gTestBrowser)
 {
   // Make sure the doorhanger does NOT appear
   var notification = PopupNotifications.getNotification("bad-content", gTestBrowser);
   is(notification, null, "Tracking Content Doorhanger did NOT appear when protection was OFF and tracking was present");
 }
--- a/browser/base/content/test/general/searchSuggestionUI.js
+++ b/browser/base/content/test/general/searchSuggestionUI.js
@@ -32,16 +32,43 @@ let messageHandlers = {
     ack();
   },
 
   blur: function () {
     gController.input.blur();
     ack();
   },
 
+  mousemove: function (suggestionIdx) {
+    // Copied from widget/tests/test_panel_mouse_coords.xul and
+    // browser/base/content/test/newtab/head.js
+    let row = gController._table.children[suggestionIdx];
+    let rect = row.getBoundingClientRect();
+    let left = content.mozInnerScreenX + rect.left;
+    let x = left + rect.width / 2;
+    let y = content.mozInnerScreenY + rect.top + rect.height / 2;
+
+    let utils = content.SpecialPowers.getDOMWindowUtils(content);
+    let scale = utils.screenPixelsPerCSSPixel;
+
+    let widgetToolkit = content.SpecialPowers.
+                        Cc["@mozilla.org/xre/app-info;1"].
+                        getService(content.SpecialPowers.Ci.nsIXULRuntime).
+                        widgetToolkit;
+    let nativeMsg = widgetToolkit == "cocoa" ? 5 : // NSMouseMoved
+                    widgetToolkit == "windows" ? 1 : // MOUSEEVENTF_MOVE
+                    3; // GDK_MOTION_NOTIFY
+
+    row.addEventListener("mousemove", function onMove() {
+      row.removeEventListener("mousemove", onMove);
+      ack();
+    });
+    utils.sendNativeMouseEvent(x * scale, y * scale, nativeMsg, 0, null);
+  },
+
   mousedown: function (suggestionIdx) {
     gController.onClick = () => {
       gController.onClick = null;
       ack();
     };
     let row = gController._table.children[suggestionIdx];
     content.sendMouseEvent({ type: "mousedown" }, row);
   },
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1623,17 +1623,17 @@
         </setter>
       </property>
     </implementation>
   </binding>
 
   <binding id="bad-content-notification" extends="chrome://global/content/bindings/notification.xml#popup-notification">
     <content>
       <xul:hbox align="start">
-        <xul:image class="popup-notification-icon" xbl:inherits="popupid"/>
+        <xul:image class="popup-notification-icon" xbl:inherits="popupid,mixedblockdisabled,trackingblockdisabled"/>
         <xul:vbox>
           <!-- header -->
           <xul:vbox>
             <xul:description anonid="badContentBlocked.title"
               class="popup-notification-item-title" xbl:inherits="popupid">
             </xul:description>
             <xul:description class="popup-notification-item-message"
               xbl:inherits="popupid">
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
+Cu.import("resource://services-common/utils.js");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/loop/MozLoopService.jsm");
 Cu.import("resource:///modules/loop/LoopContacts.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "hookWindowCloseForPanelClose",
                                         "resource://gre/modules/MozSocialAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
@@ -18,16 +19,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyGetter(this, "appInfo", function() {
   return Cc["@mozilla.org/xre/app-info;1"]
            .getService(Ci.nsIXULAppInfo)
            .QueryInterface(Ci.nsIXULRuntime);
 });
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                          "@mozilla.org/widget/clipboardhelper;1",
                                          "nsIClipboardHelper");
+XPCOMUtils.defineLazyServiceGetter(this, "extProtocolSvc",
+                                         "@mozilla.org/uriloader/external-protocol-service;1",
+                                         "nsIExternalProtocolService");
 this.EXPORTED_SYMBOLS = ["injectLoopAPI"];
 
 /**
  * Trying to clone an Error object into a different container will yield an error.
  * We can work around this by copying the properties we care about onto a regular
  * object.
  *
  * @param {Error}        error        Error object to copy
@@ -450,16 +454,46 @@ function injectLoopAPI(targetWindow) {
             channel: defaults.getCharPref("app.update.channel"),
             version: appInfo.version,
             OS: appInfo.OS
           }, targetWindow);
         }
         return appVersionInfo;
       }
     },
+
+    /**
+     * Composes an email via the external protocol service.
+     *
+     * @param {String} subject Subject of the email to send
+     * @param {String} body    Body message of the email to send
+     */
+    composeEmail: {
+      enumerable: true,
+      writable: true,
+      value: function(subject, body) {
+        let mailtoURL = "mailto:?subject=" + encodeURIComponent(subject) + "&" +
+                        "body=" + encodeURIComponent(body);
+        extProtocolSvc.loadURI(CommonUtils.makeURI(mailtoURL));
+      }
+    },
+
+    /**
+     * Adds a value to a telemetry histogram.
+     *
+     * @param  {string}  histogramId Name of the telemetry histogram to update.
+     * @param  {integer} value       Value to add to the histogram.
+     */
+    telemetryAdd: {
+      enumerable: true,
+      writable: true,
+      value: function(histogramId, value) {
+        Services.telemetry.getHistogramById(histogramId).add(value);
+      }
+    },
   };
 
   function onStatusChanged(aSubject, aTopic, aData) {
     let event = new targetWindow.CustomEvent("LoopStatusChanged");
     targetWindow.dispatchEvent(event)
   };
 
   function onDOMWindowDestroyed(aSubject, aTopic, aData) {
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -106,25 +106,32 @@ loop.Client = (function($) {
      *
      * @param  {string} nickname the nickname of the future caller
      * @param  {Function} cb Callback(err, callUrlData)
      */
     _requestCallUrlInternal: function(nickname, cb) {
       this.mozLoop.hawkRequest("/call-url/", "POST", {callerId: nickname},
                                function (error, responseText) {
         if (error) {
+          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
           this._failureHandler(cb, error);
           return;
         }
 
         try {
           var urlData = JSON.parse(responseText);
 
-          cb(null, this._validate(urlData, expectedCallUrlProperties));
+          // This throws if the data is invalid, in which case only the failure
+          // telementry will be recorded.
+          var returnData = this._validate(urlData, expectedCallUrlProperties);
+
+          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true);
+          cb(null, returnData);
         } catch (err) {
+          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
           console.log("Error requesting call info", err);
           cb(err);
         }
       }.bind(this));
     },
 
     /**
      * Block call URL based on the token identifier
@@ -182,12 +189,26 @@ loop.Client = (function($) {
         if (err) {
           cb(err);
           return;
         }
 
         this._requestCallUrlInternal(nickname, cb);
       }.bind(this));
     },
+
+    /**
+     * Adds a value to a telemetry histogram, ignoring errors.
+     *
+     * @param  {string}  histogramId Name of the telemetry histogram to update.
+     * @param  {integer} value       Value to add to the histogram.
+     */
+    _telemetryAdd: function(histogramId, value) {
+      try {
+        this.mozLoop.telemetryAdd(histogramId, value);
+      } catch (err) {
+        console.error("Error recording telemetry", err);
+      }
+    },
   };
 
   return Client;
 })(jQuery);
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -356,27 +356,21 @@ loop.panel = (function(_, mozL10n) {
         } catch(e) {
           console.log(e);
           this.props.notifications.errorL10n("unable_retrieve_url");
           this.setState(this.getInitialState());
         }
       }
     },
 
-    _generateMailTo: function() {
-      return encodeURI([
-        "mailto:?subject=" + __("share_email_subject3") + "&",
-        "body=" + __("share_email_body3", {callUrl: this.state.callUrl})
-      ].join(""));
-    },
-
     handleEmailButtonClick: function(event) {
       this.handleLinkExfiltration(event);
-      // Note: side effect
-      document.location = event.target.dataset.mailto;
+
+      navigator.mozLoop.composeEmail(__("share_email_subject3"),
+        __("share_email_body3", { callUrl: this.state.callUrl }));
     },
 
     handleCopyButtonClick: function(event) {
       this.handleLinkExfiltration(event);
       // XXX the mozLoop object should be passed as a prop, to ease testing and
       //     using a fake implementation in UI components showcase.
       navigator.mozLoop.copyString(this.state.callUrl);
       this.setState({copied: true});
@@ -404,18 +398,17 @@ loop.panel = (function(_, mozL10n) {
       return (
         PanelLayout({summary: __("share_link_header_text")}, 
           React.DOM.div({className: "invite"}, 
             React.DOM.input({type: "url", value: this.state.callUrl, readOnly: "true", 
                    onCopy: this.handleLinkExfiltration, 
                    className: inputCSSClass}), 
             React.DOM.p({className: "btn-group url-actions"}, 
               React.DOM.button({className: "btn btn-email", disabled: !this.state.callUrl, 
-                onClick: this.handleEmailButtonClick, 
-                'data-mailto': this._generateMailTo()}, 
+                onClick: this.handleEmailButtonClick}, 
                 __("share_button")
               ), 
               React.DOM.button({className: "btn btn-copy", disabled: !this.state.callUrl, 
                 onClick: this.handleCopyButtonClick}, 
                 this.state.copied ? __("copied_url_button") :
                                      __("copy_url_button")
               )
             )
@@ -525,25 +518,22 @@ loop.panel = (function(_, mozL10n) {
   /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
-    var client = new loop.Client({
-      baseServerUrl: navigator.mozLoop.serverUrl
-    });
+    var client = new loop.Client();
     var notifications = new sharedModels.NotificationCollection()
 
     React.renderComponent(PanelView({
       client: client, 
-      notifications: notifications}
-    ), document.querySelector("#main"));
+      notifications: notifications}), document.querySelector("#main"));
 
     document.body.classList.add(loop.shared.utils.getTargetPlatform());
     document.body.setAttribute("dir", mozL10n.getDirection());
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -356,27 +356,21 @@ loop.panel = (function(_, mozL10n) {
         } catch(e) {
           console.log(e);
           this.props.notifications.errorL10n("unable_retrieve_url");
           this.setState(this.getInitialState());
         }
       }
     },
 
-    _generateMailTo: function() {
-      return encodeURI([
-        "mailto:?subject=" + __("share_email_subject3") + "&",
-        "body=" + __("share_email_body3", {callUrl: this.state.callUrl})
-      ].join(""));
-    },
-
     handleEmailButtonClick: function(event) {
       this.handleLinkExfiltration(event);
-      // Note: side effect
-      document.location = event.target.dataset.mailto;
+
+      navigator.mozLoop.composeEmail(__("share_email_subject3"),
+        __("share_email_body3", { callUrl: this.state.callUrl }));
     },
 
     handleCopyButtonClick: function(event) {
       this.handleLinkExfiltration(event);
       // XXX the mozLoop object should be passed as a prop, to ease testing and
       //     using a fake implementation in UI components showcase.
       navigator.mozLoop.copyString(this.state.callUrl);
       this.setState({copied: true});
@@ -404,18 +398,17 @@ loop.panel = (function(_, mozL10n) {
       return (
         <PanelLayout summary={__("share_link_header_text")}>
           <div className="invite">
             <input type="url" value={this.state.callUrl} readOnly="true"
                    onCopy={this.handleLinkExfiltration}
                    className={inputCSSClass} />
             <p className="btn-group url-actions">
               <button className="btn btn-email" disabled={!this.state.callUrl}
-                onClick={this.handleEmailButtonClick}
-                data-mailto={this._generateMailTo()}>
+                onClick={this.handleEmailButtonClick}>
                 {__("share_button")}
               </button>
               <button className="btn btn-copy" disabled={!this.state.callUrl}
                 onClick={this.handleCopyButtonClick}>
                 {this.state.copied ? __("copied_url_button") :
                                      __("copy_url_button")}
               </button>
             </p>
@@ -525,25 +518,22 @@ loop.panel = (function(_, mozL10n) {
   /**
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
-    var client = new loop.Client({
-      baseServerUrl: navigator.mozLoop.serverUrl
-    });
+    var client = new loop.Client();
     var notifications = new sharedModels.NotificationCollection()
 
     React.renderComponent(<PanelView
       client={client}
-      notifications={notifications}
-    />, document.querySelector("#main"));
+      notifications={notifications} />, document.querySelector("#main"));
 
     document.body.classList.add(loop.shared.utils.getTargetPlatform());
     document.body.setAttribute("dir", mozL10n.getDirection());
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
--- a/browser/components/loop/test/desktop-local/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -29,17 +29,18 @@ describe("loop.Client", function() {
     fakeToken = "fakeTokenText";
     mozLoop = {
       getLoopCharPref: sandbox.stub()
         .returns(null)
         .withArgs("hawk-session-token")
         .returns(fakeToken),
       ensureRegistered: sinon.stub().callsArgWith(0, null),
       noteCallUrlExpiry: sinon.spy(),
-      hawkRequest: sinon.stub()
+      hawkRequest: sinon.stub(),
+      telemetryAdd: sinon.spy(),
     };
     // Alias for clearer tests.
     hawkRequestStub = mozLoop.hawkRequest;
     client = new loop.Client({
       mozLoop: mozLoop
     });
   });
 
@@ -151,16 +152,40 @@ describe("loop.Client", function() {
           hawkRequestStub.callsArgWith(3, null,
             JSON.stringify(callUrlData));
 
           client.requestCallUrl("foo", callback);
 
           sinon.assert.notCalled(mozLoop.noteCallUrlExpiry);
         });
 
+      it("should call mozLoop.telemetryAdd when the request succeeds",
+        function(done) {
+          var callUrlData = {
+            "callUrl": "fakeCallUrl",
+            "expiresAt": 60
+          };
+
+          // Sets up the hawkRequest stub to trigger the callback with no error
+          // and the url.
+          hawkRequestStub.callsArgWith(3, null,
+            JSON.stringify(callUrlData));
+
+          client.requestCallUrl("foo", function(err) {
+            expect(err).to.be.null;
+
+            sinon.assert.calledOnce(mozLoop.telemetryAdd);
+            sinon.assert.calledWith(mozLoop.telemetryAdd,
+                                    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
+                                    true);
+
+            done();
+          });
+        });
+
       it("should send an error when the request fails", function() {
         // Sets up the hawkRequest stub to trigger the callback with
         // an error
         hawkRequestStub.callsArgWith(3, fakeErrorRes);
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
@@ -176,11 +201,29 @@ describe("loop.Client", function() {
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /Invalid data received/.test(err.message);
         }));
       });
+
+      it("should call mozLoop.telemetryAdd when the request fails",
+        function(done) {
+          // Sets up the hawkRequest stub to trigger the callback with
+          // an error
+          hawkRequestStub.callsArgWith(3, fakeErrorRes);
+
+          client.requestCallUrl("foo", function(err) {
+            expect(err).not.to.be.null;
+
+            sinon.assert.calledOnce(mozLoop.telemetryAdd);
+            sinon.assert.calledWith(mozLoop.telemetryAdd,
+                                    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
+                                    false);
+
+            done();
+          });
+        });
     });
   });
 });
--- a/browser/components/loop/test/desktop-local/conversation_test.js
+++ b/browser/components/loop/test/desktop-local/conversation_test.js
@@ -15,19 +15,16 @@ describe("loop.conversation", function()
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers();
     notifications = new loop.shared.models.NotificationCollection();
 
     navigator.mozLoop = {
       doNotDisturb: true,
-      get serverUrl() {
-        return "http://example.com";
-      },
       getStrings: function() {
         return JSON.stringify({textContent: "fakeText"});
       },
       get locale() {
         return "en-US";
       },
       setLoopCharPref: sandbox.stub(),
       getLoopCharPref: sandbox.stub(),
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -27,29 +27,27 @@ describe("loop.panel", function() {
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
     };
     notifications = new loop.shared.models.NotificationCollection();
 
     navigator.mozLoop = {
       doNotDisturb: true,
-      get serverUrl() {
-        return "http://example.com";
-      },
       getStrings: function() {
         return JSON.stringify({textContent: "fakeText"});
       },
       get locale() {
         return "en-US";
       },
       setLoopCharPref: sandbox.stub(),
       getLoopCharPref: sandbox.stub().returns("unseen"),
       copyString: sandbox.stub(),
-      noteCallUrlExpiry: sinon.spy()
+      noteCallUrlExpiry: sinon.spy(),
+      composeEmail: sinon.spy()
     };
 
     document.mozL10n.initialize(navigator.mozLoop);
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     sandbox.restore();
@@ -338,26 +336,25 @@ describe("loop.panel", function() {
       });
 
       it("should reset all pending notifications", function() {
         sinon.assert.calledOnce(view.props.notifications.reset);
       });
 
       it("should display a share button for email", function() {
         fakeClient.requestCallUrl = sandbox.stub();
-        var mailto = 'mailto:?subject=email-subject&body=http://example.com';
         var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
           notifications: notifications,
           client: fakeClient
         }));
         view.setState({pending: false, callUrl: "http://example.com"});
 
         TestUtils.findRenderedDOMComponentWithClass(view, "btn-email");
-        expect(view.getDOMNode().querySelector(".btn-email").dataset.mailto)
-              .to.equal(encodeURI(mailto));
+        TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-email"));
+        sinon.assert.calledOnce(navigator.mozLoop.composeEmail);
       });
 
       it("should feature a copy button capable of copying the call url when clicked", function() {
         fakeClient.requestCallUrl = sandbox.stub();
         var view = TestUtils.renderIntoDocument(loop.panel.CallUrlResult({
           notifications: notifications,
           client: fakeClient
         }));
@@ -403,17 +400,16 @@ describe("loop.panel", function() {
           }));
           view.setState({
             pending: false,
             copied: false,
             callUrl: "http://example.com",
             callUrlExpiry: 6000
           });
 
-          view.getDOMNode().querySelector(".btn-email").dataset.mailto = "#";
           TestUtils.Simulate.click(view.getDOMNode().querySelector(".btn-email"));
 
           sinon.assert.calledOnce(navigator.mozLoop.noteCallUrlExpiry);
           sinon.assert.calledWithExactly(navigator.mozLoop.noteCallUrlExpiry,
             6000);
         });
 
       it("should note the call url expiry when the url is copied manually",
--- a/browser/components/loop/test/mochitest/browser.ini
+++ b/browser/components/loop/test/mochitest/browser.ini
@@ -11,8 +11,10 @@ skip-if = e10s
 [browser_LoopContacts.js]
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_prefs.js]
 [browser_mozLoop_doNotDisturb.js]
 [browser_mozLoop_softStart.js]
 skip-if = buildapp == 'mulet'
 [browser_toolbarbutton.js]
 [browser_mozLoop_pluralStrings.js]
+[browser_mozLoop_telemetry.js]
+skip-if = e10s
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_telemetry.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/*
+ * This file contains tests for the mozLoop telemetry API.
+ */
+
+add_task(loadLoopPanel);
+
+/**
+ * Tests that boolean histograms exist and can be updated.
+ */
+add_task(function* test_mozLoop_telemetryAdd_boolean() {
+  for (let histogramId of [
+    "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
+  ]) {
+    let snapshot = Services.telemetry.getHistogramById(histogramId).snapshot();
+
+    let initialFalseCount = snapshot.counts[0];
+    let initialTrueCount = snapshot.counts[1];
+
+    for (let value of [false, false, true]) {
+      gMozLoopAPI.telemetryAdd(histogramId, value);
+    }
+
+    // The telemetry service updates histograms asynchronously, so we need to
+    // poll for the final values and time out otherwise.
+    info("Waiting for update of " + histogramId);
+    do {
+      yield new Promise(resolve => setTimeout(resolve, 50));
+      snapshot = Services.telemetry.getHistogramById(histogramId).snapshot();
+    } while (snapshot.counts[0] == initialFalseCount + 2 &&
+             snapshot.counts[1] == initialTrueCount + 1);
+    ok(true, "Correctly updated " + histogramId);
+  }
+});
--- a/browser/components/loop/test/shared/models_test.js
+++ b/browser/components/loop/test/shared/models_test.js
@@ -51,31 +51,23 @@ describe("loop.shared.models", function(
       it("should require a sdk option", function() {
         expect(function() {
           new sharedModels.ConversationModel({}, {});
         }).to.Throw(Error, /missing required sdk/);
       });
     });
 
     describe("constructed", function() {
-      var conversation, fakeClient, fakeBaseServerUrl,
-          requestCallInfoStub, requestCallsInfoStub;
+      var conversation;
 
       beforeEach(function() {
         conversation = new sharedModels.ConversationModel({}, {
           sdk: fakeSDK
         });
         conversation.set("loopToken", "fakeToken");
-        fakeBaseServerUrl = "http://fakeBaseServerUrl";
-        fakeClient = {
-          requestCallInfo: sandbox.stub(),
-          requestCallsInfo: sandbox.stub()
-        };
-        requestCallInfoStub = fakeClient.requestCallInfo;
-        requestCallsInfoStub = fakeClient.requestCallsInfo;
       });
 
       describe("#incoming", function() {
         it("should trigger a `call:incoming` event", function(done) {
           conversation.once("call:incoming", function() {
             done();
           });
 
--- a/browser/components/loop/test/xpcshell/head.js
+++ b/browser/components/loop/test/xpcshell/head.js
@@ -21,16 +21,17 @@ const kServerPushUrl = "http://localhost
 const kEndPointUrl = "http://example.com/fake";
 const kUAID = "f47ac11b-58ca-4372-9567-0e02b2c3d479";
 
 // Fake loop server
 var loopServer;
 
 // Ensure loop is always enabled for tests
 Services.prefs.setBoolPref("loop.enabled", true);
+Services.prefs.setBoolPref("loop.throttled", false);
 
 function setupFakeLoopServer() {
   loopServer = new HttpServer();
   loopServer.start(-1);
 
   Services.prefs.setCharPref("services.push.serverURL", kServerPushUrl);
 
   Services.prefs.setCharPref("loop.server",
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -123,31 +123,31 @@
               React.DOM.strong(null, "Note:"), " 332px wide."
             ), 
             Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications, 
                          callUrl: "http://invalid.example.url/"})
             ), 
             Example({summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications, 
-                         callUrl: "http://invalid.example.url/",
+                         callUrl: "http://invalid.example.url/", 
                          userProfile: {email: "test@example.com"}})
             ), 
             Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications})
             ), 
             Example({summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}}, 
-              PanelView({client: mockClient, notifications: notifications,
+              PanelView({client: mockClient, notifications: notifications, 
                          userProfile: {email: "test@example.com"}})
             ), 
             Example({summary: "Error Notification", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: errNotifications})
-            ),
+            ), 
             Example({summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}}, 
-              PanelView({client: mockClient, notifications: errNotifications,
+              PanelView({client: mockClient, notifications: errNotifications, 
                          userProfile: {email: "test@example.com"}})
             )
           ), 
 
           Section({name: "IncomingCallView"}, 
             Example({summary: "Default / incoming video call", dashed: "true", style: {width: "280px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 IncomingCallView({model: mockConversationModel, 
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -262,16 +262,17 @@ skip-if = os == "linux" || e10s # Bug 88
 [browser_dbg_terminate-on-tab-close.js]
 [browser_dbg_tracing-01.js]
 [browser_dbg_tracing-02.js]
 [browser_dbg_tracing-03.js]
 [browser_dbg_tracing-04.js]
 [browser_dbg_tracing-05.js]
 [browser_dbg_tracing-06.js]
 [browser_dbg_tracing-07.js]
+[browser_dbg_tracing-08.js]
 [browser_dbg_variables-view-01.js]
 [browser_dbg_variables-view-02.js]
 [browser_dbg_variables-view-03.js]
 [browser_dbg_variables-view-04.js]
 [browser_dbg_variables-view-05.js]
 [browser_dbg_variables-view-accessibility.js]
 [browser_dbg_variables-view-data.js]
 [browser_dbg_variables-view-edit-cancel.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/debugger/test/browser_dbg_tracing-08.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that tracing about:config doesn't produce errors.
+ */
+
+const TAB_URL = "about:config";
+
+let gPanel, gDoneChecks;
+
+function test() {
+  gDoneChecks = promise.defer();
+  const tracerPref = promise.defer();
+  const configPref = promise.defer();
+  SpecialPowers.pushPrefEnv({'set': [["devtools.debugger.tracer", true]]}, tracerPref.resolve);
+  SpecialPowers.pushPrefEnv({'set': [["general.warnOnAboutConfig", false]]}, configPref.resolve);
+  promise.all([tracerPref.promise, configPref.promise]).then(() => {
+    initDebugger(TAB_URL).then(([,, aPanel]) => {
+      gPanel = aPanel;
+      gPanel.panelWin.gClient.addOneTimeListener("traces", testTraceLogs);
+    }).then(() => startTracing(gPanel))
+      .then(generateTrace)
+      .then(() => waitForClientEvents(gPanel, "traces"))
+      .then(() => gDoneChecks.promise)
+      .then(() => stopTracing(gPanel))
+      .then(resetPreferences)
+      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(null, aError => {
+        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
+      });
+  });
+}
+
+function testTraceLogs(name, packet) {
+  info("Traces: " + packet.traces.length);
+  ok(packet.traces.length > 0, "Got some traces.");
+  ok(packet.traces.every(t => t.type != "enteredFrame" || !!t.location),
+     "All enteredFrame traces contain location.");
+  gDoneChecks.resolve();
+}
+
+function generateTrace(name, packet) {
+  // Interact with the page to cause JS execution.
+  let search = content.document.getElementById("textbox");
+  info("Interacting with the page.");
+  search.value = "devtools";
+}
+
+function resetPreferences() {
+  const deferred = promise.defer();
+  SpecialPowers.popPrefEnv(() => SpecialPowers.popPrefEnv(deferred.resolve));
+  return deferred.promise;
+}
+
+registerCleanupFunction(function() {
+  gPanel = null;
+  gDoneChecks = null;
+});
--- a/browser/devtools/styleinspector/test/browser_ruleview_livepreview.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_livepreview.js
@@ -23,28 +23,28 @@ const TEST_DATA = [
 ];
 
 let test = asyncTest(function*() {
   yield addTab("data:text/html;charset=utf-8,test rule view live preview on user changes");
 
   let style = '#testid {display:block;}';
   let styleNode = addStyle(content.document, style);
   content.document.body.innerHTML = '<div id="testid">Styled Node</div><span>inline element</span>';
-  let testElement = getNode("#testid");
 
   let {toolbox, inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
   for (let data of TEST_DATA) {
-    yield testLivePreviewData(data, view, testElement);
+    yield testLivePreviewData(data, view, "#testid");
   }
 });
 
 
-function* testLivePreviewData(data, ruleView, testElement) {
+function* testLivePreviewData(data, ruleView, selector) {
+  let testElement = getNode(selector);
   let idRuleEditor = getRuleViewRuleEditor(ruleView, 1);
   let propEditor = idRuleEditor.rule.textProps[0].editor;
 
   info("Focusing the property value inplace-editor");
   let editor = yield focusEditableField(propEditor.valueSpan);
   is(inplaceEditor(propEditor.valueSpan), editor, "The focused editor is the value");
 
   info("Enter a value in the editor")
@@ -52,15 +52,18 @@ function* testLivePreviewData(data, rule
     EventUtils.sendChar(ch, ruleView.doc.defaultView);
   }
   if (data.escape) {
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   } else {
     EventUtils.synthesizeKey("VK_RETURN", {});
   }
 
+  // This wait is an orange waiting to happen, but it might take a few event
+  // loop spins in either the client or parent process before we see the
+  // updated value.
   yield wait(1);
 
   // While the editor is still focused in, the display should have changed already
-  is(content.getComputedStyle(testElement).display,
+  is((yield getComputedStyleProperty(selector, null, "display")),
     data.expected,
     "Element should be previewed as " + data.expected);
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_pseudo-element.js
@@ -16,21 +16,22 @@ let test = asyncTest(function*() {
   yield testTopRight(inspector, view);
   yield testBottomRight(inspector, view);
   yield testBottomLeft(inspector, view);
   yield testParagraph(inspector, view);
   yield testBody(inspector, view);
 });
 
 function* testTopLeft(inspector, view) {
+  let selector = "#topleft";
   let {
     rules,
     element,
     elementStyle
-  } = yield assertPseudoElementRulesNumbers("#topleft", inspector, view, {
+  } = yield assertPseudoElementRulesNumbers(selector, inspector, view, {
     elementRulesNb: 4,
     afterRulesNb: 1,
     beforeRulesNb: 2,
     firstLineRulesNb: 0,
     firstLetterRulesNb: 0,
     selectionRulesNb: 0
   });
 
@@ -82,45 +83,45 @@ function* testTopLeft(inspector, view) {
 
   is (firstProp, elementAfterRule.textProps[elementAfterRule.textProps.length - 2],
       "First added property is on back of array");
   is (secondProp, elementAfterRule.textProps[elementAfterRule.textProps.length - 1],
       "Second added property is on back of array");
 
   yield elementAfterRule._applyingModifications;
 
-  is(defaultView.getComputedStyle(element, ":after").getPropertyValue("background-color"),
+  is((yield getComputedStyleProperty(selector, ":after", "background-color")),
     "rgb(0, 255, 0)", "Added property should have been used.");
-  is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"),
+  is((yield getComputedStyleProperty(selector, ":after", "padding-top")),
     "100px", "Added property should have been used.");
-  is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"),
+  is((yield getComputedStyleProperty(selector, null, "padding-top")),
     "32px", "Added property should not apply to element");
 
   secondProp.setEnabled(false);
   yield elementAfterRule._applyingModifications;
 
-  is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"), "0px",
+  is((yield getComputedStyleProperty(selector, ":after", "padding-top")), "0px",
     "Disabled property should have been used.");
-  is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"), "32px",
+  is((yield getComputedStyleProperty(selector, null, "padding-top")), "32px",
     "Added property should not apply to element");
 
   secondProp.setEnabled(true);
   yield elementAfterRule._applyingModifications;
 
-  is(defaultView.getComputedStyle(element, ":after").getPropertyValue("padding-top"), "100px",
+  is((yield getComputedStyleProperty(selector, ":after", "padding-top")), "100px",
     "Enabled property should have been used.");
-  is(defaultView.getComputedStyle(element).getPropertyValue("padding-top"), "32px",
+  is((yield getComputedStyleProperty(selector, null, "padding-top")), "32px",
     "Added property should not apply to element");
 
   firstProp = elementRuleView.addProperty("background-color", "rgb(0, 0, 255)", "");
   yield elementRule._applyingModifications;
 
-  is(defaultView.getComputedStyle(element).getPropertyValue("background-color"), "rgb(0, 0, 255)",
+  is((yield getComputedStyleProperty(selector, null, "background-color")), "rgb(0, 0, 255)",
     "Added property should have been used.");
-  is(defaultView.getComputedStyle(element, ":after").getPropertyValue("background-color"), "rgb(0, 255, 0)",
+  is((yield getComputedStyleProperty(selector, ":after", "background-color")), "rgb(0, 255, 0)",
     "Added prop does not apply to pseudo");
 }
 
 function* testTopRight(inspector, view) {
   let {
     rules,
     element,
     elementStyle
--- a/browser/devtools/styleinspector/test/doc_frame_script.js
+++ b/browser/devtools/styleinspector/test/doc_frame_script.js
@@ -68,9 +68,24 @@ addMessageListener("Test:GetStyleSheetsI
       href: sheet.href,
       isContentSheet: CssLogic.isContentStylesheet(sheet)
     });
   }
 
   sendAsyncMessage("Test:GetStyleSheetsInfoForNode", sheets);
 });
 
+/**
+ * Get the property value from the computed style for an element.
+ * @param {Object} data Expects a data object with the following properties
+ * - {String} selector: The selector used to obtain the element.
+ * - {String} pseudo: pseudo id to query, or null.
+ * - {String} name: name of the property
+ * @return {String} The value, if found, null otherwise
+ */
+addMessageListener("Test:GetComputedStylePropertyValue", function(msg) {
+  let {selector, pseudo, name} = msg.data;
+  let element = content.document.querySelector(selector);
+  let value = content.document.defaultView.getComputedStyle(element, pseudo).getPropertyValue(name);
+  sendAsyncMessage("Test:GetComputedStylePropertyValue", value);
+});
+
 let dumpn = msg => dump(msg + "\n");
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -381,16 +381,30 @@ function executeInContent(name, data={},
   if (expectResponse) {
     return waitForContentMessage(name);
   } else {
     return promise.resolve();
   }
 }
 
 /**
+ * Send an async message to the frame script and get back the requested
+ * computed style property.
+ * @param {String} selector: The selector used to obtain the element.
+ * @param {String} pseudo: pseudo id to query, or null.
+ * @param {String} name: name of the property.
+ */
+function* getComputedStyleProperty(selector, pseudo, propName) {
+ return yield executeInContent("Test:GetComputedStylePropertyValue",
+                               {selector: selector,
+                                pseudo: pseudo,
+                                name: propName});
+}
+
+/**
  * Given an inplace editable element, click to switch it to edit mode, wait for
  * focus
  * @return a promise that resolves to the inplace-editor element when ready
  */
 let focusEditableField = Task.async(function*(editable, xOffset=1, yOffset=1, options={}) {
   let onFocus = once(editable.parentNode, "focus", true);
 
   info("Clicking on editable field to turn to edit mode");
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -1129,16 +1129,23 @@ this.UITour = {
       case "availableTargets":
         this.getAvailableTargets(aContentDocument, aCallbackID);
         break;
       case "sync":
         this.sendPageCallback(aContentDocument, aCallbackID, {
           setup: Services.prefs.prefHasUserValue("services.sync.username"),
         });
         break;
+      case "appinfo":
+        let props = ["defaultUpdateChannel", "distributionID", "isOfficialBranding",
+                     "isReleaseBuild", "name", "vendor", "version"];
+        let appinfo = {};
+        props.forEach(property => appinfo[property] = Services.appinfo[property]);
+        this.sendPageCallback(aContentDocument, aCallbackID, appinfo);
+        break;
       default:
         Cu.reportError("getConfiguration: Unknown configuration requested: " + aConfiguration);
         break;
     }
   },
 
   getAvailableTargets: function(aContentDocument, aCallbackID) {
     let window = this.getChromeWindow(aContentDocument);
--- a/browser/modules/test/browser_UITour.js
+++ b/browser/modules/test/browser_UITour.js
@@ -260,15 +260,28 @@ let tests = [
         is(desc.textContent, "search text", "Popup should have correct description text");
 
         done();
       });
     });
 
     gContentAPI.showInfo("urlbar", "urlbar title", "urlbar text");
   },
+  function test_getConfigurationVersion(done) {
+    function callback(result) {
+      let props = ["defaultUpdateChannel", "distributionID", "isOfficialBranding",
+                   "isReleaseBuild", "name", "vendor", "version"];
+      for (let property of props) {
+        ok(typeof(result[property]) !== undefined, "Check " + property + " isn't undefined.");
+        is(result[property], Services.appinfo[property], "Should have the same " + property + " property.");
+      }
+      done();
+    }
+
+    gContentAPI.getConfiguration("appinfo", callback);
+  },
 
   // Make sure this test is last in the file so the appMenu gets left open and done will confirm it got tore down.
   function cleanupMenus(done) {
     gContentAPI.showMenu("appMenu");
     done();
   },
 ];
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..54eb9f365f75d3870858cd7d7ac3544c176a642e
GIT binary patch
literal 3209
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
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1179,16 +1179,21 @@ toolbarbutton[sdk-button="true"][cui-are
 .popup-notification-icon[popupid="webapps-install"] {
   list-style-image: url(chrome://global/skin/icons/webapps-64.png);
 }
 
 .popup-notification-icon[popupid="bad-content"] {
   list-style-image: url(chrome://browser/skin/bad-content-blocked-64.png);
 }
 
+.popup-notification-icon[popupid="bad-content"][mixedblockdisabled],
+.popup-notification-icon[popupid="bad-content"][trackingblockdisabled] {
+  list-style-image: url(chrome://browser/skin/bad-content-unblocked-64.png);
+}
+
 .popup-notification-icon[popupid="webRTC-sharingDevices"],
 .popup-notification-icon[popupid="webRTC-shareDevices"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
 }
 
 .popup-notification-icon[popupid="webRTC-sharingMicrophone"],
 .popup-notification-icon[popupid="webRTC-shareMicrophone"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -41,16 +41,17 @@ browser.jar:
   skin/classic/browser/menuPanel.png
   skin/classic/browser/menuPanel-customize.png
   skin/classic/browser/menuPanel-exit.png
   skin/classic/browser/menuPanel-help.png
   skin/classic/browser/menuPanel-small.png
   skin/classic/browser/bad-content-blocked-16.png
   skin/classic/browser/bad-content-blocked-64.png
   skin/classic/browser/bad-content-unblocked-16.png
+  skin/classic/browser/bad-content-unblocked-64.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/notification-16.png
   skin/classic/browser/notification-64.png
 * skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/pluginInstall-16.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..54eb9f365f75d3870858cd7d7ac3544c176a642e
GIT binary patch
literal 3209
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
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e147189c4b3df0ecb0d3cb7914e1f69b17f16050
GIT binary patch
literal 7488
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
KMNUMnLSTZrcN47u
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -4035,16 +4035,27 @@ menulist.translate-infobar-element > .me
   list-style-image: url(chrome://browser/skin/bad-content-blocked-64.png);
 }
 @media (min-resolution: 2dppx) {
   .popup-notification-icon[popupid="bad-content"] {
     list-style-image: url(chrome://browser/skin/bad-content-blocked-64@2x.png);
   }
 }
 
+.popup-notification-icon[popupid="bad-content"][mixedblockdisabled],
+.popup-notification-icon[popupid="bad-content"][trackingblockdisabled] {
+  list-style-image: url(chrome://browser/skin/bad-content-unblocked-64.png);
+}
+@media (min-resolution: 2dppx) {
+  .popup-notification-icon[popupid="bad-content"][mixedblockdisabled],
+  .popup-notification-icon[popupid="bad-content"][trackingblockdisabled] {
+    list-style-image: url(chrome://browser/skin/bad-content-unblocked-64@2x.png);
+  }
+}
+
 .popup-notification-icon[popupid="pointerLock"] {
   list-style-image: url(chrome://browser/skin/pointerLock-64.png);
 }
 @media (min-resolution: 2dppx) {
   .popup-notification-icon[popupid="pointerLock"] {
     list-style-image: url(chrome://browser/skin/pointerLock-64@2x.png);
   }
 }
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -67,16 +67,18 @@ browser.jar:
   skin/classic/browser/menuPanel-small.png
   skin/classic/browser/menuPanel-small@2x.png
   skin/classic/browser/bad-content-blocked-16.png
   skin/classic/browser/bad-content-blocked-16@2x.png
   skin/classic/browser/bad-content-blocked-64.png
   skin/classic/browser/bad-content-blocked-64@2x.png
   skin/classic/browser/bad-content-unblocked-16.png
   skin/classic/browser/bad-content-unblocked-16@2x.png
+  skin/classic/browser/bad-content-unblocked-64.png
+  skin/classic/browser/bad-content-unblocked-64@2x.png
   skin/classic/browser/panel-expander-closed.png
   skin/classic/browser/panel-expander-closed@2x.png
   skin/classic/browser/panel-expander-open.png
   skin/classic/browser/panel-expander-open@2x.png
   skin/classic/browser/panel-plus-sign.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/page-livemarks@2x.png
   skin/classic/browser/pageInfo.css
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..54eb9f365f75d3870858cd7d7ac3544c176a642e
GIT binary patch
literal 3209
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
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2197,16 +2197,21 @@ toolbarbutton.bookmark-item[dragover="tr
 .popup-notification-icon[popupid="webapps-install"] {
   list-style-image: url(chrome://global/skin/icons/webapps-64.png);
 }
 
 .popup-notification-icon[popupid="bad-content"] {
   list-style-image: url(chrome://browser/skin/bad-content-blocked-64.png);
 }
 
+.popup-notification-icon[popupid="bad-content"][mixedblockdisabled],
+.popup-notification-icon[popupid="bad-content"][trackingblockdisabled] {
+  list-style-image: url(chrome://browser/skin/bad-content-unblocked-64.png);
+}
+
 .popup-notification-icon[popupid="webRTC-sharingDevices"],
 .popup-notification-icon[popupid="webRTC-shareDevices"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareDevice-64.png);
 }
 
 .popup-notification-icon[popupid="webRTC-sharingMicrophone"],
 .popup-notification-icon[popupid="webRTC-shareMicrophone"] {
   list-style-image: url(chrome://browser/skin/webRTC-shareMicrophone-64.png);
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -51,16 +51,17 @@ browser.jar:
         skin/classic/browser/menuPanel-help.png
         skin/classic/browser/menuPanel-small.png
         skin/classic/browser/Metro_Glyph.png                        (Metro_Glyph-aero.png)
         skin/classic/browser/Metro_Glyph-inverted.png
         skin/classic/browser/Metro_Glyph-menuPanel.png
         skin/classic/browser/bad-content-blocked-16.png
         skin/classic/browser/bad-content-blocked-64.png
         skin/classic/browser/bad-content-unblocked-16.png
+        skin/classic/browser/bad-content-unblocked-64.png
         skin/classic/browser/monitor.png
         skin/classic/browser/monitor_16-10.png
         skin/classic/browser/notification-16.png
         skin/classic/browser/notification-64.png
         skin/classic/browser/pageInfo.css
         skin/classic/browser/pageInfo.png
         skin/classic/browser/page-livemarks.png                      (feeds/feedIcon16.png)
         skin/classic/browser/pluginInstall-16.png
@@ -473,16 +474,17 @@ browser.jar:
         skin/classic/aero/browser/menuPanel-small.png
         skin/classic/aero/browser/menuPanel-small-aero.png
         skin/classic/aero/browser/Metro_Glyph.png                    (Metro_Glyph-aero.png)
         skin/classic/aero/browser/Metro_Glyph-inverted.png
         skin/classic/aero/browser/Metro_Glyph-menuPanel.png
         skin/classic/aero/browser/bad-content-blocked-16.png
         skin/classic/aero/browser/bad-content-blocked-64.png
         skin/classic/aero/browser/bad-content-unblocked-16.png
+        skin/classic/aero/browser/bad-content-unblocked-64.png
         skin/classic/aero/browser/monitor.png
         skin/classic/aero/browser/monitor_16-10.png
         skin/classic/aero/browser/notification-16.png
         skin/classic/aero/browser/notification-64.png
         skin/classic/aero/browser/pageInfo.css
         skin/classic/aero/browser/pageInfo.png                       (pageInfo-aero.png)
         skin/classic/aero/browser/page-livemarks.png                 (feeds/feedIcon16-aero.png)
         skin/classic/aero/browser/pluginInstall-16.png
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -676,28 +676,36 @@ nsChromeRegistryChrome::OverlayListHash:
   return &entry->mArray;
 }
 
 #ifdef MOZ_XUL
 NS_IMETHODIMP
 nsChromeRegistryChrome::GetStyleOverlays(nsIURI *aChromeURL,
                                          nsISimpleEnumerator **aResult)
 {
-  const nsCOMArray<nsIURI>* parray = mStyleHash.GetArray(aChromeURL);
+  nsCOMPtr<nsIURI> chromeURLWithoutHash;
+  if (aChromeURL) {
+    aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
+  }
+  const nsCOMArray<nsIURI>* parray = mStyleHash.GetArray(chromeURLWithoutHash);
   if (!parray)
     return NS_NewEmptyEnumerator(aResult);
 
   return NS_NewArrayEnumerator(aResult, *parray);
 }
 
 NS_IMETHODIMP
 nsChromeRegistryChrome::GetXULOverlays(nsIURI *aChromeURL,
                                        nsISimpleEnumerator **aResult)
 {
-  const nsCOMArray<nsIURI>* parray = mOverlayHash.GetArray(aChromeURL);
+  nsCOMPtr<nsIURI> chromeURLWithoutHash;
+  if (aChromeURL) {
+    aChromeURL->CloneIgnoringRef(getter_AddRefs(chromeURLWithoutHash));
+  }
+  const nsCOMArray<nsIURI>* parray = mOverlayHash.GetArray(chromeURLWithoutHash);
   if (!parray)
     return NS_NewEmptyEnumerator(aResult);
 
   return NS_NewArrayEnumerator(aResult, *parray);
 }
 #endif // MOZ_XUL
 
 nsIURI*
@@ -890,17 +898,20 @@ nsChromeRegistryChrome::ManifestOverlay(
   }
 
   if (!CanLoadResource(overlayuri)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Cannot register non-local URI '%s' as an overlay.", overlay);
     return;
   }
 
-  mOverlayHash.Add(baseuri, overlayuri);
+  nsCOMPtr<nsIURI> baseuriWithoutHash;
+  baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));
+
+  mOverlayHash.Add(baseuriWithoutHash, overlayuri);
 }
 
 void
 nsChromeRegistryChrome::ManifestStyle(ManifestProcessingContext& cx, int lineno,
                                       char *const * argv, bool platform,
                                       bool contentaccessible)
 {
   char* base = argv[0];
@@ -915,17 +926,20 @@ nsChromeRegistryChrome::ManifestStyle(Ma
   }
 
   if (!CanLoadResource(overlayuri)) {
     LogMessageWithContext(cx.GetManifestURI(), lineno, nsIScriptError::warningFlag,
                           "Cannot register non-local URI '%s' as a style overlay.", overlay);
     return;
   }
 
-  mStyleHash.Add(baseuri, overlayuri);
+  nsCOMPtr<nsIURI> baseuriWithoutHash;
+  baseuri->CloneIgnoringRef(getter_AddRefs(baseuriWithoutHash));
+
+  mStyleHash.Add(baseuriWithoutHash, overlayuri);
 }
 
 void
 nsChromeRegistryChrome::ManifestOverride(ManifestProcessingContext& cx, int lineno,
                                          char *const * argv, bool platform,
                                          bool contentaccessible)
 {
   char* chrome = argv[0];
--- a/content/xul/document/src/nsXULPrototypeCache.cpp
+++ b/content/xul/document/src/nsXULPrototypeCache.cpp
@@ -126,17 +126,23 @@ nsXULPrototypeCache::Observe(nsISupports
         NS_WARNING("Unexpected observer topic.");
     }
     return NS_OK;
 }
 
 nsXULPrototypeDocument*
 nsXULPrototypeCache::GetPrototype(nsIURI* aURI)
 {
-    nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(aURI);
+    if (!aURI)
+        return nullptr;
+
+    nsCOMPtr<nsIURI> uriWithoutRef;
+    aURI->CloneIgnoringRef(getter_AddRefs(uriWithoutRef));
+
+    nsXULPrototypeDocument* protoDoc = mPrototypeTable.GetWeak(uriWithoutRef);
     if (protoDoc)
         return protoDoc;
 
     nsresult rv = BeginCaching(aURI);
     if (NS_FAILED(rv))
         return nullptr;
 
     // No prototype in XUL memory cache. Spin up the cache Service.
@@ -159,17 +165,23 @@ nsXULPrototypeCache::GetPrototype(nsIURI
     
     mInputStreamTable.Remove(aURI);
     return newProto;
 }
 
 nsresult
 nsXULPrototypeCache::PutPrototype(nsXULPrototypeDocument* aDocument)
 {
-    nsCOMPtr<nsIURI> uri = aDocument->GetURI();
+    if (!aDocument->GetURI()) {
+        return NS_ERROR_FAILURE;
+    }
+
+    nsCOMPtr<nsIURI> uri;
+    aDocument->GetURI()->CloneIgnoringRef(getter_AddRefs(uri));
+
     // Put() releases any old value and addrefs the new one
     mPrototypeTable.Put(uri, aDocument);
 
     return NS_OK;
 }
 
 nsresult
 nsXULPrototypeCache::PutStyleSheet(CSSStyleSheet* aStyleSheet)
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -97,8 +97,9 @@ skip-if = e10s
 [browser_loadURI.js]
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_onbeforeunload_navigation.js]
 skip-if = e10s
 [browser_search_notification.js]
 skip-if = e10s
 [browser_timelineMarkers-01.js]
 [browser_timelineMarkers-02.js]
+skip-if = e10s
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -139,16 +139,17 @@ pref("browser.download.manager.showAlert
 pref("browser.download.manager.showAlertInterval", 2000);
 pref("browser.download.manager.retention", 2);
 pref("browser.download.manager.showWhenStarting", false);
 pref("browser.download.manager.closeWhenDone", true);
 pref("browser.download.manager.openDelay", 0);
 pref("browser.download.manager.focusWhenStarting", false);
 pref("browser.download.manager.flashCount", 2);
 pref("browser.download.manager.displayedHistoryDays", 7);
+pref("browser.download.manager.addToRecentDocs", true);
 
 /* download helper */
 pref("browser.helperApps.deleteTempFileOnExit", false);
 
 /* password manager */
 pref("signon.rememberSignons", true);
 pref("signon.expireMasterPassword", false);
 pref("signon.debug", false);
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -35,17 +35,17 @@
     <uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
     <uses-permission android:name="com.android.browser.permission.READ_HISTORY_BOOKMARKS"/>
 
     <uses-permission android:name="android.permission.WAKE_LOCK"/>
     <uses-permission android:name="android.permission.VIBRATE"/>
     <uses-permission android:name="@ANDROID_PACKAGE_NAME@.permissions.PASSWORD_PROVIDER"/>
     <uses-permission android:name="@ANDROID_PACKAGE_NAME@.permissions.BROWSER_PROVIDER"/>
     <uses-permission android:name="@ANDROID_PACKAGE_NAME@.permissions.FORMHISTORY_PROVIDER"/>
-
+    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
 #ifdef MOZ_WEBSMS_BACKEND
     <!-- WebSMS -->
     <uses-permission android:name="android.permission.SEND_SMS"/>
     <uses-permission android:name="android.permission.RECEIVE_SMS"/>
     <uses-permission android:name="android.permission.WRITE_SMS"/>
     <uses-permission android:name="android.permission.READ_SMS"/>
 #endif
 
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -55,16 +55,17 @@ import org.mozilla.gecko.util.HardwareUt
 import org.mozilla.gecko.util.NativeEventListener;
 import org.mozilla.gecko.util.NativeJSContainer;
 import org.mozilla.gecko.util.NativeJSObject;
 import org.mozilla.gecko.util.ProxySelector;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.DownloadManager;
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -1791,27 +1792,39 @@ public class GeckoAppShell
                 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(file) && file.startsWith(filter))
                     Log.d(LOGTAG, "[OPENFILE] " + name + "(" + split[pidColumn] + ") : " + file);
             }
             in.close();
         } catch (Exception e) { }
     }
 
     @WrapElementForJNI
-    public static void scanMedia(String aFile, String aMimeType) {
+    public static void scanMedia(final String aFile, String aMimeType) {
         // If the platform didn't give us a mimetype, try to guess one from the filename
         if (TextUtils.isEmpty(aMimeType)) {
             int extPosition = aFile.lastIndexOf(".");
             if (extPosition > 0 && extPosition < aFile.length() - 1) {
                 aMimeType = getMimeTypeFromExtension(aFile.substring(extPosition+1));
             }
         }
 
-        Context context = getContext();
-        GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
+        final File f = new File(aFile);
+        if (AppConstants.Versions.feature12Plus) {
+            final DownloadManager dm = (DownloadManager) getContext().getSystemService(Context.DOWNLOAD_SERVICE);
+            dm.addCompletedDownload(f.getName(),
+                                    f.getName(),
+                                    !TextUtils.isEmpty(aMimeType),
+                                    aMimeType,
+                                    f.getAbsolutePath(),
+                                    f.length(),
+                                    false);
+        } else {
+            Context context = getContext();
+            GeckoMediaScannerClient.startScan(context, aFile, aMimeType);
+        }
     }
 
     @WrapElementForJNI(stubName = "GetIconForExtensionWrapper")
     public static byte[] getIconForExtension(String aExt, int iconSize) {
         try {
             if (iconSize <= 0)
                 iconSize = 16;
 
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -73,13 +73,13 @@ var FindHelper = {
           Cu.reportError("Warning: selected tab changed during find!");
           // fall through and restore viewport on the initial tab anyway
         }
         this._targetTab.setViewport(JSON.parse(this._initialViewport));
         this._targetTab.sendViewportUpdate();
       }
     } else {
       // Disabled until bug 1014113 is fixed
-      //ZoomHelper.zoomToRect(aData.rect, -1, false, true);
+      // ZoomHelper.zoomToRect(aData.rect);
       this._viewportChanged = true;
     }
   }
 };
--- a/mobile/android/chrome/content/ZoomHelper.js
+++ b/mobile/android/chrome/content/ZoomHelper.js
@@ -76,72 +76,71 @@ var ZoomHelper = {
             dw > minDifference && dw < maxDifference);
   },
 
   /* Zoom to an element, optionally keeping a particular part of it
    * in view if it is really tall.
    */
   zoomToElement: function(aElement, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true) {
     let rect = ElementTouchHelper.getBoundingContentRect(aElement);
-    ZoomHelper.zoomToRect(rect, aClickY, aCanZoomOut, aCanScrollHorizontally, aElement);
-  },
 
-  zoomToRect: function(aRect, aClickY = -1, aCanZoomOut = true, aCanScrollHorizontally = true, aElement) {
     const margin = 15;
 
-    if(!aRect.h || !aRect.w) {
-      aRect.h = aRect.height;
-      aRect.w = aRect.width;
-    }
-
     let viewport = BrowserApp.selectedTab.getViewport();
-    let bRect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, aRect.x - margin) : viewport.cssX,
-                         aRect.y,
-                         aCanScrollHorizontally ? aRect.w + 2 * margin : viewport.cssWidth,
-                         aRect.h);
+    rect = new Rect(aCanScrollHorizontally ? Math.max(viewport.cssPageLeft, rect.x - margin) : viewport.cssX,
+                    rect.y,
+                    aCanScrollHorizontally ? rect.w + 2 * margin : viewport.cssWidth,
+                    rect.h);
     // constrict the rect to the screen's right edge
-    bRect.width = Math.min(bRect.width, viewport.cssPageRight - bRect.x);
+    rect.width = Math.min(rect.width, viewport.cssPageRight - rect.x);
 
     // if the rect is already taking up most of the visible area and is stretching the
     // width of the page, then we want to zoom out instead.
     if (aElement) {
       if (BrowserEventHandler.mReflozPref) {
         let zoomFactor = BrowserApp.selectedTab.getZoomToMinFontSize(aElement);
 
-        bRect.width = zoomFactor <= 1.0 ? bRect.width : gScreenWidth / zoomFactor;
-        bRect.height = zoomFactor <= 1.0 ? bRect.height : bRect.height / zoomFactor;
-        if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(bRect, viewport)) {
+        rect.width = zoomFactor <= 1.0 ? rect.width : gScreenWidth / zoomFactor;
+        rect.height = zoomFactor <= 1.0 ? rect.height : rect.height / zoomFactor;
+        if (zoomFactor == 1.0 || ZoomHelper.isRectZoomedIn(rect, viewport)) {
           if (aCanZoomOut) {
             ZoomHelper.zoomOut();
           }
           return;
         }
-      } else if (ZoomHelper.isRectZoomedIn(bRect, viewport)) {
+      } else if (ZoomHelper.isRectZoomedIn(rect, viewport)) {
         if (aCanZoomOut) {
           ZoomHelper.zoomOut();
         }
         return;
       }
+
+      ZoomHelper.zoomToRect(rect, aClickY);
     }
+  },
 
-    let rect = {};
+  /* Zoom to a specific part of the screen defined by a rect,
+   * optionally keeping a particular part of it in view
+   * if it is really tall.
+   */
+  zoomToRect: function(aRect, aClickY = -1) {
+    let rect = new Rect(aRect.x,
+                        aRect.y,
+                        aRect.width,
+                        Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height));
 
     rect.type = "Browser:ZoomToRect";
-    rect.x = bRect.x;
-    rect.y = bRect.y;
-    rect.w = bRect.width;
-    rect.h = Math.min(bRect.width * viewport.cssHeight / viewport.cssWidth, bRect.height);
 
     if (aClickY >= 0) {
       // if the block we're zooming to is really tall, and we want to keep a particular
       // part of it in view, then adjust the y-coordinate of the target rect accordingly.
-      // the 1.2 multiplier is just a little fuzz to compensate for bRect including horizontal
+      // the 1.2 multiplier is just a little fuzz to compensate for aRect including horizontal
       // margins but not vertical ones.
       let cssTapY = viewport.cssY + aClickY;
-      if ((bRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) {
+      if ((aRect.height > rect.h) && (cssTapY > rect.y + (rect.h * 1.2))) {
         rect.y = cssTapY - (rect.h / 2);
       }
     }
 
     if (rect.w > viewport.cssWidth || rect.h > viewport.cssHeight) {
       BrowserEventHandler.resetMaxLineBoxWidth();
     }
 
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -2760,17 +2760,17 @@ nsDownload::SetState(DownloadState aStat
       nsCOMPtr<nsIFile> file;
       nsAutoString path;
 
       if (fileURL &&
           NS_SUCCEEDED(fileURL->GetFile(getter_AddRefs(file))) &&
           file &&
           NS_SUCCEEDED(file->GetPath(path))) {
 
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
         // On Windows and Gtk, add the download to the system's "recent documents"
         // list, with a pref to disable.
         {
           bool addToRecentDocs = true;
           if (pref)
             pref->GetBoolPref(PREF_BDM_ADDTORECENTDOCS, &addToRecentDocs);
 
           if (addToRecentDocs && !mPrivate) {
@@ -2780,16 +2780,25 @@ nsDownload::SetState(DownloadState aStat
             GtkRecentManager* manager = gtk_recent_manager_get_default();
 
             gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
                                            nullptr, nullptr);
             if (uri) {
               gtk_recent_manager_add_item(manager, uri);
               g_free(uri);
             }
+#elif defined(MOZ_WIDGET_ANDROID)
+            nsCOMPtr<nsIMIMEInfo> mimeInfo;
+            nsAutoCString contentType;
+            GetMIMEInfo(getter_AddRefs(mimeInfo));
+
+            if (mimeInfo)
+              mimeInfo->GetMIMEType(contentType);
+
+            mozilla::widget::android::GeckoAppShell::ScanMedia(path, NS_ConvertUTF8toUTF16(contentType));
 #endif
           }
 #ifdef MOZ_ENABLE_GIO
           // Use GIO to store the source URI for later display in the file manager.
           GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
           nsCString source_uri;
           mSource->GetSpec(source_uri);
           GFileInfo *file_info = g_file_info_new();
@@ -2809,26 +2818,16 @@ nsDownload::SetState(DownloadState aStat
         CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
                                                  NS_ConvertUTF16toUTF8(path).get(),
                                                  kCFStringEncodingUTF8);
         CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
         ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
                                                observedObject, nullptr, TRUE);
         ::CFRelease(observedObject);
 #endif
-#ifdef MOZ_WIDGET_ANDROID
-        nsCOMPtr<nsIMIMEInfo> mimeInfo;
-        nsAutoCString contentType;
-        GetMIMEInfo(getter_AddRefs(mimeInfo));
-
-        if (mimeInfo)
-          mimeInfo->GetMIMEType(contentType);
-
-        mozilla::widget::android::GeckoAppShell::ScanMedia(path, NS_ConvertUTF8toUTF16(contentType));
-#endif
       }
 
 #ifdef XP_WIN
       // Adjust file attributes so that by default, new files are indexed
       // by desktop search services. Skip off those that land in the temp
       // folder.
       nsCOMPtr<nsIFile> tempDir, fileDir;
       rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
--- a/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
+++ b/toolkit/components/jsdownloads/src/DownloadPlatform.cpp
@@ -67,33 +67,35 @@ static void gio_set_metadata_done(GObjec
 #endif
 
 nsresult DownloadPlatform::DownloadDone(nsIURI* aSource, nsIFile* aTarget,
                                         const nsACString& aContentType, bool aIsPrivate)
 {
 #if defined(XP_WIN) || defined(XP_MACOSX) || defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GTK)
   nsAutoString path;
   if (aTarget && NS_SUCCEEDED(aTarget->GetPath(path))) {
-#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
+#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_ANDROID)
     // On Windows and Gtk, add the download to the system's "recent documents"
     // list, with a pref to disable.
     {
       bool addToRecentDocs = Preferences::GetBool(PREF_BDM_ADDTORECENTDOCS);
       if (addToRecentDocs && !aIsPrivate) {
 #ifdef XP_WIN
         ::SHAddToRecentDocs(SHARD_PATHW, path.get());
 #elif defined(MOZ_WIDGET_GTK)
         GtkRecentManager* manager = gtk_recent_manager_get_default();
 
         gchar* uri = g_filename_to_uri(NS_ConvertUTF16toUTF8(path).get(),
                                        nullptr, nullptr);
         if (uri) {
           gtk_recent_manager_add_item(manager, uri);
           g_free(uri);
         }
+#elif MOZ_WIDGET_ANDROID
+        mozilla::widget::android::GeckoAppShell::ScanMedia(path, NS_ConvertUTF8toUTF16(aContentType));
 #endif
       }
 #ifdef MOZ_ENABLE_GIO
       // Use GIO to store the source URI for later display in the file manager.
       GFile* gio_file = g_file_new_for_path(NS_ConvertUTF16toUTF8(path).get());
       nsCString source_uri;
       aSource->GetSpec(source_uri);
       GFileInfo *file_info = g_file_info_new();
@@ -113,21 +115,16 @@ nsresult DownloadPlatform::DownloadDone(
     CFStringRef observedObject = ::CFStringCreateWithCString(kCFAllocatorDefault,
                                              NS_ConvertUTF16toUTF8(path).get(),
                                              kCFStringEncodingUTF8);
     CFNotificationCenterRef center = ::CFNotificationCenterGetDistributedCenter();
     ::CFNotificationCenterPostNotification(center, CFSTR("com.apple.DownloadFileFinished"),
                                            observedObject, nullptr, TRUE);
     ::CFRelease(observedObject);
 #endif
-#ifdef MOZ_WIDGET_ANDROID
-    if (!aContentType.IsEmpty()) {
-      mozilla::widget::android::GeckoAppShell::ScanMedia(path, NS_ConvertUTF8toUTF16(aContentType));
-    }
-#endif
   }
 
 #ifdef XP_WIN
   // Adjust file attributes so that by default, new files are indexed by
   // desktop search services. Skip off those that land in the temp folder.
   nsCOMPtr<nsIFile> tempDir, fileDir;
   nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tempDir));
   NS_ENSURE_SUCCESS(rv, rv);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -6584,16 +6584,21 @@
     "description": "Tracking protection shield (0 = not shown, 1 = blocked, 2 = loaded, 3 = due to mixed content"
   },
   "TRACKING_PROTECTION_EVENTS": {
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 3,
     "description": "Doorhanger shown = 0, Disable = 1, Enable = 2"
   },
+  "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS": {
+    "expires_in_version": "never",
+    "kind": "boolean",
+    "description": "Stores 1 if generating a call URL succeeded, and 0 if it failed."
+  },
   "E10S_AUTOSTART": {
     "expires_in_version": "never",
     "kind": "boolean",
     "description": "Whether a session is set to autostart e10s windows"
   },
   "E10S_WINDOW": {
     "expires_in_version": "never",
     "kind": "boolean",
--- a/toolkit/components/thumbnails/test/head.js
+++ b/toolkit/components/thumbnails/test/head.js
@@ -225,17 +225,17 @@ function addVisits(aPlaceInfo, aCallback
     places = places.concat(aPlaceInfo);
   } else {
     places.push(aPlaceInfo)
   }
 
   // Create mozIVisitInfo for each entry.
   let now = Date.now();
   for (let i = 0; i < places.length; i++) {
-    if (typeof(places[i] == "string")) {
+    if (typeof(places[i]) == "string") {
       places[i] = { uri: Services.io.newURI(places[i], "", null) };
     }
     if (!places[i].title) {
       places[i].title = "test visit for " + places[i].uri.spec;
     }
     places[i].visits = [{
       transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
                                                          : places[i].transition,
--- a/toolkit/devtools/server/actors/timeline.js
+++ b/toolkit/devtools/server/actors/timeline.js
@@ -2,18 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /**
  * Many Gecko operations (painting, reflows, restyle, ...) can be tracked
  * in real time. A marker is a representation of one operation. A marker
- * has a name, and start and end timestamps. Markers are stored within
- * a docshell.
+ * has a name, and start and end timestamps. Markers are stored in docShells.
  *
  * This actor exposes this tracking mechanism to the devtools protocol.
  *
  * To start/stop recording markers:
  *   TimelineFront.start()
  *   TimelineFront.stop()
  *   TimelineFront.isRecording()
  *
@@ -23,101 +22,163 @@
  */
 
 const {Ci, Cu} = require("chrome");
 const protocol = require("devtools/server/protocol");
 const {method, Arg, RetVal} = protocol;
 const events = require("sdk/event/core");
 const {setTimeout, clearTimeout} = require("sdk/timers");
 
+// How often do we pull markers from the docShells, and therefore, how often do
+// we send events to the front (knowing that when there are no markers in the
+// docShell, no event is sent).
 const DEFAULT_TIMELINE_DATA_PULL_TIMEOUT = 200; // ms
 
 /**
- * The timeline actor pops and forwards timeline markers registered in
- * a docshell.
+ * The timeline actor pops and forwards timeline markers registered in docshells.
  */
 let TimelineActor = exports.TimelineActor = protocol.ActorClass({
   typeName: "timeline",
 
   events: {
     /**
-     * "markers" events are emitted at regular intervals when profile markers
-     * are found. A marker has the following properties:
-     * - start {Number}
-     * - end {Number}
+     * "markers" events are emitted every DEFAULT_TIMELINE_DATA_PULL_TIMEOUT ms
+     * at most, when profile markers are found. A marker has the following
+     * properties:
+     * - start {Number} ms
+     * - end {Number} ms
      * - name {String}
      */
     "markers" : {
       type: "markers",
       markers: Arg(0, "array:json")
     }
   },
 
   initialize: function(conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
-    this.docshell = tabActor.docShell;
+    this.tabActor = tabActor;
+
+    this._isRecording = false;
+
+    // Make sure to get markers from new windows as they become available
+    this._onWindowReady = this._onWindowReady.bind(this);
+    events.on(this.tabActor, "window-ready", this._onWindowReady);
   },
 
   /**
    * The timeline actor is the first (and last) in its hierarchy to use protocol.js
    * so it doesn't have a parent protocol actor that takes care of its lifetime.
    * So it needs a disconnect method to cleanup.
    */
   disconnect: function() {
     this.destroy();
   },
 
   destroy: function() {
     this.stop();
-    this.docshell = null;
+
+    events.off(this.tabActor, "window-ready", this._onWindowReady);
+    this.tabActor = null;
+
     protocol.Actor.prototype.destroy.call(this);
   },
 
   /**
+   * Convert a window to a docShell.
+   * @param {nsIDOMWindow}
+   * @return {nsIDocShell}
+   */
+  toDocShell: win => win.QueryInterface(Ci.nsIInterfaceRequestor)
+                        .getInterface(Ci.nsIWebNavigation)
+                        .QueryInterface(Ci.nsIDocShell),
+
+  /**
+   * Get the list of docShells in the currently attached tabActor.
+   * @return {Array}
+   */
+  get docShells() {
+    return this.tabActor.windows.map(this.toDocShell);
+  },
+
+  /**
    * At regular intervals, pop the markers from the docshell, and forward
    * markers if any.
    */
   _pullTimelineData: function() {
-    let markers = this.docshell.popProfileTimelineMarkers();
+    if (!this._isRecording) {
+      return;
+    }
+
+    let markers = [];
+    for (let docShell of this.docShells) {
+      markers = [...markers, ...docShell.popProfileTimelineMarkers()];
+    }
     if (markers.length > 0) {
       events.emit(this, "markers", markers);
     }
+
     this._dataPullTimeout = setTimeout(() => {
       this._pullTimelineData();
     }, DEFAULT_TIMELINE_DATA_PULL_TIMEOUT);
   },
 
   /**
-   * Are we recording profile markers for the current docshell (window)?
+   * Are we recording profile markers currently?
    */
   isRecording: method(function() {
-    return this.docshell.recordProfileTimelineMarkers;
+    return this._isRecording;
   }, {
     request: {},
     response: {
       value: RetVal("boolean")
     }
   }),
 
   /**
-   * Start/stop recording profile markers.
+   * Start recording profile markers.
    */
   start: method(function() {
-    if (!this.docshell.recordProfileTimelineMarkers) {
-      this.docshell.recordProfileTimelineMarkers = true;
-      this._pullTimelineData();
+    if (this._isRecording) {
+      return;
     }
+    this._isRecording = true;
+
+    for (let docShell of this.docShells) {
+      docShell.recordProfileTimelineMarkers = true;
+    }
+
+    this._pullTimelineData();
   }, {}),
 
+  /**
+   * Stop recording profile markers.
+   */
   stop: method(function() {
-    if (this.docshell.recordProfileTimelineMarkers) {
-      this.docshell.recordProfileTimelineMarkers = false;
-      clearTimeout(this._dataPullTimeout);
+    if (!this._isRecording) {
+      return;
+    }
+    this._isRecording = false;
+
+    for (let docShell of this.docShells) {
+      docShell.recordProfileTimelineMarkers = false;
     }
+
+    clearTimeout(this._dataPullTimeout);
   }, {}),
+
+  /**
+   * When a new window becomes available in the tabActor, start recording its
+   * markers if we were recording.
+   */
+  _onWindowReady: function({window}) {
+    if (this._isRecording) {
+      this.toDocShell(window).recordProfileTimelineMarkers = true;
+    }
+  }
 });
 
 exports.TimelineFront = protocol.FrontClass(TimelineActor, {
   initialize: function(client, {timelineActor}) {
     protocol.Front.prototype.initialize.call(this, client, {actor: timelineActor});
     this.manage(this);
   },
 
--- a/toolkit/devtools/server/actors/tracer.js
+++ b/toolkit/devtools/server/actors/tracer.js
@@ -101,17 +101,16 @@ TracerActor.prototype = {
 
   get attached() { return this._attached; },
   get idle()     { return this._attached && this._activeTraces.size === 0; },
   get tracing()  { return this._attached && this._activeTraces.size > 0; },
 
   get dbg() {
     if (!this._dbg) {
       this._dbg = this._parent.makeDebugger();
-      this._dbg.onEnterFrame = this.onEnterFrame;
     }
     return this._dbg;
   },
 
   /**
    * Buffer traces and only send them every BUFFER_SEND_DELAY milliseconds.
    */
   _send: function(aPacket) {
@@ -183,16 +182,17 @@ TracerActor.prototype = {
         return {
           error: "badParameterType",
           message: "No such trace type: " + traceType
         };
       }
     }
 
     if (this.idle) {
+      this.dbg.onEnterFrame = this.onEnterFrame;
       this.dbg.enabled = true;
       this._sequence = 0;
       this._startTime = Date.now();
     }
 
     // Start recording all requested trace types.
     for (let traceType of aRequest.trace) {
       this._requestsForTraceType[traceType]++;
@@ -239,16 +239,17 @@ TracerActor.prototype = {
     }
 
     // Clear hit counts if no trace is requesting them.
     if (!this._requestsForTraceType.hitCount) {
       this._hitCounts.clear();
     }
 
     if (this.idle) {
+      this._dbg.onEnterFrame = undefined;
       this.dbg.enabled = false;
     }
 
     return {
       type: "stoppedTrace",
       why: "requested",
       name
     };
@@ -259,21 +260,21 @@ TracerActor.prototype = {
   /**
    * Called by the engine when a frame is entered. Sends an unsolicited packet
    * to the client carrying requested trace information.
    *
    * @param aFrame Debugger.Frame
    *        The stack frame that was entered.
    */
   onEnterFrame: function(aFrame) {
+    if (aFrame.script && aFrame.script.url == "self-hosted") {
+      return;
+    }
+
     Task.spawn(function*() {
-      if (aFrame.script && aFrame.script.url == "self-hosted") {
-        return;
-      }
-
       // This function might request original (i.e. source-mapped) location,
       // which is asynchronous. We need to ensure that packets are sent out
       // in the correct order.
       let runInOrder = this._packetScheduler.schedule();
 
       let packet = {
         type: "enteredFrame",
         sequence: this._sequence++
--- a/toolkit/devtools/server/tests/browser/browser.ini
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -1,19 +1,23 @@
 [DEFAULT]
 skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
 subsuite = devtools
 support-files =
   head.js
+  navigate-first.html
+  navigate-second.html
   storage-dynamic-windows.html
   storage-listings.html
   storage-unsecured-iframe.html
   storage-updates.html
   storage-secured-iframe.html
-  navigate-first.html
-  navigate-second.html
+  timeline-iframe-child.html
+  timeline-iframe-parent.html
 
+[browser_navigateEvents.js]
 [browser_storage_dynamic_windows.js]
 [browser_storage_listings.js]
 [browser_storage_updates.js]
-[browser_navigateEvents.js]
 [browser_timeline.js]
 skip-if = buildapp == 'mulet'
+[browser_timeline_iframes.js]
+skip-if = buildapp == 'mulet'
--- a/toolkit/devtools/server/tests/browser/browser_navigateEvents.js
+++ b/toolkit/devtools/server/tests/browser/browser_navigateEvents.js
@@ -1,22 +1,18 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let Cu = Components.utils;
-let Cc = Components.classes;
-let Ci = Components.interfaces;
+"use strict";
 
 const URL1 = MAIN_DOMAIN + "navigate-first.html";
 const URL2 = MAIN_DOMAIN + "navigate-second.html";
 
-let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
-let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
-
-let devtools = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools;
-let events = devtools.require("sdk/event/core");
-
+let events = require("sdk/event/core");
 let client;
 
 // State machine to check events order
 let i = 0;
 function assertEvent(event, data) {
   let x = 0;
   switch(i++) {
     case x++:
@@ -92,34 +88,28 @@ function onDOMContentLoaded() {
   assertEvent("DOMContentLoaded");
 }
 function onLoad() {
   assertEvent("load");
 }
 
 function getServerTabActor(callback) {
   // Ensure having a minimal server
-  if (!DebuggerServer.initialized) {
-    DebuggerServer.init(function () { return true; });
-    DebuggerServer.addBrowserActors();
-  }
+  initDebuggerServer();
 
   // Connect to this tab
   let transport = DebuggerServer.connectPipe();
   client = new DebuggerClient(transport);
-  client.connect(function onConnect() {
-    client.listTabs(function onListTabs(aResponse) {
-      // Fetch the BrowserTabActor for this tab
-      let actorID = aResponse.tabs[aResponse.selected].actor;
-      client.attachTab(actorID, function(aResponse, aTabClient) {
-        // !Hack! Retrieve a server side object, the BrowserTabActor instance
-        let conn = transport._serverConnection;
-        let tabActor = conn.getActor(actorID);
-        callback(tabActor);
-      });
+  connectDebuggerClient(client).then(form => {
+    let actorID = form.actor;
+    client.attachTab(actorID, function(aResponse, aTabClient) {
+      // !Hack! Retrieve a server side object, the BrowserTabActor instance
+      let conn = transport._serverConnection;
+      let tabActor = conn.getActor(actorID);
+      callback(tabActor);
     });
   });
 
   client.addListener("tabNavigated", function (aEvent, aPacket) {
     assertEvent("tabNavigated", aPacket);
   });
 }
 
--- a/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js
+++ b/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -1,16 +1,13 @@
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/Services.jsm");
-let tempScope = {};
-Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
-Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
-Cu.import("resource://gre/modules/Promise.jsm", tempScope);
-let {DebuggerServer, DebuggerClient, Promise} = tempScope;
-tempScope = null;
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
 
 const {StorageFront} = require("devtools/server/actors/storage");
 let gFront, gWindow;
 
 const beforeReload = {
   cookies: {
     "test1.example.org": ["c1", "cs2", "c3", "uc1"],
     "sectest1.example.org": ["uc1", "cs2"]
@@ -55,17 +52,17 @@ function finishTests(client) {
 
   let closeConnection = () => {
     // Forcing GC/CC to get rid of docshells and windows created by this test.
     forceCollections();
     client.close(() => {
       forceCollections();
       DebuggerServer.destroy();
       forceCollections();
-      gFront = gWindow = DebuggerClient = DebuggerServer = null;
+      gFront = gWindow = null;
       finish();
     });
   }
   gWindow.clearIterator = gWindow.clear(() => {
     clearIDB(gWindow, 0, closeConnection);
   });
   gWindow.clearIterator.next();
 }
@@ -301,33 +298,23 @@ function testRemoveIframe() {
       break;
     }
   }
   return reloaded.promise;
 }
 
 function test() {
   addTab(MAIN_DOMAIN + "storage-dynamic-windows.html").then(function(doc) {
-    try {
-      // Sometimes debugger server does not get destroyed correctly by previous
-      // tests.
-      DebuggerServer.destroy();
-    } catch (ex) { }
-    DebuggerServer.init(function () { return true; });
-    DebuggerServer.addBrowserActors();
+    initDebuggerServer();
 
     let createConnection = () => {
       let client = new DebuggerClient(DebuggerServer.connectPipe());
-      client.connect(function onConnect() {
-        client.listTabs(function onListTabs(aResponse) {
-          let form = aResponse.tabs[aResponse.selected];
-          gFront = StorageFront(client, form);
-
-          gFront.listStores().then(data => testStores(data, client));
-        });
+      connectDebuggerClient(client).then(form => {
+        gFront = StorageFront(client, form);
+        gFront.listStores().then(data => testStores(data, client));
       });
     };
 
     /**
      * This method iterates over iframes in a window and setups the indexed db
      * required for this test.
      */
     let setupIDBInFrames = (w, i, c) => {
--- a/toolkit/devtools/server/tests/browser/browser_storage_listings.js
+++ b/toolkit/devtools/server/tests/browser/browser_storage_listings.js
@@ -1,18 +1,15 @@
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/Services.jsm");
-let tempScope = {};
-Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
-Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
-let {DebuggerServer, DebuggerClient} = tempScope;
-tempScope = null;
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
 
 const {StorageFront} = require("devtools/server/actors/storage");
-let {Task} = require("resource://gre/modules/Task.jsm");
 let gWindow = null;
 
 const storeMap = {
   cookies: {
     "test1.example.org": [
       {
         name: "c1",
         value: "foobar",
@@ -343,17 +340,17 @@ function finishTests(client) {
 
   let closeConnection = () => {
     // Forcing GC/CC to get rid of docshells and windows created by this test.
     forceCollections();
     client.close(() => {
       forceCollections();
       DebuggerServer.destroy();
       forceCollections();
-      gWindow = DebuggerClient = DebuggerServer = null;
+      gWindow = null;
       finish();
     });
   }
   gWindow.clearIterator = gWindow.clear(() => {
     clearIDB(gWindow, 0, closeConnection);
   });
   gWindow.clearIterator.next();
 }
@@ -635,34 +632,24 @@ let testIDBEntries = Task.async(function
   if (index == Object.keys(hosts).length - 1) {
     return;
   }
   yield testObjectStores(++index, hosts, indexedDBActor);
 });
 
 function test() {
   addTab(MAIN_DOMAIN + "storage-listings.html").then(function(doc) {
-    try {
-      // Sometimes debugger server does not get destroyed correctly by previous
-      // tests.
-      DebuggerServer.destroy();
-    } catch (ex) { }
-    DebuggerServer.init(function () { return true; });
-    DebuggerServer.addBrowserActors();
+    initDebuggerServer();
 
     let createConnection = () => {
       let client = new DebuggerClient(DebuggerServer.connectPipe());
-      client.connect(function onConnect() {
-        client.listTabs(function onListTabs(aResponse) {
-          let form = aResponse.tabs[aResponse.selected];
-          let front = StorageFront(client, form);
-
-          front.listStores().then(data => testStores(data))
-               .then(() => finishTests(client));
-        });
+      connectDebuggerClient(client).then(form => {
+        let front = StorageFront(client, form);
+        front.listStores().then(data => testStores(data))
+                          .then(() => finishTests(client));
       });
     };
 
     /**
      * This method iterates over iframes in a window and setups the indexed db
      * required for this test.
      */
     let setupIDBInFrames = (w, i, c) => {
--- a/toolkit/devtools/server/tests/browser/browser_storage_updates.js
+++ b/toolkit/devtools/server/tests/browser/browser_storage_updates.js
@@ -1,16 +1,13 @@
-const Cu = Components.utils;
-Cu.import("resource://gre/modules/Services.jsm");
-let tempScope = {};
-Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
-Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
-Cu.import("resource://gre/modules/Promise.jsm", tempScope);
-let {DebuggerServer, DebuggerClient, Promise} = tempScope;
-tempScope = null;
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
 
 const {StorageFront} = require("devtools/server/actors/storage");
 let gTests;
 let gExpected;
 let index = 0;
 
 const beforeReload = {
   cookies: ["test1.example.org", "sectest1.example.org"],
@@ -20,17 +17,17 @@ const beforeReload = {
 
 function finishTests(client) {
   // Forcing GC/CC to get rid of docshells and windows created by this test.
   forceCollections();
   client.close(() => {
     forceCollections();
     DebuggerServer.destroy();
     forceCollections();
-    DebuggerClient = DebuggerServer = gTests = null;
+    gTests = null;
     finish();
   });
 }
 
 function markOutMatched(toBeEmptied, data, deleted) {
   if (!Object.keys(toBeEmptied).length) {
     info("Object empty")
     return;
@@ -227,29 +224,20 @@ function* UpdateTests(front, win, client
   front.off("stores-cleared", onStoresCleared);
   front.off("stores-update", onStoresUpdate);
   finishTests(client);
 }
 
 
 function test() {
   addTab(MAIN_DOMAIN + "storage-updates.html").then(function(doc) {
-    try {
-      // Sometimes debugger server does not get destroyed correctly by previous
-      // tests.
-      DebuggerServer.destroy();
-    } catch (ex) { }
-    DebuggerServer.init(function () { return true; });
-    DebuggerServer.addBrowserActors();
+    initDebuggerServer();
 
     let client = new DebuggerClient(DebuggerServer.connectPipe());
-    client.connect(function onConnect() {
-      client.listTabs(function onListTabs(aResponse) {
-        let form = aResponse.tabs[aResponse.selected];
-        let front = StorageFront(client, form);
-        gTests = UpdateTests(front, doc.defaultView.wrappedJSObject,
-                             client);
-        // Make an initial call to initialize the actor
-        front.listStores().then(() => gTests.next());
-      });
+    connectDebuggerClient(client).then(form => {
+      let front = StorageFront(client, form);
+      gTests = UpdateTests(front, doc.defaultView.wrappedJSObject,
+                           client);
+      // Make an initial call to initialize the actor
+      front.listStores().then(() => gTests.next());
     });
   })
 }
--- a/toolkit/devtools/server/tests/browser/browser_timeline.js
+++ b/toolkit/devtools/server/tests/browser/browser_timeline.js
@@ -1,34 +1,26 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
+// Test that the timeline front's start/stop/isRecording methods work in a
+// simple use case, and that markers events are sent when operations occur.
+
+const {TimelineFront} = require("devtools/server/actors/timeline");
+
 let test = asyncTest(function*() {
-  const {TimelineFront} = require("devtools/server/actors/timeline");
-  const Cu = Components.utils;
-  let tempScope = {};
-  Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
-  Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
-  let {DebuggerServer, DebuggerClient} = tempScope;
-
   let doc = yield addTab("data:text/html;charset=utf-8,mop");
 
-  DebuggerServer.init(function () { return true; });
-  DebuggerServer.addBrowserActors();
+  initDebuggerServer();
   let client = new DebuggerClient(DebuggerServer.connectPipe());
-  let onListTabs = promise.defer();
-  client.connect(() => {
-    client.listTabs(onListTabs.resolve);
-  });
 
-  let listTabs = yield onListTabs.promise;
-
-  let form = listTabs.tabs[listTabs.selected];
+  let form = yield connectDebuggerClient(client);
   let front = TimelineFront(client, form);
 
   let isActive = yield front.isRecording();
   ok(!isActive, "Not initially recording");
 
   doc.body.innerHeight; // flush any pending reflow
 
   yield front.start();
@@ -54,14 +46,11 @@ let test = asyncTest(function*() {
   ok(markers.some(m => m.name == "Paint"), "markers includes Paint");
   ok(markers.some(m => m.name == "Styles"), "markers includes Restyle");
 
   yield front.stop();
 
   isActive = yield front.isRecording();
   ok(!isActive, "Not recording after stop()");
 
-  let onClose = promise.defer();
-  client.close(onClose.resolve);
-  yield onClose;
-
+  yield closeDebuggerClient(client);
   gBrowser.removeCurrentTab();
 });
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_timeline_iframes.js
@@ -0,0 +1,41 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the timeline front receives markers events for operations that occur in
+// iframes.
+
+const {TimelineFront} = require("devtools/server/actors/timeline");
+
+let test = asyncTest(function*() {
+  let doc = yield addTab(MAIN_DOMAIN + "timeline-iframe-parent.html");
+
+  initDebuggerServer();
+  let client = new DebuggerClient(DebuggerServer.connectPipe());
+  let form = yield connectDebuggerClient(client);
+  let front = TimelineFront(client, form);
+
+  info("Start timeline marker recording");
+  yield front.start();
+
+  // Check that we get markers for a few iterations of the timer that runs in
+  // the child frame.
+  for (let i = 0; i < 3; i ++) {
+    yield wait(300); // That's the time the child frame waits before changing styles.
+    let markers = yield once(front, "markers");
+    ok(markers.length, "Markers were received for operations in the child frame");
+  }
+
+  info("Stop timeline marker recording");
+  yield front.stop();
+  yield closeDebuggerClient(client);
+  gBrowser.removeCurrentTab();
+});
+
+function wait(ms) {
+  let def = promise.defer();
+  setTimeout(def.resolve, ms);
+  return def.promise;
+}
--- a/toolkit/devtools/server/tests/browser/head.js
+++ b/toolkit/devtools/server/tests/browser/head.js
@@ -1,28 +1,33 @@
 /* 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/. */
-let tempScope = {};
-Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
-Cu.import("resource://gre/modules/devtools/Console.jsm", tempScope);
-const require = tempScope.devtools.require;
-const console = tempScope.console;
-tempScope = null;
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+const {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {devtools: {require}} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+const {DebuggerClient} = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
+const {DebuggerServer} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+
 const PATH = "browser/toolkit/devtools/server/tests/browser/";
 const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
 const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
 const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
-const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
 
-// All test are asynchronous
+// All tests are asynchronous.
 waitForExplicitFinish();
 
 /**
- * Define an async test based on a generator function
+ * Define an async test based on a generator function.
  */
 function asyncTest(generator) {
   return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
 }
 
 /**
  * Add a new test tab in the browser and load the given url.
  * @param {String} url The url to be loaded in the new tab
@@ -42,16 +47,53 @@ let addTab = Task.async(function* (url) 
   let isBlank = url == "about:blank";
   waitForFocus(def.resolve, content, isBlank);
 
   yield def.promise;
 
   return tab.linkedBrowser.contentWindow.document;
 });
 
+function initDebuggerServer() {
+  try {
+    // Sometimes debugger server does not get destroyed correctly by previous
+    // tests.
+    DebuggerServer.destroy();
+  } catch (ex) { }
+  DebuggerServer.init(() => true);
+  DebuggerServer.addBrowserActors();
+}
+
+/**
+ * Connect a debugger client.
+ * @param {DebuggerClient}
+ * @return {Promise} Resolves to the selected tabActor form when the client is
+ * connected.
+ */
+function connectDebuggerClient(client) {
+  let def = promise.defer();
+  client.connect(() => {
+    client.listTabs(tabs => {
+      def.resolve(tabs.tabs[tabs.selected]);
+    });
+  });
+  return def.promise;
+}
+
+/**
+ * Close a debugger client's connection.
+ * @param {DebuggerClient}
+ * @return {Promise} Resolves when the connection is closed.
+ */
+function closeDebuggerClient(client) {
+  let def = promise.defer();
+  client.close(def.resolve);
+  return def.promise;
+}
+
 /**
  * Wait for eventName on target.
  * @param {Object} target An observable object that either supports on/off or
  * addEventListener/removeEventListener
  * @param {String} eventName
  * @param {Boolean} useCapture Optional, for addEventListener/removeEventListener
  * @return A promise that resolves when the event has been handled
  */
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/timeline-iframe-child.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Timeline iframe test - child frame</title>
+</head>
+<body>
+  <h1>Child frame</h1>
+  <script>
+    var h1 = document.querySelector("h1");
+    setInterval(function() {
+      h1.style.backgroundColor = "rgb(" + ((Math.random()*255)|0) + "," +
+                                          ((Math.random()*255)|0) + "," +
+                                          ((Math.random()*255)|0) +")";
+      h1.style.width = ((Math.random()*500)|0) + "px";
+    }, 300);
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/timeline-iframe-parent.html
@@ -0,0 +1,11 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Timeline iframe test - parent frame</title>
+</head>
+<body>
+  <h1>Parent frame</h1>
+  <iframe src="timeline-iframe-child.html"></iframe>
+</body>
+</html>
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -546,16 +546,17 @@ PopupNotifications.prototype = {
         gNotificationParents.set(popupnotification, popupnotification.parentNode);
       else
         popupnotification = doc.createElementNS(XUL_NS, "popupnotification");
 
       popupnotification.setAttribute("label", n.message);
       popupnotification.setAttribute("id", popupnotificationID);
       popupnotification.setAttribute("popupid", n.id);
       popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._dismiss();");
+      popupnotification.setAttribute("noautofocus", "true");
       if (n.mainAction) {
         popupnotification.setAttribute("buttonlabel", n.mainAction.label);
         popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
         popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
         popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
         popupnotification.setAttribute("closeitemcommand", "PopupNotifications._dismiss();event.stopPropagation();");
       } else {
         popupnotification.removeAttribute("buttonlabel");