Merge fx-team to m-c a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Thu, 26 Mar 2015 17:22:05 -0700
changeset 264746 e046475a75cb2dbcce6ebc3cd012efd7385a2142
parent 264705 47c1e9a82e01b849845608d10ab4dfc8da9274c0 (current diff)
parent 264745 ea2a95df5a621e5cb2f920cec07aff84294fdc28 (diff)
child 264754 ec7f4206645965556622841b3c544727576defab
child 264797 9ee3773c1ead9b16df3b03e27c556283e2faa8c3
child 264917 2446d768506cab77e9fb052540107e573d21a3ea
push id4718
push userraliiev@mozilla.com
push dateMon, 11 May 2015 18:39:53 +0000
treeherdermozilla-beta@c20c4ef55f08 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
e046475a75cb / 39.0a1 / 20150327030212 / files
nightly linux64
e046475a75cb / 39.0a1 / 20150327030212 / files
nightly mac
e046475a75cb / 39.0a1 / 20150327030212 / files
nightly win32
e046475a75cb / 39.0a1 / 20150327030212 / files
nightly win64
e046475a75cb / 39.0a1 / 20150327030212 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge CLOSED TREE
toolkit/components/places/tests/unit/test_398914.js
toolkit/devtools/server/tests/mochitest/test_attachProcess.html
--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -214,20 +214,16 @@ const gXPInstallObserver = {
       cancelButton.label = gNavigatorBundle.getString("addonInstall.cancelButton.label");
       cancelButton.accessKey = gNavigatorBundle.getString("addonInstall.cancelButton.accesskey");
 
       let acceptButton = document.getElementById("addon-install-confirmation-accept");
       acceptButton.label = gNavigatorBundle.getString("addonInstall.acceptButton.label");
       acceptButton.accessKey = gNavigatorBundle.getString("addonInstall.acceptButton.accesskey");
 
       let showNotification = () => {
-        // The download may have been cancelled during the security delay
-        if (!PopupNotifications.getNotification("addon-progress", browser))
-          return;
-
         let tab = gBrowser.getTabForBrowser(browser);
         if (tab)
           gBrowser.selectedTab = tab;
 
         if (PopupNotifications.isPanelOpen) {
           let rect = document.getElementById("addon-progress-notification").getBoundingClientRect();
           let notification = document.getElementById("addon-install-confirmation-notification");
           notification.style.minHeight = rect.height + "px";
@@ -238,25 +234,30 @@ const gXPInstallObserver = {
 
         this._removeProgressNotification(browser);
 
         Services.telemetry
                 .getHistogramById("SECURITY_UI")
                 .add(Ci.nsISecurityUITelemetry.WARNING_CONFIRM_ADDON_INSTALL);
       };
 
-      let downloadDuration = 0;
       let progressNotification = PopupNotifications.getNotification("addon-progress", browser);
-      if (progressNotification)
-        downloadDuration = Date.now() - progressNotification._startTime;
-      let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
-      if (securityDelay > 0)
-        setTimeout(showNotification, securityDelay);
-      else
-        showNotification();
+      if (progressNotification) {
+        let downloadDuration = Date.now() - progressNotification._startTime;
+        let securityDelay = Services.prefs.getIntPref("security.dialog_enable_delay") - downloadDuration;
+        if (securityDelay > 0) {
+          setTimeout(() => {
+            // The download may have been cancelled during the security delay
+            if (PopupNotifications.getNotification("addon-progress", browser))
+              showNotification();
+          }, securityDelay);
+          break;
+        }
+      }
+      showNotification();
       break; }
     case "addon-install-complete": {
       let needsRestart = installInfo.installs.some(function(i) {
         return i.addon.pendingOperations != AddonManager.PENDING_NONE;
       });
 
       if (needsRestart) {
         messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
--- a/browser/base/content/newtab/newTab.css
+++ b/browser/base/content/newtab/newTab.css
@@ -257,38 +257,42 @@ input[type=button] {
   left: 0;
   right: auto;
 }
 
 .newtab-site:-moz-any([type=enhanced], [type=sponsored]) .newtab-sponsored {
   display: block;
 }
 
-.newtab-site[type=related] .newtab-suggested {
+.newtab-site[suggested] .newtab-suggested {
   display: table;
 }
 
 .sponsored-explain,
-.sponsored-explain a {
+.sponsored-explain a,
+.suggested-explain,
+.suggested-explain a {
   color: white;
 }
 
-.sponsored-explain {
+.sponsored-explain,
+.suggested-explain {
   background-color: rgba(51, 51, 51, 0.95);
   border-bottom-left-radius: 6px;
   border-bottom-right-radius: 6px;
   bottom: 0px;
   line-height: 20px;
   padding: 15px 10px;
   position: absolute;
   text-align: start;
 }
 
 #newtab-intro-panel input,
-.sponsored-explain input {
+.sponsored-explain input,
+.suggested-explain input {
   background-size: 18px;
   height: 18px;
   opacity: 1;
   pointer-events: none;
   position: static;
   width: 18px;
 }
 
--- a/browser/base/content/newtab/sites.js
+++ b/browser/base/content/newtab/sites.js
@@ -127,16 +127,17 @@ Site.prototype = {
 
     let link = this._querySelector(".newtab-link");
     link.setAttribute("title", tooltip);
     link.setAttribute("href", url);
     this._querySelector(".newtab-title").textContent = title;
     this.node.setAttribute("type", this.link.type);
 
     if (this.link.targetedSite) {
+      this.node.setAttribute("suggested", true);
       let targetedSite = `<strong> ${this.link.targetedSite} </strong>`;
       this._querySelector(".newtab-suggested").innerHTML =
         `<div class='newtab-suggested-bounds'> ${newTabString("suggested.button", [targetedSite])} </div>`;
     }
 
     if (this.isPinned())
       this._updateAttributes(true);
     // Capture the page if the thumbnail is missing, which will cause page.js
@@ -228,34 +229,34 @@ Site.prototype = {
       // We only want to get indices for the default configuration, everything
       // else goes in the same bucket.
       aIndex = 9;
     }
     Services.telemetry.getHistogramById("NEWTAB_PAGE_SITE_CLICKED")
                       .add(aIndex);
   },
 
-  _toggleSponsored: function() {
-    let button = this._querySelector(".newtab-sponsored");
+  _toggleLegalText: function(buttonClass, explanationTextClass) {
+    let button = this._querySelector(buttonClass);
     if (button.hasAttribute("active")) {
-      let explain = this._querySelector(".sponsored-explain");
+      let explain = this._querySelector(explanationTextClass);
       explain.parentNode.removeChild(explain);
 
       button.removeAttribute("active");
     }
     else {
       let explain = document.createElementNS(HTML_NAMESPACE, "div");
-      explain.className = "sponsored-explain";
+      explain.className = explanationTextClass.slice(1); // Slice off the first character, '.'
       this.node.appendChild(explain);
 
       let link = '<a href="' + TILES_EXPLAIN_LINK + '">' +
                  newTabString("learn.link") + "</a>";
-      let type = this.node.getAttribute("type");
+      let type = this.node.getAttribute("suggested") ? "suggested" : this.node.getAttribute("type");
       let icon = '<input type="button" class="newtab-control newtab-' +
-                 (type == "sponsored" ? "control-block" : "customize") + '"/>';
+                 (type == "enhanced" ? "customize" : "control-block") + '"/>';
       explain.innerHTML = newTabString(type + ".explain", [icon, link]);
 
       button.setAttribute("active", "true");
     }
   },
 
   /**
    * Handles site click events.
@@ -274,34 +275,38 @@ Site.prototype = {
         this._recordSiteClicked(tileIndex);
         action = "click";
       }
     }
     // Handle sponsored explanation link click
     else if (target.parentElement.classList.contains("sponsored-explain")) {
       action = "sponsored_link";
     }
+    else if (target.parentElement.classList.contains("suggested-explain")) {
+      action = "suggested_link";
+    }
     // Only handle primary clicks for the remaining targets
     else if (button == 0) {
-      if (target.parentElement.classList.contains("newtab-suggested") ||
-          target.classList.contains("newtab-suggested")) {
-        // Suggested explanation text should do nothing when clicked and
-        // the link in the suggested explanation should act as default.
-        return;
-      }
       aEvent.preventDefault();
       if (target.classList.contains("newtab-control-block")) {
         this.block();
         action = "block";
       }
       else if (target.classList.contains("sponsored-explain") ||
                target.classList.contains("newtab-sponsored")) {
-        this._toggleSponsored();
+        this._toggleLegalText(".newtab-sponsored", ".sponsored-explain");
         action = "sponsored";
       }
+      else if (target.classList.contains("suggested-explain") ||
+               target.classList.contains("newtab-suggested-bounds") ||
+               target.parentElement.classList.contains("newtab-suggested-bounds") ||
+               target.classList.contains("newtab-suggested")) {
+        this._toggleLegalText(".newtab-suggested", ".suggested-explain");
+        action = "suggested";
+      }
       else if (pinned) {
         this.unpin();
         action = "unpin";
       }
       else {
         this.pin();
         action = "pin";
       }
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5518,18 +5518,16 @@
           let switchPromise = gBrowser._prepareForTabSwitch(toTab, fromTab);
 
           var panel = this._selectedPanel;
           var newPanel = this.childNodes[val];
           this._selectedPanel = newPanel;
           if (this._selectedPanel != panel) {
             var event = document.createEvent("Events");
             event.initEvent("select", true, true);
-            event.fromTab = fromTab;
-            event.toTab = toTab;
             this.dispatchEvent(event);
 
             this._selectedIndex = val;
 
             switchPromise.then(() => {
               // If we cannot find the tabpanel that we were trying to switch to, then
               // it must have been removed before our Promise could be resolved. In
               // that case, we just cancel the tab switch.
--- a/browser/base/content/test/general/browser_urlbarStop.js
+++ b/browser/base/content/test/general/browser_urlbarStop.js
@@ -20,50 +20,12 @@ add_task(function* () {
   gBrowser.removeCurrentTab();
 });
 
 function typeAndSubmitAndStop(url) {
   gBrowser.userTypedValue = url;
   URLBarSetURI();
   is(gURLBar.textValue, gURLBar.trimValue(url), "location bar reflects loading page");
 
-  let promise = waitForDocLoadAndStopIt();
+  let promise = waitForDocLoadAndStopIt(url);
   gURLBar.handleCommand();
   return promise;
 }
-
-function waitForDocLoadAndStopIt() {
-  function content_script() {
-    const {interfaces: Ci, utils: Cu} = Components;
-    Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-    let progressListener = {
-      onStateChange(webProgress, req, flags, status) {
-        if (flags & Ci.nsIWebProgressListener.STATE_START) {
-          wp.removeProgressListener(progressListener);
-
-          /* Hammer time. */
-          content.stop();
-
-          /* Let the parent know we're done. */
-          sendAsyncMessage("{MSG}");
-        }
-      },
-
-      QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
-    };
-
-    let wp = docShell.QueryInterface(Ci.nsIWebProgress);
-    wp.addProgressListener(progressListener, wp.NOTIFY_ALL);
-  }
-
-  return new Promise(resolve => {
-    const MSG = "test:waitForDocLoadAndStopIt";
-    const SCRIPT = content_script.toString().replace("{MSG}", MSG);
-
-    let mm = gBrowser.selectedBrowser.messageManager;
-    mm.loadFrameScript("data:,(" + SCRIPT + ")();", true);
-    mm.addMessageListener(MSG, function onComplete() {
-      mm.removeMessageListener(MSG, onComplete);
-      resolve();
-    });
-  });
-}
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -374,29 +374,45 @@ function waitForDocLoadAndStopIt(aExpect
   function content_script() {
     let { interfaces: Ci, utils: Cu } = Components;
     Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
     let wp = docShell.QueryInterface(Ci.nsIWebProgress);
 
     let progressListener = {
       onStateChange: function (webProgress, req, flags, status) {
         dump("waitForDocLoadAndStopIt: onStateChange " + flags.toString(16) + ": " + req.name + "\n");
-        let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
-                       Ci.nsIWebProgressListener.STATE_START;
-        if (((flags & docStart) == docStart) && webProgress.isTopLevel) {
-          dump("waitForDocLoadAndStopIt: Document start: " +
-               req.QueryInterface(Ci.nsIChannel).URI.spec + "\n");
-          req.cancel(Components.results.NS_ERROR_FAILURE);
+
+        if (webProgress.isTopLevel &&
+            flags & Ci.nsIWebProgressListener.STATE_START) {
           wp.removeProgressListener(progressListener);
-          sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri: req.originalURI.spec });
+
+          let chan = req.QueryInterface(Ci.nsIChannel);
+          dump(`waitForDocLoadAndStopIt: Document start: ${chan.URI.spec}\n`);
+
+          /* Hammer time. */
+          content.stop();
+
+          /* Let the parent know we're done. */
+          sendAsyncMessage("Test:WaitForDocLoadAndStopIt", { uri: chan.originalURI.spec });
         }
       },
       QueryInterface: XPCOMUtils.generateQI(["nsISupportsWeakReference"])
     };
-    wp.addProgressListener(progressListener, wp.NOTIFY_ALL);
+    wp.addProgressListener(progressListener, wp.NOTIFY_STATE_WINDOW);
+
+    /**
+     * As |this| is undefined and we can't extend |docShell|, adding an unload
+     * event handler is the easiest way to ensure the weakly referenced
+     * progress listener is kept alive as long as necessary.
+     */
+    addEventListener("unload", function () {
+      try {
+        wp.removeProgressListener(progressListener);
+      } catch (e) { /* Will most likely fail. */ }
+    });
   }
 
   return new Promise((resolve, reject) => {
     function complete({ data }) {
       is(data.uri, aExpectedURL, "waitForDocLoadAndStopIt: The expected URL was loaded");
       mm.removeMessageListener("Test:WaitForDocLoadAndStopIt", complete);
       resolve();
     }
--- a/browser/base/content/test/newtab/browser_newtab_enhanced.js
+++ b/browser/base/content/test/newtab/browser_newtab_enhanced.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 gDirectorySource = "data:application/json," + JSON.stringify({
-  "en-US": [{
+  "directory": [{
     url: "http://example.com/",
     enhancedImageURI: "data:image/png;base64,helloWORLD",
     title: "title",
     type: "organic"
   }]
 });
 
 function runTests() {
--- a/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
+++ b/browser/base/content/test/newtab/browser_newtab_reportLinkAction.js
@@ -1,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const PRELOAD_PREF = "browser.newtab.preload";
 
 gDirectorySource = "data:application/json," + JSON.stringify({
-  "en-US": [{
+  "directory": [{
     url: "http://example.com/organic",
     type: "organic"
   }, {
     url: "http://localhost/sponsored",
     type: "sponsored"
   }]
 });
 
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -1112,17 +1112,18 @@ this.MozLoopService = {
       if (MozLoopServiceInternal.doNotDisturb || participant.owner) {
         return;
       }
 
       let window = gWM.getMostRecentWindow("navigator:browser");
       if (window) {
         window.LoopUI.showNotification({
           sound: "room-joined",
-          title: room.roomName,
+          // Fallback to the brand short name if the roomName isn't available.
+          title: room.roomName || MozLoopServiceInternal.localizedStrings.get("clientShortname2"),
           message: MozLoopServiceInternal.localizedStrings.get("rooms_room_joined_label"),
           selectTab: "rooms"
         });
       }
     });
 
     LoopRooms.on("joined", this.maybeResumeTourOnRoomJoined.bind(this));
 
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -368,30 +368,30 @@ loop.shared.actions = (function() {
 
     /**
      * Sets up the room information when it is received.
      * XXX: should move to some roomActions module - refs bug 1079284
      *
      * @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
      */
     SetupRoomInfo: Action.define("setupRoomInfo", {
-      roomName: String,
+      // roomName: String - Optional.
       roomOwner: String,
       roomToken: String,
       roomUrl: String
     }),
 
     /**
      * Updates the room information when it is received.
      * XXX: should move to some roomActions module - refs bug 1079284
      *
      * @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
      */
     UpdateRoomInfo: Action.define("updateRoomInfo", {
-      roomName: String,
+      // roomName: String - Optional.
       roomOwner: String,
       roomUrl: String
     }),
 
     /**
      * Starts the process for the user to join the room.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
--- a/browser/components/loop/content/shared/js/roomStore.js
+++ b/browser/components/loop/content/shared/js/roomStore.js
@@ -32,17 +32,17 @@ loop.store = loop.store || {};
 
   /**
    * Room validation schema. See validate.js.
    * @type {Object}
    */
   var roomSchema = {
     roomToken:    String,
     roomUrl:      String,
-    roomName:     String,
+    // roomName:     String - Optional.
     maxSize:      Number,
     participants: Array,
     ctime:        Number
   };
 
   /**
    * Room type. Basically acts as a typed object constructor.
    *
--- a/browser/components/loop/standalone/content/js/standaloneMozLoop.js
+++ b/browser/components/loop/standalone/content/js/standaloneMozLoop.js
@@ -88,17 +88,16 @@ loop.StandaloneMozLoop = (function(mozL1
           }
         }.bind(this)
       });
 
       req.done(function(responseData) {
         try {
           // We currently only require things we need rather than everything possible.
           callback(null, validate(responseData, {
-            roomName: String,
             roomOwner: String,
             roomUrl: String
           }));
         } catch (err) {
           console.error("Error requesting call info", err.message);
           callback(err);
         }
       }.bind(this));
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -40,24 +40,21 @@
       <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field>
 
       <!-- Check if we should hide the drop indicator for the target -->
       <method name="_hideDropIndicator">
         <parameter name="aEvent"/>
         <body><![CDATA[
           let target = aEvent.target;
 
-          // Don't draw the drop indicator outside of markers.
-          // The markers are hidden, since otherwise sometimes popups acquire
-          // scrollboxes on OS X, so we can't use them directly.
-          let firstChildTop = this._startMarker.nextSibling.boxObject.y;
-          let lastChildBottom = this._endMarker.previousSibling.boxObject.y +
-                                this._endMarker.previousSibling.boxObject.height;
-          let betweenMarkers = target.boxObject.y >= firstChildTop ||
-                               target.boxObject.y <= lastChildBottom;
+          // Don't draw the drop indicator outside of markers or if current
+          // node is not a Places node.
+          let betweenMarkers =
+            (this._startMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) &&
+            (this._endMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING);
 
           // Hide the dropmarker if current node is not a Places node.
           return !(target && target._placesNode && betweenMarkers);
         ]]></body>
       </method>
 
       <!-- This function returns information about where to drop when
            dragging over this popup insertion point -->
--- a/browser/components/readinglist/test/xpcshell/test_Sync.js
+++ b/browser/components/readinglist/test/xpcshell/test_Sync.js
@@ -260,17 +260,20 @@ MockClient.prototype = {
       }
       for (let prop in body) {
         item[prop] = body[prop];
       }
       item.last_modified = this._nextLastModifiedToken++;
       return new MockResponse(200, item);
     },
 
-    delete(body, routeMatch) {
+    // There's a bug in pre-39's ES strict mode around forbidding the
+    // redefinition of reserved keywords that flags defining `delete` on an
+    // object as a syntax error.  This weird syntax works around that.
+    ["delete"](body, routeMatch) {
       let id = routeMatch[1];
       let item = this.itemByID(id);
       if (!item) {
         return new MockResponse(404);
       }
       item.deleted = true;
       return new MockResponse(200);
     },
--- a/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js
+++ b/browser/devtools/debugger/test/browser_dbg_chrome-debugging.js
@@ -38,17 +38,17 @@ function test() {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     testChromeActor();
   });
 }
 
 function testChromeActor() {
-  gClient.attachProcess().then(aResponse => {
+  gClient.getProcess().then(aResponse => {
     gClient.addListener("newGlobal", onNewGlobal);
     gClient.addListener("newSource", onNewSource);
 
     let actor = aResponse.form.actor;
     gClient.attachTab(actor, (response, tabClient) => {
       tabClient.attachThread(null, (aResponse, aThreadClient) => {
         gThreadClient = aThreadClient;
 
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -124,23 +124,23 @@ let onConnectionReady = Task.async(funct
   // Add one entry for each open tab.
   for (let i = 0; i < response.tabs.length; i++) {
     buildTabLink(response.tabs[i], parent, i == response.selected);
   }
 
   let gParent = document.getElementById("globalActors");
 
   // Build the Remote Process button
-  // If Fx<37, tab actors were used to be exposed on RootActor
-  // but in Fx>=37, chrome is debuggable via attachProcess() and ChromeActor
+  // If Fx<39, tab actors were used to be exposed on RootActor
+  // but in Fx>=39, chrome is debuggable via getProcess() and ChromeActor
   if (globals.consoleActor || gClient.mainRoot.traits.allowChromeProcess) {
     let a = document.createElement("a");
     a.onclick = function() {
       if (gClient.mainRoot.traits.allowChromeProcess) {
-        gClient.attachProcess()
+        gClient.getProcess()
                .then(aResponse => {
                  openToolbox(aResponse.form, true);
                });
       } else if (globals.consoleActor) {
         openToolbox(globals, true, "webconsole", false);
       }
     }
     a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -744,17 +744,17 @@ let gDevToolsBrowser = {
         let contentProcesses = response.processes.filter(p => (!p.parent));
         if (contentProcesses.length < 1) {
           let msg = bundle.GetStringFromName("toolbox.noContentProcess.message");
           Services.prompt.alert(null, "", msg);
           deferred.reject("No content processes available.");
           return;
         }
         // Otherwise, arbitrary connect to the unique content process.
-        client.attachProcess(contentProcesses[0].id)
+        client.getProcess(contentProcesses[0].id)
               .then(response => {
                 let options = {
                   form: response.form,
                   client: client,
                   chrome: true,
                   isTabActor: false
                 };
                 return devtools.TargetFactory.forRemoteTab(options);
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -421,36 +421,19 @@ TabTarget.prototype = {
         this.activeTab = aTabClient;
         this.threadActor = aResponse.threadActor;
         this._remote.resolve(null);
       });
     };
 
     if (this.isLocalTab) {
       this._client.connect((aType, aTraits) => {
-        this._client.listTabs(aResponse => {
-          this._root = aResponse;
-
-          if (this.window) {
-            let windowUtils = this.window
-              .QueryInterface(Ci.nsIInterfaceRequestor)
-              .getInterface(Ci.nsIDOMWindowUtils);
-            let outerWindow = windowUtils.outerWindowID;
-            aResponse.tabs.some((tab) => {
-              if (tab.outerWindowID === outerWindow) {
-                this._form = tab;
-                return true;
-              }
-              return false;
-            });
-          }
-
-          if (!this._form) {
-            this._form = aResponse.tabs[aResponse.selected];
-          }
+        this._client.getTab({ tab: this.tab })
+            .then(aResponse => {
+          this._form = aResponse.tab;
           attachTab();
         });
       });
     } else if (this.isTabActor) {
       // In the remote debugging case, the protocol connection will have been
       // already initialized in the connection screen code.
       attachTab();
     } else {
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -17,16 +17,17 @@ support-files =
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_target_support.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
 [browser_toolbox_getpanelwhenready.js]
 [browser_toolbox_highlight.js]
 [browser_toolbox_hosts.js]
+[browser_toolbox_hosts_size.js]
 [browser_toolbox_options.js]
 [browser_toolbox_options_disable_buttons.js]
 [browser_toolbox_options_disable_cache-01.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_cache-02.js]
 skip-if = e10s # Bug 1030318
 [browser_toolbox_options_disable_js.js]
 skip-if = e10s # Bug 1030318
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_toolbox_hosts_size.js
@@ -0,0 +1,69 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that getPanelWhenReady returns the correct panel in promise
+// resolutions regardless of whether it has opened first.
+
+const URL = "data:text/html;charset=utf8,test for host sizes";
+
+add_task(function*() {
+  // Set size prefs to make the hosts way too big, so that the size has
+  // to be clamped to fit into the browser window.
+  Services.prefs.setIntPref("devtools.toolbox.footer.height", 10000);
+  Services.prefs.setIntPref("devtools.toolbox.sidebar.width", 10000);
+
+  let tab = yield addTab(URL);
+  let nbox = gBrowser.getNotificationBox();
+  let {clientHeight: nboxHeight, clientWidth: nboxWidth} = nbox;
+  let toolbox = yield gDevTools.showToolbox(TargetFactory.forTab(tab));
+
+  is (nbox.clientHeight, nboxHeight, "Opening the toolbox hasn't changed the height of the nbox");
+  is (nbox.clientWidth, nboxWidth, "Opening the toolbox hasn't changed the width of the nbox");
+
+  let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
+  is (iframe.clientHeight, nboxHeight - 10, "The iframe fits within the available space ");
+
+  yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
+  iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
+  iframe.style.minWidth = "1px"; // Disable the min width set in css
+  is (iframe.clientWidth, nboxWidth - 10, "The iframe fits within the available space");
+
+  yield cleanup(toolbox);
+});
+
+add_task(function*() {
+  // Set size prefs to something reasonable, so we can check to make sure
+  // they are being set properly.
+  Services.prefs.setIntPref("devtools.toolbox.footer.height", 100);
+  Services.prefs.setIntPref("devtools.toolbox.sidebar.width", 100);
+
+  let tab = yield addTab(URL);
+  let nbox = gBrowser.getNotificationBox();
+  let {clientHeight: nboxHeight, clientWidth: nboxWidth} = nbox;
+  let toolbox = yield gDevTools.showToolbox(TargetFactory.forTab(tab));
+
+  is (nbox.clientHeight, nboxHeight, "Opening the toolbox hasn't changed the height of the nbox");
+  is (nbox.clientWidth, nboxWidth, "Opening the toolbox hasn't changed the width of the nbox");
+
+  let iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-bottom-iframe");
+  is (iframe.clientHeight, 100, "The iframe is resized properly");
+
+  yield toolbox.switchHost(devtools.Toolbox.HostType.SIDE);
+  iframe = document.getAnonymousElementByAttribute(nbox, "class", "devtools-toolbox-side-iframe");
+  iframe.style.minWidth = "1px"; // Disable the min width set in css
+  is (iframe.clientWidth, 100, "The iframe is resized properly");
+
+  yield cleanup(toolbox);
+});
+
+function* cleanup(toolbox) {
+  Services.prefs.clearUserPref("devtools.toolbox.host");
+  Services.prefs.clearUserPref("devtools.toolbox.footer.height");
+  Services.prefs.clearUserPref("devtools.toolbox.sidebar.width");
+  yield toolbox.destroy();
+  gBrowser.removeCurrentTab();
+}
--- a/browser/devtools/framework/test/browser_two_tabs.js
+++ b/browser/devtools/framework/test/browser_two_tabs.js
@@ -49,21 +49,74 @@ function connect() {
   // Connect to debugger server to fetch the two tab actors
   gClient = new DebuggerClient(DebuggerServer.connectPipe());
   gClient.connect(() => {
     gClient.listTabs(response => {
       // Fetch the tab actors for each tab
       gTabActor1 = response.tabs.filter(a => a.url === TAB_URL_1)[0];
       gTabActor2 = response.tabs.filter(a => a.url === TAB_URL_2)[0];
 
-      checkSelectedTabActor();
+      checkGetTab();
     });
   });
 }
 
+function checkGetTab() {
+  gClient.getTab({tab: gTab1})
+         .then(response => {
+           is(JSON.stringify(gTabActor1), JSON.stringify(response.tab),
+              "getTab returns the same tab grip for first tab");
+         })
+         .then(() => {
+           let filter = {};
+           // Filter either by tabId or outerWindowID,
+           // if we are running tests OOP or not.
+           if (gTab1.linkedBrowser.frameLoader.tabParent) {
+             filter.tabId = gTab1.linkedBrowser.frameLoader.tabParent.tabId;
+           } else {
+             let windowUtils = gTab1.linkedBrowser.contentWindow
+               .QueryInterface(Ci.nsIInterfaceRequestor)
+               .getInterface(Ci.nsIDOMWindowUtils);
+             filter.outerWindowID = windowUtils.outerWindowID;
+           }
+           return gClient.getTab(filter);
+         })
+         .then(response => {
+           is(JSON.stringify(gTabActor1), JSON.stringify(response.tab),
+              "getTab returns the same tab grip when filtering by tabId/outerWindowID");
+         })
+         .then(() => gClient.getTab({tab: gTab2}))
+         .then(response => {
+           is(JSON.stringify(gTabActor2), JSON.stringify(response.tab),
+              "getTab returns the same tab grip for second tab");
+         })
+         .then(checkGetTabFailures);
+}
+
+function checkGetTabFailures() {
+  gClient.getTab({ tabId: -999 })
+    .then(
+      response => ok(false, "getTab unexpectedly succeed with a wrong tabId"),
+      response => {
+        is(response.error, "noTab");
+        is(response.message, "Unable to find tab with tabId '-999'");
+      }
+    )
+    .then(() => gClient.getTab({ outerWindowID: -999 }))
+    .then(
+      response => ok(false, "getTab unexpectedly succeed with a wrong outerWindowID"),
+      response => {
+        is(response.error, "noTab");
+        is(response.message, "Unable to find tab with outerWindowID '-999'");
+      }
+    )
+    .then(checkSelectedTabActor);
+
+}
+
 function checkSelectedTabActor() {
   // Send a naive request to the second tab actor
   // to check if it works
   gClient.request({ to: gTabActor2.consoleActor, type: "startListeners", listeners: [] }, aResponse => {
     ok("startedListeners" in aResponse, "Actor from the selected tab should respond to the request.");
 
     closeSecondTab();
   });
--- a/browser/devtools/framework/test/head.js
+++ b/browser/devtools/framework/test/head.js
@@ -161,17 +161,17 @@ function getChromeActors(callback)
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
     DebuggerServer.addBrowserActors();
   }
   DebuggerServer.allowChromeProcess = true;
 
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   client.connect(() => {
-    client.attachProcess().then(response => {
+    client.getProcess().then(response => {
       callback(client, response.form);
     });
   });
 
   SimpleTest.registerCleanupFunction(() => {
     DebuggerServer.destroy();
   });
 }
--- a/browser/devtools/framework/toolbox-hosts.js
+++ b/browser/devtools/framework/toolbox-hosts.js
@@ -43,25 +43,28 @@ BottomHost.prototype = {
   /**
    * Create a box at the bottom of the host tab.
    */
   create: function BH_create() {
     let deferred = promise.defer();
 
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
+    this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.className = "devtools-toolbox-bottom-iframe";
-    this.frame.height = Services.prefs.getIntPref(this.heightPref);
+    this.frame.height = Math.min(
+      Services.prefs.getIntPref(this.heightPref),
+      this._nbox.clientHeight - 10 // Always show at least some page content
+    );
 
-    this._nbox = gBrowser.getNotificationBox(this.hostTab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this.frame);
 
     let frameLoad = () => {
       this.emit("ready", this.frame);
       deferred.resolve(this.frame);
     };
 
@@ -126,25 +129,29 @@ SidebarHost.prototype = {
   /**
    * Create a box in the sidebar of the host tab.
    */
   create: function SH_create() {
     let deferred = promise.defer();
 
     let gBrowser = this.hostTab.ownerDocument.defaultView.gBrowser;
     let ownerDocument = gBrowser.ownerDocument;
+    this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-side-splitter");
 
     this.frame = ownerDocument.createElement("iframe");
     this.frame.className = "devtools-toolbox-side-iframe";
-    this.frame.width = Services.prefs.getIntPref(this.widthPref);
 
-    this._sidebar = gBrowser.getSidebarContainer(this.hostTab.linkedBrowser);
+    this.frame.width = Math.min(
+      Services.prefs.getIntPref(this.widthPref),
+      this._sidebar.clientWidth - 10 // Always show at least some page content
+    );
+
     this._sidebar.appendChild(this._splitter);
     this._sidebar.appendChild(this.frame);
 
     let frameLoad = () => {
       this.emit("ready", this.frame);
       deferred.resolve(this.frame);
     };
 
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -36,17 +36,17 @@ let connect = Task.async(function*() {
     let addonID = getParameterByName("addonID");
 
     if (addonID) {
       gClient.listAddons(({addons}) => {
         let addonActor = addons.filter(addon => addon.id === addonID).pop();
         openToolbox({ form: addonActor, chrome: true, isTabActor: false });
       });
     } else {
-      gClient.attachProcess().then(aResponse => {
+      gClient.getProcess().then(aResponse => {
         openToolbox({ form: aResponse.form, chrome: true });
       });
     }
   });
 });
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -312,24 +312,26 @@ MarkupView.prototype = {
    * hidden, taking into account that there could already be highlighter requests
    * queued up
    */
   _hideBoxModel: function(forceHide) {
     return this._inspector.toolbox.highlighterUtils.unhighlight(forceHide);
   },
 
   _briefBoxModelTimer: null,
-  _brieflyShowBoxModel: function(nodeFront) {
-    let win = this._frame.contentWindow;
-
+
+  _clearBriefBoxModelTimer: function() {
     if (this._briefBoxModelTimer) {
       clearTimeout(this._briefBoxModelTimer);
       this._briefBoxModelTimer = null;
     }
-
+  },
+
+  _brieflyShowBoxModel: function(nodeFront) {
+    this._clearBriefBoxModelTimer();
     this._showBoxModel(nodeFront);
 
     this._briefBoxModelTimer = setTimeout(() => {
       this._hideBoxModel();
     }, NEW_SELECTION_HIGHLIGHTER_TIMER);
   },
 
   template: function(aName, aDest, aOptions={stack: "markup-view.xhtml"}) {
@@ -1374,16 +1376,17 @@ MarkupView.prototype = {
 
     this._destroyer = promise.resolve();
 
     // Note that if the toolbox is closed, this will work fine, but will fail
     // in case the browser is closed and will trigger a noSuchActor message.
     // We ignore the promise that |_hideBoxModel| returns, since we should still
     // proceed with the rest of destruction if it fails.
     this._hideBoxModel();
+    this._clearBriefBoxModelTimer();
 
     this._elt.removeEventListener("click", this._onMouseClick, false);
 
     this._hoveredNode = null;
     this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
 
     this.htmlEditor.destroy();
     this.htmlEditor = null;
--- a/browser/devtools/performance/test/browser_profiler_tree-model-04.js
+++ b/browser/devtools/performance/test/browser_profiler_tree-model-04.js
@@ -32,18 +32,21 @@ function test() {
   is(Object.keys(root.calls["http://A"].calls)[0], "https://E",
     "The '.A' node's only child call is correct.");
 
   is(Object.keys(root.calls["http://A"].calls["https://E"].calls).length, 1,
     "The correct number of child calls were calculated for the '.A.E' node.");
   is(Object.keys(root.calls["http://A"].calls["https://E"].calls)[0], "file://F",
     "The '.A.E' node's only child call is correct.");
 
-  is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls).length, 0,
+  is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls).length, 1,
     "The correct number of child calls were calculated for the '.A.E.F' node.");
+  is(Object.keys(root.calls["http://A"].calls["https://E"].calls["file://F"].calls)[0], "app://H",
+    "The '.A.E.F' node's only child call is correct.");
+
   is(Object.keys(root.calls["http://D"].calls).length, 0,
     "The correct number of child calls were calculated for the '.D' node.");
 
   finish();
 }
 
 let gSamples = [{
   time: 5,
@@ -54,25 +57,27 @@ let gSamples = [{
     { location: "http://C" }
   ]
 }, {
   time: 5 + 6,
   frames: [
     { location: "(root)" },
     { location: "chrome://A" },
     { location: "resource://B" },
+    { location: "jar:file://G" },
     { location: "http://D" }
   ]
 }, {
   time: 5 + 6 + 7,
   frames: [
     { location: "(root)" },
     { location: "http://A" },
     { location: "https://E" },
-    { location: "file://F" }
+    { location: "file://F" },
+    { location: "app://H" },
   ]
 }, {
   time: 5 + 6 + 7 + 8,
   frames: [
     { location: "(root)" },
     { location: "http://A" },
     { location: "http://B" },
     { location: "http://C" },
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -94,48 +94,45 @@ let OverviewView = {
 
   /**
    * Sets the time interval selection for all graphs in this overview.
    *
    * @param object interval
    *        The { startTime, endTime }, in milliseconds.
    */
   setTimeInterval: function(interval, options = {}) {
-    if (this.isDisabled()) {
-      return;
-    }
-
     let recording = PerformanceController.getCurrentRecording();
     if (recording == null) {
       throw new Error("A recording should be available in order to set the selection.");
     }
+    if (this.isDisabled()) {
+      return;
+    }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = { start: interval.startTime, end: interval.endTime };
     this._stopSelectionChangeEventPropagation = options.stopPropagation;
     this.markersOverview.setMappedSelection(selection, { mapStart, mapEnd });
     this._stopSelectionChangeEventPropagation = false;
   },
 
   /**
    * Gets the time interval selection for all graphs in this overview.
    *
    * @return object
    *         The { startTime, endTime }, in milliseconds.
    */
   getTimeInterval: function() {
     let recording = PerformanceController.getCurrentRecording();
-
+    if (recording == null) {
+      throw new Error("A recording should be available in order to get the selection.");
+    }
     if (this.isDisabled()) {
       return { startTime: 0, endTime: recording.getDuration() };
     }
-
-    if (recording == null) {
-      throw new Error("A recording should be available in order to get the selection.");
-    }
     let mapStart = () => 0;
     let mapEnd = () => recording.getDuration();
     let selection = this.markersOverview.getMappedSelection({ mapStart, mapEnd });
     return { startTime: selection.min, endTime: selection.max };
   },
 
   /**
    * Sets up the markers overivew graph, if needed.
--- a/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
@@ -59,28 +59,28 @@ function test() {
     inspector.selection.setNode(div);
     inspector.once("inspector-updated", testShrink);
   }
 
   function testShrink() {
 
     is(numberOfRules(), 2, "Should have two rules initially.");
 
-    ruleView.element.addEventListener("CssRuleViewRefreshed", function refresh() {
-      ruleView.element.removeEventListener("CssRuleViewRefreshed", refresh, false);
+    ruleView.on("ruleview-refreshed", function refresh() {
+      ruleView.off("ruleview-refreshed", refresh, false);
       is(numberOfRules(), 3, "Should have three rules after shrinking.");
       testGrow();
     }, false);
 
     instance.setSize(100, 100);
   }
 
   function testGrow() {
-    ruleView.element.addEventListener("CssRuleViewRefreshed", function refresh() {
-      ruleView.element.removeEventListener("CssRuleViewRefreshed", refresh, false);
+    ruleView.on("ruleview-refreshed", function refresh() {
+      ruleView.off("ruleview-refreshed", refresh, false);
       is(numberOfRules(), 2, "Should have two rules after growing.");
       testEscapeOpensSplitConsole();
     }, false);
 
     instance.setSize(500, 500);
   }
 
   function testEscapeOpensSplitConsole() {
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -2151,17 +2151,17 @@ ScratchpadWindow.prototype = Heritage.ex
     if (!DebuggerServer.initialized) {
       DebuggerServer.init();
       DebuggerServer.addBrowserActors();
     }
     DebuggerServer.allowChromeProcess = true;
 
     let client = new DebuggerClient(DebuggerServer.connectPipe());
     client.connect(() => {
-      client.attachProcess().then(aResponse => {
+      client.getProcess().then(aResponse => {
         if (aResponse.error) {
           reportError("listTabs", aResponse);
           deferred.reject(aResponse);
         }
         else {
           deferred.resolve({ form: aResponse.form, client: client });
         }
       });
--- a/browser/devtools/shared/profiler/tree-model.js
+++ b/browser/devtools/shared/profiler/tree-model.js
@@ -8,18 +8,18 @@ const {Cc, Ci, Cu, Cr} = require("chrome
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "L10N",
   "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "CATEGORY_MAPPINGS",
   "devtools/shared/profiler/global", true);
 loader.lazyRequireGetter(this, "CATEGORY_JIT",
   "devtools/shared/profiler/global", true);
 
-const CHROME_SCHEMES = ["chrome://", "resource://"];
-const CONTENT_SCHEMES = ["http://", "https://", "file://"];
+const CHROME_SCHEMES = ["chrome://", "resource://", "jar:file://"];
+const CONTENT_SCHEMES = ["http://", "https://", "file://", "app://"];
 
 exports.ThreadNode = ThreadNode;
 exports.FrameNode = FrameNode;
 exports.FrameNode.isContent = isContent;
 
 /**
  * A call tree for a thread. This is essentially a linkage between all frames
  * of all samples into a single tree structure, with additional information
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -11,16 +11,17 @@ const {Promise: promise} = Cu.import("re
 const {CssLogic} = require("devtools/styleinspector/css-logic");
 const {InplaceEditor, editableField, editableItem} = require("devtools/shared/inplace-editor");
 const {ELEMENT_STYLE, PSEUDO_ELEMENTS} = require("devtools/server/actors/styles");
 const {gDevTools} = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});
 const {OutputParser} = require("devtools/output-parser");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/styleeditor/utils");
 const {parseSingleValue, parseDeclarations} = require("devtools/styleinspector/css-parsing-utils");
 const overlays = require("devtools/styleinspector/style-inspector-overlays");
+const EventEmitter = require("devtools/toolkit/event-emitter");
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const PREF_UA_STYLES = "devtools.inspector.showUserAgentStyles";
 const PREF_DEFAULT_COLOR_UNIT = "devtools.defaultColorUnit";
@@ -1147,16 +1148,18 @@ function CssRuleView(aInspector, aDoc, a
   this._buildContextMenu();
   this._showEmpty();
 
   // Add the tooltips and highlighters to the view
   this.tooltips = new overlays.TooltipsOverlay(this);
   this.tooltips.addToView();
   this.highlighters = new overlays.HighlightersOverlay(this);
   this.highlighters.addToView();
+
+  EventEmitter.decorate(this);
 }
 
 exports.CssRuleView = CssRuleView;
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
@@ -1202,16 +1205,100 @@ CssRuleView.prototype = {
       popupset = doc.createElementNS(XUL_NS, "popupset");
       doc.documentElement.appendChild(popupset);
     }
 
     popupset.appendChild(this._contextmenu);
   },
 
   /**
+   * Get an instance of SelectorHighlighter (used to highlight nodes that match
+   * selectors in the rule-view). A new instance is only created the first time
+   * this function is called. The same instance will then be returned.
+   * @return {Promise} Resolves to the instance of the highlighter.
+   */
+  getSelectorHighlighter: Task.async(function*() {
+    let utils = this.inspector.toolbox.highlighterUtils;
+    if (!utils.supportsCustomHighlighters()) {
+      return null;
+    }
+
+    if (this.selectorHighlighter) {
+      return this.selectorHighlighter;
+    }
+
+    try {
+      let h = yield utils.getHighlighterByType("SelectorHighlighter");
+      return this.selectorHighlighter = h;
+    } catch (e) {
+      // The SelectorHighlighter type could not be created in the current target.
+      // It could be an older server, or a XUL page.
+      return null;
+    }
+  }),
+
+  /**
+   * Highlight/unhighlight all the nodes that match a given set of selectors
+   * inside the document of the current selected node.
+   * Only one selector can be highlighted at a time, so calling the method a
+   * second time with a different selector will first unhighlight the previously
+   * highlighted nodes.
+   * Calling the method a second time with the same selector will just
+   * unhighlight the highlighted nodes.
+   *
+   * @param {DOMNode} The icon that was clicked to toggle the selector. The
+   * class 'highlighted' will be added when the selector is highlighted.
+   * @param {String} The selector used to find nodes in the page.
+   */
+  toggleSelectorHighlighter: function(selectorIcon, selector) {
+    if (this.lastSelectorIcon) {
+      this.lastSelectorIcon.classList.remove("highlighted");
+    }
+    selectorIcon.classList.remove("highlighted");
+
+    this.unhighlightSelector().then(() => {
+      if (selector !== this.highlightedSelector) {
+        this.highlightedSelector = selector;
+        selectorIcon.classList.add("highlighted");
+        this.lastSelectorIcon = selectorIcon;
+        this.highlightSelector(selector).then(() => {
+          this.emit("ruleview-selectorhighlighter-toggled", true);
+        }, Cu.reportError);
+      } else {
+        this.highlightedSelector = null;
+        this.emit("ruleview-selectorhighlighter-toggled", false);
+      }
+    }, Cu.reportError);
+  },
+
+  highlightSelector: Task.async(function*(selector) {
+    let node = this.inspector.selection.nodeFront;
+
+    let highlighter = yield this.getSelectorHighlighter();
+    if (!highlighter) {
+      return;
+    }
+
+    yield highlighter.show(node, {
+      hideInfoBar: true,
+      hideGuides: true,
+      selector
+    });
+  }),
+
+  unhighlightSelector: Task.async(function*() {
+    let highlighter = yield this.getSelectorHighlighter();
+    if (!highlighter) {
+      return;
+    }
+
+    yield highlighter.hide();
+  }),
+
+  /**
    * Update the context menu. This means enabling or disabling menuitems as
    * appropriate.
    */
   _contextMenuUpdate: function() {
     let win = this.doc.defaultView;
 
     // Copy selection.
     let selection = win.getSelection();
@@ -1605,19 +1692,17 @@ CssRuleView.prototype = {
       }
 
       if (clearRules) {
         this._clearRules();
       }
       this._createEditors();
 
       // Notify anyone that cares that we refreshed.
-      var evt = this.doc.createEvent("Events");
-      evt.initEvent("CssRuleViewRefreshed", true, false);
-      this.element.dispatchEvent(evt);
+      this.emit("ruleview-refreshed");
       return undefined;
     }).then(null, promiseWarn);
   },
 
   /**
    * Show the user that the rule view has no node selected.
    */
   _showEmpty: function() {
@@ -1639,33 +1724,33 @@ CssRuleView.prototype = {
       this.element.removeChild(this.element.lastChild);
     }
   },
 
   /**
    * Clear the rule view.
    */
   clear: function() {
+    this.lastSelectorIcon = null;
+
     this._clearRules();
     this._viewedElement = null;
 
     if (this._elementStyle) {
       this._elementStyle.destroy();
       this._elementStyle = null;
     }
   },
 
   /**
    * Called when the user has made changes to the ElementStyle.
    * Emits an event that clients can listen to.
    */
   _changed: function() {
-    var evt = this.doc.createEvent("Events");
-    evt.initEvent("CssRuleViewChanged", true, false);
-    this.element.dispatchEvent(evt);
+    this.emit("ruleview-changed");
   },
 
   /**
    * Text for header that shows above rules for this element
    */
   get selectedElementLabel() {
     if (this._selectedElementLabel) {
       return this._selectedElementLabel;
@@ -1831,16 +1916,17 @@ CssRuleView.prototype = {
  * @param {Rule} aRule
  *        The Rule object we're editing.
  * @constructor
  */
 function RuleEditor(aRuleView, aRule) {
   this.ruleView = aRuleView;
   this.doc = this.ruleView.doc;
   this.rule = aRule;
+
   this.isEditable = !aRule.isSystem;
   // Flag that blocks updates of the selector and properties when it is
   // being edited
   this.isEditing = false;
 
   this._onNewProperty = this._onNewProperty.bind(this);
   this._newPropertyDestroy = this._newPropertyDestroy.bind(this);
   this._onSelectorDone = this._onSelectorDone.bind(this);
@@ -1875,21 +1961,17 @@ RuleEditor.prototype = {
     let source = createChild(this.element, "div", {
       class: "ruleview-rule-source theme-link"
     });
     source.addEventListener("click", function() {
       if (source.hasAttribute("unselectable")) {
         return;
       }
       let rule = this.rule.domRule;
-      let evt = this.doc.createEvent("CustomEvent");
-      evt.initCustomEvent("CssRuleViewCSSLinkClicked", true, false, {
-        rule: rule,
-      });
-      this.element.dispatchEvent(evt);
+      this.ruleView.emit("ruleview-linked-clicked", rule);
     }.bind(this));
     let sourceLabel = this.doc.createElementNS(XUL_NS, "label");
     sourceLabel.setAttribute("crop", "center");
     sourceLabel.classList.add("source-link-label");
     source.appendChild(sourceLabel);
 
     this.updateSourceLink();
 
@@ -1898,16 +1980,30 @@ RuleEditor.prototype = {
     });
 
     let header = createChild(code, "div", {});
 
     this.selectorContainer = createChild(header, "span", {
       class: "ruleview-selectorcontainer"
     });
 
+    if (this.rule.domRule.type !== Ci.nsIDOMCSSRule.KEYFRAME_RULE &&
+        this.rule.domRule.selectors) {
+      let selector = this.rule.domRule.selectors.join(", ");
+
+      let selectorHighlighter = createChild(header, "span", {
+        class: "ruleview-selectorhighlighter" +
+               (this.ruleView.highlightedSelector === selector ? " highlighted": ""),
+        title: CssLogic.l10n("rule.selectorHighlighter.tooltip")
+      });
+      selectorHighlighter.addEventListener("click", () => {
+        this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector);
+      });
+    }
+
     this.selectorText = createChild(this.selectorContainer, "span", {
       class: "ruleview-selector theme-fg-color3"
     });
 
     if (this.isSelectorEditable) {
       this.selectorContainer.addEventListener("click", aEvent => {
         // Clicks within the selector shouldn't propagate any further.
         aEvent.stopPropagation();
--- a/browser/devtools/styleinspector/style-inspector-overlays.js
+++ b/browser/devtools/styleinspector/style-inspector-overlays.js
@@ -24,24 +24,16 @@ Cu.import("resource://gre/modules/Task.j
 Cu.import("resource://gre/modules/Services.jsm");
 
 const PREF_IMAGE_TOOLTIP_SIZE = "devtools.inspector.imagePreviewTooltipSize";
 
 // Types of existing tooltips
 const TOOLTIP_IMAGE_TYPE = "image";
 const TOOLTIP_FONTFAMILY_TYPE = "font-family";
 
-// Types of existing highlighters
-const HIGHLIGHTER_TRANSFORM_TYPE = "CssTransformHighlighter";
-const HIGHLIGHTER_SELECTOR_TYPE = "SelectorHighlighter";
-const HIGHLIGHTER_TYPES = [
-  HIGHLIGHTER_TRANSFORM_TYPE,
-  HIGHLIGHTER_SELECTOR_TYPE
-];
-
 // Types of nodes in the rule/computed-view
 const VIEW_NODE_SELECTOR_TYPE = exports.VIEW_NODE_SELECTOR_TYPE = 1;
 const VIEW_NODE_PROPERTY_TYPE = exports.VIEW_NODE_PROPERTY_TYPE = 2;
 const VIEW_NODE_VALUE_TYPE = exports.VIEW_NODE_VALUE_TYPE = 3;
 const VIEW_NODE_IMAGE_URL_TYPE = exports.VIEW_NODE_IMAGE_URL_TYPE = 4;
 
 /**
  * Manages all highlighters in the style-inspector.
@@ -116,35 +108,27 @@ HighlightersOverlay.prototype = {
     this._lastHovered = event.target;
 
     let nodeInfo = this.view.getNodeInfo(event.target);
     if (!nodeInfo) {
       return;
     }
 
     // Choose the type of highlighter required for the hovered node
-    let type, options;
+    let type;
     if (this._isRuleViewTransform(nodeInfo) ||
         this._isComputedViewTransform(nodeInfo)) {
-      type = HIGHLIGHTER_TRANSFORM_TYPE;
-    } else if (nodeInfo.type === VIEW_NODE_SELECTOR_TYPE) {
-      type = HIGHLIGHTER_SELECTOR_TYPE;
-      options = {
-        selector: nodeInfo.value,
-        hideInfoBar: true,
-        showOnly: "border",
-        region: "border"
-      };
+      type = "CssTransformHighlighter";
     }
 
     if (type) {
       this.highlighterShown = type;
       let node = this.view.inspector.selection.nodeFront;
       this._getHighlighter(type).then(highlighter => {
-        highlighter.show(node, options);
+        highlighter.show(node);
       });
     }
   },
 
   _onMouseLeave: function(event) {
     this._lastHovered = null;
     this._hideCurrent();
   },
--- a/browser/devtools/styleinspector/style-inspector.js
+++ b/browser/devtools/styleinspector/style-inspector.js
@@ -29,19 +29,19 @@ function RuleViewTool(inspector, window,
   this.onLinkClicked = this.onLinkClicked.bind(this);
   this.onSelected = this.onSelected.bind(this);
   this.refresh = this.refresh.bind(this);
   this.clearUserProperties = this.clearUserProperties.bind(this);
   this.onPropertyChanged = this.onPropertyChanged.bind(this);
   this.onViewRefreshed = this.onViewRefreshed.bind(this);
   this.onPanelSelected = this.onPanelSelected.bind(this);
 
-  this.view.element.addEventListener("CssRuleViewChanged", this.onPropertyChanged);
-  this.view.element.addEventListener("CssRuleViewRefreshed", this.onViewRefreshed);
-  this.view.element.addEventListener("CssRuleViewCSSLinkClicked", this.onLinkClicked);
+  this.view.on("ruleview-changed", this.onPropertyChanged);
+  this.view.on("ruleview-refreshed", this.onViewRefreshed);
+  this.view.on("ruleview-linked-clicked", this.onLinkClicked);
 
   this.inspector.selection.on("detached", this.onSelected);
   this.inspector.selection.on("new-node-front", this.onSelected);
   this.inspector.on("layout-change", this.refresh);
   this.inspector.selection.on("pseudoclass", this.refresh);
   this.inspector.target.on("navigate", this.clearUserProperties);
   this.inspector.sidebar.on("ruleview-selected", this.onPanelSelected);
 
@@ -99,18 +99,17 @@ RuleViewTool.prototype = {
   onPanelSelected: function() {
     if (this.inspector.selection.nodeFront === this.view.viewedElement) {
       this.refresh();
     } else {
       this.onSelected();
     }
   },
 
-  onLinkClicked: function(event) {
-    let rule = event.detail.rule;
+  onLinkClicked: function(e, rule) {
     let sheet = rule.parentStyleSheet;
 
     // Chrome stylesheets are not listed in the style editor, so show
     // these sheets in the view source window instead.
     if (!sheet || sheet.isSystem) {
       let contentDoc = this.inspector.selection.document;
       let viewSourceUtils = this.inspector.viewSourceUtils;
       let href = rule.nodeHref || rule.href;
@@ -144,19 +143,19 @@ RuleViewTool.prototype = {
 
   destroy: function() {
     this.inspector.off("layout-change", this.refresh);
     this.inspector.selection.off("pseudoclass", this.refresh);
     this.inspector.selection.off("new-node-front", this.onSelected);
     this.inspector.target.off("navigate", this.clearUserProperties);
     this.inspector.sidebar.off("ruleview-selected", this.onPanelSelected);
 
-    this.view.element.removeEventListener("CssRuleViewCSSLinkClicked", this.onLinkClicked);
-    this.view.element.removeEventListener("CssRuleViewChanged", this.onPropertyChanged);
-    this.view.element.removeEventListener("CssRuleViewRefreshed", this.onViewRefreshed);
+    this.view.off("ruleview-linked-clicked", this.onLinkClicked);
+    this.view.off("ruleview-changed", this.onPropertyChanged);
+    this.view.off("ruleview-refreshed", this.onViewRefreshed);
 
     this.doc.documentElement.removeChild(this.view.element);
 
     this.view.destroy();
 
     this.view = this.doc = this.inspector = null;
   }
 };
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -97,16 +97,17 @@ skip-if = (os == "win" && debug) || e10s
 [browser_ruleview_pseudo-element_02.js]
 skip-if = e10s # Bug 1090340
 [browser_ruleview_refresh-on-attribute-change_01.js]
 [browser_ruleview_refresh-on-attribute-change_02.js]
 [browser_ruleview_refresh-on-style-change.js]
 [browser_ruleview_select-and-copy-styles.js]
 [browser_ruleview_selector-highlighter_01.js]
 [browser_ruleview_selector-highlighter_02.js]
+[browser_ruleview_selector-highlighter_03.js]
 [browser_ruleview_style-editor-link.js]
 skip-if = e10s # bug 1040670 Cannot open inline styles in viewSourceUtils
 [browser_ruleview_urls-clickable.js]
 [browser_ruleview_user-agent-styles.js]
 [browser_ruleview_user-agent-styles-uneditable.js]
 [browser_ruleview_user-property-reset.js]
 [browser_styleinspector_context-menu-copy-color_01.js]
 [browser_styleinspector_context-menu-copy-color_02.js]
--- a/browser/devtools/styleinspector/test/browser_ruleview_add-rule_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_add-rule_01.js
@@ -52,17 +52,17 @@ function* runTestData(inspector, view, d
 
   EventUtils.synthesizeMouseAtCenter(view.element,
     {button: 2, type: "contextmenu"}, win);
   yield onPopup;
 
   ok(!view.menuitemAddRule.hidden, "Add rule is visible");
 
   info("Waiting for rule view to change");
-  let onRuleViewChanged = once(view.element, "CssRuleViewChanged");
+  let onRuleViewChanged = once(view, "ruleview-changed");
 
   info("Adding the new rule");
   view.menuitemAddRule.click();
   yield onRuleViewChanged;
   view._contextmenu.hidePopup();
 
   yield testNewRule(view, expected, 1);
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_add-rule_02.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_add-rule_02.js
@@ -35,17 +35,17 @@ add_task(function*() {
 
   EventUtils.synthesizeMouseAtCenter(view.element,
     {button: 2, type: "contextmenu"}, win);
   yield onPopup;
 
   ok(!view.menuitemAddRule.hidden, "Add rule is visible");
 
   info("Waiting for rule view to change");
-  let onRuleViewChanged = once(view.element, "CssRuleViewChanged");
+  let onRuleViewChanged = once(view, "ruleview-changed");
 
   info("Adding the new rule");
   view.menuitemAddRule.click();
   yield onRuleViewChanged;
   view._contextmenu.hidePopup();
 
   yield testEditSelector(view, "span");
 
@@ -58,17 +58,17 @@ function* testEditSelector(view, name) {
   info("Test editing existing selector field");
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
 
   info("Entering a new selector name and committing");
   editor.value = name;
 
   info("Waiting for rule view to refresh");
-  let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
+  let onRuleViewRefresh = once(view, "ruleview-refreshed");
 
   info("Entering the commit key");
   EventUtils.synthesizeKey("VK_RETURN", {});
   yield onRuleViewRefresh;
 
   is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
 }
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_add-rule_03.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_add-rule_03.js
@@ -35,17 +35,17 @@ add_task(function*() {
 
   EventUtils.synthesizeMouseAtCenter(view.element,
     {button: 2, type: "contextmenu"}, win);
   yield onPopup;
 
   ok(!view.menuitemAddRule.hidden, "Add rule is visible");
 
   info("Waiting for rule view to change");
-  let onRuleViewChanged = once(view.element, "CssRuleViewChanged");
+  let onRuleViewChanged = once(view, "ruleview-changed");
 
   info("Adding the new rule");
   view.menuitemAddRule.click();
   yield onRuleViewChanged;
   view._contextmenu.hidePopup();
 
   info("Adding new properties to the new rule");
   yield testNewRule(view, "#testid", 1);
@@ -88,17 +88,17 @@ function* testEditSelector(view, name) {
 
   is(inplaceEditor(idRuleEditor.selectorText), editor,
     "The selector editor got focused");
 
   info("Entering a new selector name: " + name);
   editor.input.value = name;
 
   info("Waiting for rule view to refresh");
-  let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
+  let onRuleViewRefresh = once(view, "ruleview-refreshed");
 
   info("Entering the commit key");
   EventUtils.synthesizeKey("VK_RETURN", {});
   yield onRuleViewRefresh;
 
   is(view._elementStyle.rules.length, 2, "Should have 2 rules.");
 }
 
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-property-commit.js
@@ -73,12 +73,12 @@ function* runTestData(view, {value, comm
 
   info("Entering the commit key " + commitKey + " " + modifiers);
   EventUtils.synthesizeKey(commitKey, modifiers);
   yield onBlur;
 
   if (commitKey === "VK_ESCAPE") {
     is(propEditor.valueSpan.textContent, expected, "Value is as expected: " + expected);
   } else {
-    yield once(view.element, "CssRuleViewChanged");
+    yield once(view, "ruleview-changed");
     is(propEditor.valueSpan.textContent, expected, "Value is as expected: " + expected);
   }
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector-commit.js
@@ -83,16 +83,16 @@ function* runTestData(inspector, view, d
   info("Entering the commit key " + commitKey + " " + modifiers);
   EventUtils.synthesizeKey(commitKey, modifiers);
 
   if (commitKey === "VK_ESCAPE") {
     is(idRuleEditor.rule.selectorText, expected,
         "Value is as expected: " + expected);
     is(idRuleEditor.isEditing, false, "Selector is not being edited.")
   } else {
-    yield once(view.element, "CssRuleViewRefreshed");
+    yield once(view, "ruleview-refreshed");
     ok(getRuleViewRule(view, expected),
         "Rule with " + name + " selector exists.");
   }
 
   info("Resetting page content");
   content.document.body.innerHTML = PAGE_CONTENT;
 }
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_01.js
@@ -44,17 +44,17 @@ function* testEditSelector(view, name) {
 
   is(inplaceEditor(idRuleEditor.selectorText), editor,
     "The selector editor got focused");
 
   info("Entering a new selector name and committing");
   editor.input.value = name;
 
   info("Waiting for rule view to refresh");
-  let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
+  let onRuleViewRefresh = once(view, "ruleview-refreshed");
 
   info("Entering the commit key");
   EventUtils.synthesizeKey("VK_RETURN", {});
   yield onRuleViewRefresh;
 
   is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
   is(getRuleViewRule(view, name), undefined,
       name + " selector has been removed.");
--- a/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_edit-selector_02.js
@@ -59,17 +59,17 @@ function* testEditSelector(view, name) {
 
   is(inplaceEditor(idRuleEditor.selectorText), editor,
     "The selector editor got focused");
 
   info("Entering a new selector name: " + name);
   editor.input.value = name;
 
   info("Waiting for rule view to refresh");
-  let onRuleViewRefresh = once(view.element, "CssRuleViewRefreshed");
+  let onRuleViewRefresh = once(view, "ruleview-refreshed");
 
   info("Entering the commit key");
   EventUtils.synthesizeKey("VK_RETURN", {});
   yield onRuleViewRefresh;
 
   is(view._elementStyle.rules.length, 1, "Should have 1 rule.");
   is(getRuleViewRule(view, name), undefined,
       name + " selector has been removed.");
--- a/browser/devtools/styleinspector/test/browser_ruleview_selector-highlighter_01.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_selector-highlighter_01.js
@@ -1,44 +1,34 @@
 /* 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 selector highlighter is created when hovering over a selector
-// in the rule view
+// Test that the selector highlighter is created when clicking on a selector
+// icon in the rule view.
 
 const PAGE_CONTENT = [
   '<style type="text/css">',
   '  body, p, td {',
   '    background: red;',
   '  }',
   '</style>',
   'Test the selector highlighter'
 ].join("\n");
 
-let TYPE = "SelectorHighlighter";
-
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
 
-  let {view: rView} = yield openRuleView();
-  let hs = rView.highlighters;
+  let {view} = yield openRuleView();
+  ok(!view.selectorHighlighter, "No selectorhighlighter exist in the rule-view");
 
-  ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (1)");
-  ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (1)");
+  info("Clicking on a selector icon");
+  let icon = getRuleViewSelectorHighlighterIcon(view, "body, p, td");
 
-  info("Faking a mousemove NOT on a selector");
-  let {valueSpan} = getRuleViewProperty(rView, "body, p, td", "background");
-  hs._onMouseMove({target: valueSpan});
-  ok(!hs.highlighters[TYPE], "No highlighter exists in the rule-view (2)");
-  ok(!hs.promises[TYPE], "No highlighter is being created in the rule-view (2)");
+  let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+  EventUtils.synthesizeMouseAtCenter(icon, {}, view.doc.defaultView);
+  let isVisible = yield onToggled;
 
-  info("Faking a mousemove on the body selector");
-  let selectorContainer = getRuleViewSelector(rView, "body, p, td");
-  // The highlighter appears for individual selectors only
-  let bodySelector = selectorContainer.firstElementChild;
-  hs._onMouseMove({target: bodySelector});
-  ok(hs.promises[TYPE], "The highlighter is being initialized");
-  let h = yield hs.promises[TYPE];
-  is(h, hs.highlighters[TYPE], "The initialized highlighter is the right one");
+  ok(view.selectorHighlighter, "The selectorhighlighter instance was created");
+  ok(isVisible, "The toggle event says the highlighter is visible");
 });
--- a/browser/devtools/styleinspector/test/browser_ruleview_selector-highlighter_02.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_selector-highlighter_02.js
@@ -1,15 +1,15 @@
 /* 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 selector highlighter is shown when hovering over a selector
+// Test that the selector highlighter is shown when clicking on a selector icon
 // in the rule-view
 
 // Note that in this test, we mock the highlighter front, merely testing the
 // behavior of the style-inspector UI for now
 
 const PAGE_CONTENT = [
   '<style type="text/css">',
   '  body {',
@@ -17,22 +17,20 @@ const PAGE_CONTENT = [
   '  }',
   '  p {',
   '    color: white;',
   '  }',
   '</style>',
   '<p>Testing the selector highlighter</p>'
 ].join("\n");
 
-const TYPE = "SelectorHighlighter";
-
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
 
-  let {inspector, view: rView} = yield openRuleView();
+  let {inspector, view} = yield openRuleView();
 
   // Mock the highlighter front to get the reference of the NodeFront
   let HighlighterFront = {
     isShown: false,
     nodeFront: null,
     options: null,
     show: function(nodeFront, options) {
       this.nodeFront = nodeFront;
@@ -42,44 +40,46 @@ add_task(function*() {
     hide: function() {
       this.nodeFront = null;
       this.options = null;
       this.isShown = false;
     }
   };
 
   // Inject the mock highlighter in the rule-view
-  rView.highlighters.promises[TYPE] = {
-    then: function(cb) {
-      cb(HighlighterFront);
-    }
-  };
+  view.selectorHighlighter = HighlighterFront;
 
-  let selectorSpan = getRuleViewSelector(rView, "body").firstElementChild;
+  let icon = getRuleViewSelectorHighlighterIcon(view, "body");
 
   info("Checking that the HighlighterFront's show/hide methods are called");
-  rView.highlighters._onMouseMove({target: selectorSpan});
+
+  info("Clicking once on the body selector highlighter icon");
+  yield clickSelectorIcon(icon, view);
   ok(HighlighterFront.isShown, "The highlighter is shown");
-  rView.highlighters._onMouseLeave();
+
+  info("Clicking once again on the body selector highlighter icon");
+  yield clickSelectorIcon(icon, view);
   ok(!HighlighterFront.isShown, "The highlighter is hidden");
 
   info("Checking that the right NodeFront reference and options are passed");
   yield selectNode("p", inspector);
-  selectorSpan = getRuleViewSelector(rView, "p").firstElementChild;
-  rView.highlighters._onMouseMove({target: selectorSpan});
+  icon = getRuleViewSelectorHighlighterIcon(view, "p");
+
+  yield clickSelectorIcon(icon, view);
   is(HighlighterFront.nodeFront.tagName, "P",
     "The right NodeFront is passed to the highlighter (1)");
   is(HighlighterFront.options.selector, "p",
     "The right selector option is passed to the highlighter (1)");
 
   yield selectNode("body", inspector);
-  selectorSpan = getRuleViewSelector(rView, "body").firstElementChild;
-  rView.highlighters._onMouseMove({target: selectorSpan});
+  icon = getRuleViewSelectorHighlighterIcon(view, "body");
+  yield clickSelectorIcon(icon, view);
   is(HighlighterFront.nodeFront.tagName, "BODY",
     "The right NodeFront is passed to the highlighter (2)");
   is(HighlighterFront.options.selector, "body",
     "The right selector option is passed to the highlighter (2)");
+});
 
-  info("Checking that the highlighter gets hidden when hovering somewhere else");
-  let {valueSpan} = getRuleViewProperty(rView, "body", "background");
-  rView.highlighters._onMouseMove({target: valueSpan});
-  ok(!HighlighterFront.isShown, "The highlighter is hidden");
-});
+function* clickSelectorIcon(icon, view) {
+  let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+  EventUtils.synthesizeMouseAtCenter(icon, {}, view.doc.defaultView);
+  yield onToggled;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_selector-highlighter_03.js
@@ -0,0 +1,84 @@
+/* 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 selector highlighter toggling mechanism works correctly.
+
+// Note that in this test, we mock the highlighter front, merely testing the
+// behavior of the style-inspector UI for now
+
+const PAGE_CONTENT = [
+  '<style type="text/css">',
+  '  div {text-decoration: underline;}',
+  '  .node-1 {color: red;}',
+  '  .node-2 {color: green;}',
+  '</style>',
+  '<div class="node-1">Node 1</div>',
+  '<div class="node-2">Node 2</div>'
+].join("\n");
+
+add_task(function*() {
+  yield addTab("data:text/html;charset=utf-8," + PAGE_CONTENT);
+
+  let {inspector, view} = yield openRuleView();
+
+  // Mock the highlighter front.
+  let HighlighterFront = {
+    isShown: false,
+    show: function() {
+      this.isShown = true;
+    },
+    hide: function() {
+      this.isShown = false;
+    }
+  };
+
+  // Inject the mock highlighter in the rule-view
+  view.selectorHighlighter = HighlighterFront;
+
+  info("Select .node-1 and click on the .node-1 selector icon");
+  yield selectNode(".node-1", inspector);
+  let icon = getRuleViewSelectorHighlighterIcon(view, ".node-1");
+  yield clickSelectorIcon(icon, view);
+  ok(HighlighterFront.isShown, "The highlighter is shown");
+
+  info("With .node-1 still selected, click again on the .node-1 selector icon");
+  yield clickSelectorIcon(icon, view);
+  ok(!HighlighterFront.isShown, "The highlighter is now hidden");
+
+  info("With .node-1 still selected, click on the div selector icon");
+  icon = getRuleViewSelectorHighlighterIcon(view, "div");
+  yield clickSelectorIcon(icon, view);
+  ok(HighlighterFront.isShown, "The highlighter is shown again");
+
+  info("With .node-1 still selected, click again on the .node-1 selector icon");
+  icon = getRuleViewSelectorHighlighterIcon(view, ".node-1");
+  yield clickSelectorIcon(icon, view);
+  ok(HighlighterFront.isShown,
+    "The highlighter is shown again since the clicked selector was different");
+
+  info("Selecting .node-2");
+  yield selectNode(".node-2", inspector);
+  ok(HighlighterFront.isShown, "The highlighter is still shown after selection");
+
+  info("With .node-2 selected, click on the div selector icon");
+  icon = getRuleViewSelectorHighlighterIcon(view, "div");
+  yield clickSelectorIcon(icon, view);
+  ok(HighlighterFront.isShown,
+    "The highlighter is shown still since the selected was different");
+
+  info("Switching back to .node-1 and clicking on the div selector");
+  yield selectNode(".node-1", inspector);
+  icon = getRuleViewSelectorHighlighterIcon(view, "div");
+  yield clickSelectorIcon(icon, view);
+  ok(!HighlighterFront.isShown,
+    "The highlighter is hidden now that the same selector was clicked");
+});
+
+function* clickSelectorIcon(icon, view) {
+  let onToggled = view.once("ruleview-selectorhighlighter-toggled");
+  EventUtils.synthesizeMouseAtCenter(icon, {}, view.doc.defaultView);
+  yield onToggled;
+}
--- a/browser/devtools/styleinspector/test/head.js
+++ b/browser/devtools/styleinspector/test/head.js
@@ -648,16 +648,28 @@ function getRuleViewPropertyValue(view, 
  * @return {DOMNode} The selector DOM element
  */
 function getRuleViewSelector(view, selectorText) {
   let rule = getRuleViewRule(view, selectorText);
   return rule.querySelector(".ruleview-selector, .ruleview-selector-matched");
 }
 
 /**
+ * Get a reference to the selectorhighlighter icon DOM element corresponding to
+ * a given selector in the rule-view
+ * @param {CssRuleView} view The instance of the rule-view panel
+ * @param {String} selectorText The selector in the rule-view to look for
+ * @return {DOMNode} The selectorhighlighter icon DOM element
+ */
+function getRuleViewSelectorHighlighterIcon(view, selectorText) {
+  let rule = getRuleViewRule(view, selectorText);
+  return rule.querySelector(".ruleview-selectorhighlighter");
+}
+
+/**
  * Simulate a color change in a given color picker tooltip, and optionally wait
  * for a given element in the page to have its style changed as a result
  * @param {SwatchColorPickerTooltip} colorPicker
  * @param {Array} newRgba The new color to be set [r, g, b, a]
  * @param {Object} expectedChange Optional object that needs the following props:
  *                 - {DOMNode} element The element in the page that will have its
  *                   style changed.
  *                 - {String} name The style name that will be changed
--- a/browser/devtools/webconsole/hudservice.js
+++ b/browser/devtools/webconsole/hudservice.js
@@ -186,17 +186,17 @@ HUD_SERVICE.prototype =
       if (!DebuggerServer.initialized) {
         DebuggerServer.init();
         DebuggerServer.addBrowserActors();
       }
       DebuggerServer.allowChromeProcess = true;
 
       let client = new DebuggerClient(DebuggerServer.connectPipe());
       client.connect(() => {
-        client.attachProcess().then(aResponse => {
+        client.getProcess().then(aResponse => {
           // Set chrome:false in order to attach to the target
           // (i.e. send an `attach` request to the chrome actor)
           deferred.resolve({ form: aResponse.form, client: client, chrome: false });
         }, deferred.reject);
       });
 
       return deferred.promise;
     }
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -224,28 +224,28 @@ let AppManager = exports.AppManager = {
     }
     return this.getTarget().then(target => {
       target.activeTab.reload();
     }, console.error.bind(console));
   },
 
   getTarget: function() {
     if (this.selectedProject.type == "mainProcess") {
-      // Fx >=37 exposes a ChromeActor to debug the main process
+      // Fx >=39 exposes a ChromeActor to debug the main process
       if (this.connection.client.mainRoot.traits.allowChromeProcess) {
-        return this.connection.client.attachProcess()
+        return this.connection.client.getProcess()
                    .then(aResponse => {
                      return devtools.TargetFactory.forRemoteTab({
                        form: aResponse.form,
                        client: this.connection.client,
                        chrome: true
                      });
                    });
       } else {
-        // Fx <37 exposes tab actors on the root actor
+        // Fx <39 exposes tab actors on the root actor
         return devtools.TargetFactory.forRemoteTab({
           form: this._listTabsResponse,
           client: this.connection.client,
           chrome: true,
           isTabActor: false
         });
       }
     }
@@ -437,18 +437,18 @@ let AppManager = exports.AppManager = {
       // |connectToRuntime| caller should listen for rejections.
       // Bug 1121100 may find a better way to silence these.
     });
 
     return deferred.promise;
   },
 
   isMainProcessDebuggable: function() {
-    // Fx <37 exposes chrome tab actors on RootActor
-    // Fx >=37 exposes a dedicated actor via attachProcess request
+    // Fx <39 exposes chrome tab actors on RootActor
+    // Fx >=39 exposes a dedicated actor via getProcess request
     return this.connection.client &&
            this.connection.client.mainRoot &&
            this.connection.client.mainRoot.traits.allowChromeProcess ||
            (this._listTabsResponse &&
             this._listTabsResponse.consoleActor);
   },
 
   get deviceFront() {
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -414,16 +414,24 @@ Experiments.Experiments = function (poli
   this._firstEvaluate = true;
 
   this.init();
 };
 
 Experiments.Experiments.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsITimerCallback, Ci.nsIObserver]),
 
+  /**
+   * `true` if the experiments manager is currently setup (has been fully initialized
+   * and not uninitialized yet).
+   */
+  get isReady() {
+    return !this._shutdown;
+  },
+
   init: function () {
     this._shutdown = false;
     configureLogging();
 
     gExperimentsEnabled = gPrefs.get(PREF_ENABLED, false);
     this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled);
 
     gPrefs.observe(PREF_LOGGING, configureLogging);
--- a/browser/experiments/ExperimentsService.js
+++ b/browser/experiments/ExperimentsService.js
@@ -51,17 +51,20 @@ ExperimentsService.prototype = {
 
   notify: function (timer) {
     if (!gExperimentsEnabled) {
       return;
     }
     if (OS.Constants.Path.profileDir === undefined) {
       throw Error("Update timer fired before profile was initialized?");
     }
-    Experiments.instance().updateManifest();
+    let instance = Experiments.instance();
+    if (instance.isReady) {
+      instance.updateManifest();
+    }
   },
 
   _delayedInit: function () {
     if (!this._initialized) {
       this._initialized = true;
       Experiments.instance(); // for side effects
     }
   },
--- a/browser/locales/en-US/chrome/browser/newTab.properties
+++ b/browser/locales/en-US/chrome/browser/newTab.properties
@@ -13,16 +13,20 @@ newtab.sponsored.button=SPONSORED
 # one of the user's top 100 sites that triggered this suggested tile.
 # This text appears for suggested tiles under the tile's title, so prefer short
 # strings to avoid truncating important text.
 newtab.suggested.button=Suggested for %1$S visitors
 # LOCALIZATION NOTE(newtab.sponsored.explain): %1$S will be replaced inline by
 # the (X) block icon. %2$S will be replaced by an active link using string
 # newtab.learn.link as text.
 newtab.sponsored.explain=This tile is being shown to you on behalf of a Mozilla partner. You can remove it at any time by clicking the %1$S button. %2$S
+# LOCALIZATION NOTE(newtab.suggested.explain): %1$S will be replaced inline by
+# the (X) block icon. %2$S will be replaced by an active link using string
+# newtab.learn.link as text.
+newtab.suggested.explain=This site is suggested to you by Mozilla. You can remove it at any time by clicking the %1$S button. %2$S
 # LOCALIZATION NOTE(newtab.enhanced.explain): %1$S will be replaced inline by
 # the gear icon used to customize the new tab window. %2$S will be replaced by
 # an active link using string newtab.learn.link as text.
 newtab.enhanced.explain=A Mozilla partner has visually enhanced this tile, replacing the screenshot. You can turn off enhanced tiles by clicking the %1$S button for your preferences. %2$S
 # LOCALIZATION NOTE(newtab.intro.paragraph1): %1$S will be replaced inline by
 # active link using string newtab.learn.link as text.
 newtab.intro.paragraph1=When you open a new tab, you’ll see tiles from the sites you frequently visit, along with tiles that we think might be of interest to you. Some of these tiles may be sponsored by Mozilla partners. We’ll always indicate to you which tiles are sponsored. %1$S
 # LOCALIZATION NOTE(newtab.intro.paragraph2): %1$S will be replaced inline by
--- a/browser/modules/DirectoryLinksProvider.jsm
+++ b/browser/modules/DirectoryLinksProvider.jsm
@@ -197,17 +197,17 @@ let DirectoryLinksProvider = {
   _removePrefsObserver: function DirectoryLinksProvider_removeObserver() {
     for (let pref in this._observedPrefs) {
       let prefName = this._observedPrefs[pref];
       Services.prefs.removeObserver(prefName, this);
     }
   },
 
   _cacheRelatedLinks: function(link) {
-    for (let relatedSite of link.related) {
+    for (let relatedSite of link.frecent_sites) {
       let relatedMap = this._relatedLinks.get(relatedSite) || new Map();
       relatedMap.set(link.url, link);
       this._relatedLinks.set(relatedSite, relatedMap);
     }
   },
 
   _fetchAndCacheLinks: function DirectoryLinksProvider_fetchAndCacheLinks(uri) {
     // Replace with the same display locale used for selecting links data
@@ -288,35 +288,37 @@ let DirectoryLinksProvider = {
     if ((Date.now() - this._lastDownloadMS) > this._downloadIntervalMS) {
       return true;
     }
     return false;
   },
 
   /**
    * Reads directory links file and parses its content
-   * @return a promise resolved to valid list of links or [] if read or parse fails
+   * @return a promise resolved to an object with keys 'directory' and 'suggested',
+   *         each containing a valid list of links,
+   *         or {'directory': [], 'suggested': []} if read or parse fails.
    */
   _readDirectoryLinksFile: function DirectoryLinksProvider_readDirectoryLinksFile() {
+    let emptyOutput = {directory: [], suggested: []};
     return OS.File.read(this._directoryFilePath).then(binaryData => {
       let output;
       try {
-        let locale = this.locale;
         let json = gTextDecoder.decode(binaryData);
-        let list = JSON.parse(json);
-        output = list[locale];
+        let linksObj = JSON.parse(json);
+        output = {directory: linksObj.directory || [], suggested: linksObj.suggested || []};
       }
       catch (e) {
         Cu.reportError(e);
       }
-      return output || [];
+      return output || emptyOutput;
     },
     error => {
       Cu.reportError(error);
-      return [];
+      return emptyOutput;
     });
   },
 
   /**
    * Report some action on a newtab page (view, click)
    * @param sites Array of sites shown on newtab page
    * @param action String of the behavior to report
    * @param triggeringSiteIndex optional Int index of the site triggering action
@@ -410,39 +412,44 @@ let DirectoryLinksProvider = {
    * @param aCallback The function that the array of links is passed to.
    */
   getLinks: function DirectoryLinksProvider_getLinks(aCallback) {
     this._readDirectoryLinksFile().then(rawLinks => {
       // Reset the cache of related tiles and enhanced images for this new set of links
       this._enhancedLinks.clear();
       this._relatedLinks.clear();
 
-      let links = [];
-      rawLinks.filter(link => {
+      let validityFilter = function(link) {
         // Make sure the link url is allowed and images too if they exist
         return this.isURLAllowed(link.url, ALLOWED_LINK_SCHEMES) &&
                this.isURLAllowed(link.imageURI, ALLOWED_IMAGE_SCHEMES) &&
                this.isURLAllowed(link.enhancedImageURI, ALLOWED_IMAGE_SCHEMES);
-      }).forEach((link, position) => {
+      }.bind(this);
+
+      let setCommonProperties = function(link, length, position) {
         // Stash the enhanced image for the site
         if (link.enhancedImageURI) {
           this._enhancedLinks.set(NewTabUtils.extractSite(link.url), link);
         }
-        link.lastVisitDate = rawLinks.length - position;
+        link.lastVisitDate = length - position;
+      }.bind(this);
+
+      rawLinks.suggested.filter(validityFilter).forEach((link, position) => {
+        setCommonProperties(link, rawLinks.suggested.length, position);
 
         // We cache related tiles here but do not push any of them in the links list yet.
         // The decision for which related tile to include will be made separately.
-        if ("related" == link.type) {
-          this._cacheRelatedLinks(link);
-          return;
-        }
+        this._cacheRelatedLinks(link);
+      });
+
+      return rawLinks.directory.filter(validityFilter).map((link, position) => {
+        setCommonProperties(link, rawLinks.directory.length, position);
         link.frecency = DIRECTORY_FRECENCY;
-        links.push(link);
+        return link;
       });
-      return links;
     }).catch(ex => {
       Cu.reportError(ex);
       return [];
     }).then(links => {
       aCallback(links);
       this._populatePlacesLinks();
     });
   },
@@ -537,22 +544,22 @@ let DirectoryLinksProvider = {
       return;
     }
 
     // Delete the current related tile, if one exists.
     let initialLength = sortedLinks.length;
     this.maxNumLinks = initialLength;
     if (initialLength) {
       let mostFrecentLink = sortedLinks[0];
-      if ("related" == mostFrecentLink.type) {
+      if (mostFrecentLink.targetedSite) {
         this._callObservers("onLinkChanged", {
           url: mostFrecentLink.url,
           frecency: 0,
           lastVisitDate: mostFrecentLink.lastVisitDate,
-          type: "related",
+          type: mostFrecentLink.type,
         }, 0, true);
       }
     }
 
     if (this._topSitesWithRelatedLinks.size == 0) {
       // There are no potential related links we can show.
       return;
     }
@@ -584,17 +591,17 @@ let DirectoryLinksProvider = {
     let chosenRelatedLink = flattenedLinks[relatedIndex];
 
     // Show the new directory tile.
     this._callObservers("onLinkChanged", {
       url: chosenRelatedLink.url,
       title: chosenRelatedLink.title,
       frecency: RELATED_FRECENCY,
       lastVisitDate: chosenRelatedLink.lastVisitDate,
-      type: "related",
+      type: chosenRelatedLink.type,
 
       // Choose the first site a user has visited as the target. In the future,
       // this should be the site with the highest frecency. However, we currently
       // store frecency by URL not by site.
       targetedSite: targetedSites.get(chosenRelatedLink.url).length ?
         targetedSites.get(chosenRelatedLink.url)[0] : null
     });
     return chosenRelatedLink;
--- a/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
+++ b/browser/modules/test/xpcshell/test_DirectoryLinksProvider.js
@@ -21,17 +21,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils",
   "resource://gre/modules/NewTabUtils.jsm");
 
 do_get_profile();
 
 const DIRECTORY_LINKS_FILE = "directoryLinks.json";
 const DIRECTORY_FRECENCY = 1000;
-const kURLData = {"en-US": [{"url":"http://example.com","title":"LocalSource"}]};
+const kURLData = {"directory": [{"url":"http://example.com","title":"LocalSource"}]};
 const kTestURL = 'data:application/json,' + JSON.stringify(kURLData);
 
 // DirectoryLinksProvider preferences
 const kLocalePref = DirectoryLinksProvider._observedPrefs.prefSelectedLocale;
 const kSourceUrlPref = DirectoryLinksProvider._observedPrefs.linksURL;
 const kPingUrlPref = "browser.newtabpage.directory.ping";
 const kNewtabEnhancedPref = "browser.newtabpage.enhanced";
 
@@ -48,51 +48,51 @@ const kPingUrl = kBaseUrl + kPingPath;
 
 // app/profile/firefox.js are not avaialble in xpcshell: hence, preset them
 Services.prefs.setCharPref(kLocalePref, "en-US");
 Services.prefs.setCharPref(kSourceUrlPref, kTestURL);
 Services.prefs.setCharPref(kPingUrlPref, kPingUrl);
 Services.prefs.setBoolPref(kNewtabEnhancedPref, true);
 
 const kHttpHandlerData = {};
-kHttpHandlerData[kExamplePath] = {"en-US": [{"url":"http://example.com","title":"RemoteSource"}]};
+kHttpHandlerData[kExamplePath] = {"directory": [{"url":"http://example.com","title":"RemoteSource"}]};
 
 const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
                               "nsIBinaryInputStream",
                               "setInputStream");
 
 let gLastRequestPath;
 
 let relatedTile1 = {
   url: "http://turbotax.com",
-  type: "related",
-  lastVisitDate: 4,
-  related: [
+  type: "affiliate",
+  lastVisitDate: 3,
+  frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let relatedTile2 = {
   url: "http://irs.gov",
-  type: "related",
-  lastVisitDate: 3,
-  related: [
+  type: "affiliate",
+  lastVisitDate: 2,
+  frecent_sites: [
     "taxact.com",
     "hrblock.com",
     "freetaxusa.com",
     "taxslayer.com"
   ]
 };
 let relatedTile3 = {
   url: "http://hrblock.com",
-  type: "related",
-  lastVisitDate: 2,
-  related: [
+  type: "affiliate",
+  lastVisitDate: 1,
+  frecent_sites: [
     "taxact.com",
     "freetaxusa.com",
     "1040.com",
     "taxslayer.com"
   ]
 };
 let someOtherSite = {url: "http://someothersite.com", title: "Not_A_Related_Site"};
 
@@ -213,17 +213,17 @@ function run_test() {
     Services.prefs.clearUserPref(kNewtabEnhancedPref);
   });
 }
 
 add_task(function test_updateRelatedTile() {
   let topSites = ["site0.com", "1040.com", "site2.com", "hrblock.com", "site4.com", "freetaxusa.com", "site6.com"];
 
   // Initial setup
-  let data = {"en-US": [relatedTile1, relatedTile2, relatedTile3, someOtherSite]};
+  let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   let testObserver = new TestFirstRun();
   DirectoryLinksProvider.addObserver(testObserver);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
@@ -243,31 +243,31 @@ add_task(function test_updateRelatedTile
     this.promise = new Promise(resolve => {
       this.onLinkChanged = (directoryLinksProvider, link) => {
         links.unshift(link);
         let possibleLinks = [relatedTile1.url, relatedTile2.url, relatedTile3.url];
 
         isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], ["hrblock.com", "1040.com", "freetaxusa.com"]);
         do_check_true(possibleLinks.indexOf(link.url) > -1);
         do_check_eq(link.frecency, Infinity);
-        do_check_eq(link.type, "related");
+        do_check_eq(link.type, "affiliate");
         resolve();
       };
     });
   }
 
   function TestChangingRelatedTile() {
     this.count = 0;
     this.promise = new Promise(resolve => {
       this.onLinkChanged = (directoryLinksProvider, link) => {
         this.count++;
         let possibleLinks = [relatedTile1.url, relatedTile2.url, relatedTile3.url];
 
         do_check_true(possibleLinks.indexOf(link.url) > -1);
-        do_check_eq(link.type, "related");
+        do_check_eq(link.type, "affiliate");
         do_check_true(this.count <= 2);
 
         if (this.count == 1) {
           // The removed related link is the one we added initially.
           do_check_eq(link.url, links.shift().url);
           do_check_eq(link.frecency, 0);
         } else {
           links.unshift(link);
@@ -280,17 +280,17 @@ add_task(function test_updateRelatedTile
   }
 
   function TestRemovingRelatedTile() {
     this.count = 0;
     this.promise = new Promise(resolve => {
       this.onLinkChanged = (directoryLinksProvider, link) => {
         this.count++;
 
-        do_check_eq(link.type, "related");
+        do_check_eq(link.type, "affiliate");
         do_check_eq(this.count, 1);
         do_check_eq(link.frecency, 0);
         do_check_eq(link.url, links.shift().url);
         isIdentical([...DirectoryLinksProvider._topSitesWithRelatedLinks], []);
         resolve();
       }
     });
   }
@@ -335,17 +335,17 @@ add_task(function test_updateRelatedTile
 
   // Cleanup
   yield promiseCleanDirectoryLinksProvider();
   NewTabUtils.isTopPlacesSite = origIsTopPlacesSite;
   NewTabUtils.getProviderLinks = origGetProviderLinks;
 });
 
 add_task(function test_relatedLinksMap() {
-  let data = {"en-US": [relatedTile1, relatedTile2, relatedTile3, someOtherSite]};
+  let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   // Ensure the related tiles were not considered directory tiles.
   do_check_eq(links.length, 1);
   let expected_data = [{url: "http://someothersite.com", title: "Not_A_Related_Site", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
@@ -381,17 +381,17 @@ add_task(function test_topSitesWithRelat
   let origGetProviderLinks = NewTabUtils.getProviderLinks;
   NewTabUtils.getProviderLinks = function(provider) {
     return [];
   }
 
   // We start off with no top sites with related links.
   do_check_eq(DirectoryLinksProvider._topSitesWithRelatedLinks.size, 0);
 
-  let data = {"en-US": [relatedTile1, relatedTile2, relatedTile3, someOtherSite]};
+  let data = {"suggested": [relatedTile1, relatedTile2, relatedTile3], "directory": [someOtherSite]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
 
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   let links = yield fetchData();
 
   // Check we've populated related links as expected.
   do_check_eq(DirectoryLinksProvider._relatedLinks.size, 5);
 
@@ -600,49 +600,16 @@ add_task(function test_DirectoryLinksPro
 
   yield testObserver.deferred.promise;
   DirectoryLinksProvider._removeObservers();
   do_check_eq(DirectoryLinksProvider._observers.size, 0);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_linksURL_locale() {
-  let data = {
-    "en-US": [{url: "http://example.com", title: "US"}],
-    "zh-CN": [
-              {url: "http://example.net", title: "CN"},
-              {url:"http://example.net/2", title: "CN2"}
-    ],
-  };
-  let dataURI = 'data:application/json,' + JSON.stringify(data);
-
-  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
-
-  let links;
-  let expected_data;
-
-  links = yield fetchData();
-  do_check_eq(links.length, 1);
-  expected_data = [{url: "http://example.com", title: "US", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
-  isIdentical(links, expected_data);
-
-  yield promiseDirectoryDownloadOnPrefChange("general.useragent.locale", "zh-CN");
-
-  links = yield fetchData();
-  do_check_eq(links.length, 2)
-  expected_data = [
-    {url: "http://example.net", title: "CN", frecency: DIRECTORY_FRECENCY, lastVisitDate: 2},
-    {url: "http://example.net/2", title: "CN2", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}
-  ];
-  isIdentical(links, expected_data);
-
-  yield promiseCleanDirectoryLinksProvider();
-});
-
 add_task(function test_DirectoryLinksProvider__prefObserver_url() {
   yield promiseSetupDirectoryLinksProvider({linksURL: kTestURL});
 
   let links = yield fetchData();
   do_check_eq(links.length, 1);
   let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1}];
   isIdentical(links, expectedData);
 
@@ -662,18 +629,23 @@ add_task(function test_DirectoryLinksPro
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl + " ");
   // we now should see empty links
   newLinks = yield fetchData();
   isIdentical(newLinks, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
-add_task(function test_DirectoryLinksProvider_getLinks_noLocaleData() {
-  yield promiseSetupDirectoryLinksProvider({locale: 'zh-CN'});
+add_task(function test_DirectoryLinksProvider_getLinks_noDirectoryData() {
+  let data = {
+    "directory": [],
+  };
+  let dataURI = 'data:application/json,' + JSON.stringify(data);
+  yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
+
   let links = yield fetchData();
   do_check_eq(links.length, 0);
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_getLinks_badData() {
   let data = {
     "en-US": {
@@ -799,80 +771,80 @@ add_task(function test_DirectoryLinksPro
   yield OS.File.writeAtomic(directoryLinksFilePath, '{"en-US":');
   let data = yield fetchData();
   isIdentical(data, []);
 
   yield promiseCleanDirectoryLinksProvider();
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedLinks() {
-  let data = {"en-US": [
+  let data = {"directory": [
     {url: "ftp://example.com"},
     {url: "http://example.net"},
     {url: "javascript:5"},
     {url: "https://example.com"},
     {url: "httpJUNKjavascript:42"},
     {url: "data:text/plain,hi"},
     {url: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining url should be http and https
-  do_check_eq(links[0].url, data["en-US"][1].url);
-  do_check_eq(links[1].url, data["en-US"][3].url);
+  do_check_eq(links[0].url, data["directory"][1].url);
+  do_check_eq(links[1].url, data["directory"][3].url);
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedImages() {
-  let data = {"en-US": [
+  let data = {"directory": [
     {url: "http://example.com", imageURI: "ftp://example.com"},
     {url: "http://example.com", imageURI: "http://example.net"},
     {url: "http://example.com", imageURI: "javascript:5"},
     {url: "http://example.com", imageURI: "https://example.com"},
     {url: "http://example.com", imageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", imageURI: "data:text/plain,hi"},
     {url: "http://example.com", imageURI: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining images should be https and data
-  do_check_eq(links[0].imageURI, data["en-US"][3].imageURI);
-  do_check_eq(links[1].imageURI, data["en-US"][5].imageURI);
+  do_check_eq(links[0].imageURI, data["directory"][3].imageURI);
+  do_check_eq(links[1].imageURI, data["directory"][5].imageURI);
 });
 
 add_task(function test_DirectoryLinksProvider_getAllowedEnhancedImages() {
-  let data = {"en-US": [
+  let data = {"directory": [
     {url: "http://example.com", enhancedImageURI: "ftp://example.com"},
     {url: "http://example.com", enhancedImageURI: "http://example.net"},
     {url: "http://example.com", enhancedImageURI: "javascript:5"},
     {url: "http://example.com", enhancedImageURI: "https://example.com"},
     {url: "http://example.com", enhancedImageURI: "httpJUNKjavascript:42"},
     {url: "http://example.com", enhancedImageURI: "data:text/plain,hi"},
     {url: "http://example.com", enhancedImageURI: "http/bork:eh"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
   do_check_eq(links.length, 2);
 
   // The only remaining enhancedImages should be http and https and data
-  do_check_eq(links[0].enhancedImageURI, data["en-US"][3].enhancedImageURI);
-  do_check_eq(links[1].enhancedImageURI, data["en-US"][5].enhancedImageURI);
+  do_check_eq(links[0].enhancedImageURI, data["directory"][3].enhancedImageURI);
+  do_check_eq(links[1].enhancedImageURI, data["directory"][5].enhancedImageURI);
 });
 
 add_task(function test_DirectoryLinksProvider_getEnhancedLink() {
-  let data = {"en-US": [
+  let data = {"directory": [
     {url: "http://example.net", enhancedImageURI: "data:,net1"},
     {url: "http://example.com", enhancedImageURI: "data:,com1"},
     {url: "http://example.com", enhancedImageURI: "data:,com2"},
   ]};
   let dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
 
   let links = yield fetchData();
@@ -902,17 +874,17 @@ add_task(function test_DirectoryLinksPro
 
   // Undefined for not enhanced
   checkEnhanced("http://sub.example.net/", undefined);
   checkEnhanced("http://example.org", undefined);
   checkEnhanced("http://localhost", undefined);
   checkEnhanced("http://127.0.0.1", undefined);
 
   // Make sure old data is not cached
-  data = {"en-US": [
+  data = {"directory": [
     {url: "http://example.com", enhancedImageURI: "data:,fresh"},
   ]};
   dataURI = 'data:application/json,' + JSON.stringify(data);
   yield promiseSetupDirectoryLinksProvider({linksURL: dataURI});
   links = yield fetchData();
   do_check_eq(links.length, 1);
   checkEnhanced("http://example.net", undefined);
   checkEnhanced("http://example.com", "data:,fresh");
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -227,8 +227,24 @@
 
 .ruleview-selector {
   word-wrap: break-word;
 }
 
 .ruleview-selector-separator, .ruleview-selector-unmatched {
   color: #888;
 }
+
+.ruleview-selectorhighlighter {
+  background: url("chrome://browser/skin/devtools/vview-open-inspector.png") no-repeat 0 0;
+  padding-left: 16px;
+  margin-left: 5px;
+  cursor: pointer;
+}
+
+.ruleview-selectorhighlighter:hover {
+  background-position: -32px 0;
+}
+
+.ruleview-selectorhighlighter:active,
+.ruleview-selectorhighlighter.highlighted {
+  background-position: -16px 0;
+}
--- a/browser/themes/shared/newtab/newTab.inc.css
+++ b/browser/themes/shared/newtab/newTab.inc.css
@@ -137,16 +137,27 @@
 /* TITLES */
 #newtab-intro-what,
 .newtab-sponsored,
 .newtab-title,
 .newtab-suggested  {
   color: #5c5c5c;
 }
 
+.newtab-suggested:hover {
+  color: #588FE4;
+  border: 1px solid #588FE4;
+}
+
+.newtab-suggested[active] {
+  background-color: rgba(51, 51, 51, 0.95);
+  border: 0;
+  color: white;
+}
+
 .newtab-suggested {
   background-color: white;
 }
 
 .newtab-site:hover .newtab-title {
   color: #222;
 }
 
--- a/dom/media/webrtc/MediaEngineTabVideoSource.cpp
+++ b/dom/media/webrtc/MediaEngineTabVideoSource.cpp
@@ -194,29 +194,32 @@ MediaEngineTabVideoSource::Draw() {
   int32_t innerWidth, innerHeight;
   win->GetInnerWidth(&innerWidth);
   win->GetInnerHeight(&innerHeight);
 
   if (innerWidth == 0 || innerHeight == 0) {
     return;
   }
 
+  float pixelRatio;
+  win->GetDevicePixelRatio(&pixelRatio);
+  const int deviceInnerWidth = (int)(pixelRatio * innerWidth);
+  const int deviceInnerHeight = (int)(pixelRatio * innerHeight);
+
   IntSize size;
-  // maintain source aspect ratio
-  if (mBufWidthMax/innerWidth < mBufHeightMax/innerHeight) {
-    // mBufWidthMax is quite large by default, so use innerWidth if less.
-    int32_t width = std::min(innerWidth, mBufWidthMax);
-    // adjust width to be divisible by 4 to work around bug 1125393
-    width = width - (width % 4);
-    size = IntSize(width, (width * ((float) innerHeight/innerWidth)));
+
+  if ((deviceInnerWidth <= mBufWidthMax) && (deviceInnerHeight <= mBufHeightMax)) {
+    size = IntSize(deviceInnerWidth, deviceInnerHeight);
   } else {
-    int32_t width = std::min(innerHeight, mBufHeightMax) *
-                     ((float) innerWidth/innerHeight);
-    width =  width - (width % 4);
-    size = IntSize(width, (width * ((float) innerHeight/innerWidth)));
+
+    const float scaleWidth = (float)mBufWidthMax / (float)deviceInnerWidth;
+    const float scaleHeight = (float)mBufHeightMax / (float)deviceInnerHeight;
+    const float scale = scaleWidth < scaleHeight ? scaleWidth : scaleHeight;
+
+    size = IntSize((int)(scale * deviceInnerWidth), (int)(scale * deviceInnerHeight));
   }
 
   gfxImageFormat format = gfxImageFormat::RGB24;
   uint32_t stride = gfxASurface::FormatStrideForWidth(format, size.width);
 
   if (mDataSize < static_cast<size_t>(stride * size.height)) {
     mDataSize = stride * size.height;
     mData = static_cast<unsigned char*>(malloc(mDataSize));
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -548,16 +548,23 @@ just addresses the organization to follo
                                       from Android">
 <!ENTITY bookmarkhistory_import_bookmarks "Importing bookmarks
                                            from Android">
 <!ENTITY bookmarkhistory_import_history "Importing history
                                          from Android">
 <!ENTITY bookmarkhistory_import_wait "Please wait...">
 
 <!ENTITY suggestions_prompt3 "Would you like to turn on search suggestions?">
+<!--  Localization note (search_bar_item_desc): When the user clicks the url bar
+      and starts typing, a list of icons of search engines appears at the bottom
+      of the screen. When a user clicks an icon, the entered text will be searched
+      via the search engine that uses the icon they clicked. This text is used
+      for screen reader users when they hover each icon - &formatS; will be
+      replaced with the name of the currently highlighted icon. -->
+<!ENTITY search_bar_item_desc "Search with &formatS;">
 
 <!-- Localization note (suggestion_for_engine): The placeholder &formatS1; will be
      replaced with the name of the search engine. The placeholder &formatS2; will be
      replaced with the search query. -->
 <!ENTITY suggestion_for_engine "Search &formatS1; for &formatS2;">
 
 <!ENTITY webapp_generic_name "App">
 
--- a/mobile/android/base/overlays/ui/SendTabDeviceListArrayAdapter.java
+++ b/mobile/android/base/overlays/ui/SendTabDeviceListArrayAdapter.java
@@ -143,20 +143,20 @@ public class SendTabDeviceListArrayAdapt
             });
         }
 
         return row;
     }
 
     private static int getImage(ParcelableClientRecord record) {
         if ("mobile".equals(record.type)) {
-            return R.drawable.sync_mobile_inactive;
+            return R.drawable.device_mobile;
         }
 
-        return R.drawable.sync_desktop_inactive;
+        return R.drawable.device_pc;
     }
 
     public void switchState(State newState) {
         if (currentState == newState) {
             return;
         }
 
         currentState = newState;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..5b765970b48ba10d0b0680d056e966429ba6b16a
GIT binary patch
literal 201
zc%17D@N?(olHy`uVBq!ia0vp^qCl+3!3-oBl?3F0luCe4h%1oJ%E~H*fifge4g(R<
z?~;M)xJ!ckf*E*{QmPs*U%B@Cui|XJy+C14PZ!6K3dXmWtoa%Ycvvo;WtT~RS06vi
zL*S;6?W3uSb;GlgKbg2s)(V^|I8&xib_c`ThW&MG-SW3iuM1Dt)Y8%O-j@FF<e&ZG
XRqlKd-Q{cC89=7G`njxgN@xNAi4H`_
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..213c6776a6049b688ceca8e0a6ac88314a6cb0e6
GIT binary patch
literal 342
zc%17D@N?(olHy`uVBq!ia0vp^IzTMT!3-py)zn!7snP(S5LY0bnVDHyT3S$003^%6
zpuD^sD+y6RP#LmfWR(yG5bQW5%MP?ytt7}Vm_blNvaq<Jv8lPGwXMCQv#YzOx37P~
z#7UE<Or17;#>`o>4<5SmSF}TO7Eni)r;B4q#jUre%=sD&1Y9nv>RLXZlk=|LMSyqV
zhTazf`=!q|+j_dDtzvQid??c@@zmijE!!>mYOkA>hKf~AQ}dK*ZG6A8FL!3!zPsT(
zJ#~K``zNQA9DWm*@TAP*VnU^WK%uw8#Q+1}IL3P}7M+0_OGAOSGI+ZBxvX<aXaWEq
CHH!uS
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..99989be618aaba6b37a61a904cb4fbd925195ec9
GIT binary patch
literal 186
zc%17D@N?(olHy`uVBq!ia0vp^d_XM5!3-pi3&)BADa8Pv5LX~wR8&+}R#sYC3M9+H
zpbQ3p>}N{OEkH$FB|(0{46<gSvsPWYa_`^GOpilAQ9Dl;#}J9jcQ5P~WKiH>x%gTC
zcKty%gPv^<)Oa4+UQKn`wn{BL*zL4IqtTDYvXUpt-@gs_3-0>N;`hGs^Cj7seXN_O
T=kMGKG>O5})z4*}Q$iB}#kNB{
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..22b34961c5e19f4b6ad7122e5daca0114d0285e9
GIT binary patch
literal 254
zc%17D@N?(olHy`uVBq!ia0vp^GC(ZM!3-p~w9YLCQXT<5A+A8WtgNiGw6qKi%FD~K
zk`M*BlmUVA-94H>v&2h+{DK+Gtm_*Zo0?l%+uA!iySjUN`}!xm{%F@{YX?-~=jq}Y
zQgQ3;Nl(581s;Y2POrPVq`&;Pzv{wlm|OoKhCPqZ(}BJJNY@v&&y{a(OHHrjN|gDv
xF1KT+3P*k2<d#`ola{PXu1O1K>rZj0XH4zlO_{v<VK&fY22WQ%mvv4FO#oCHVJrXu
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..269d4d5dc15897fe4e7595e871ba02f6243c77c0
GIT binary patch
literal 263
zc%17D@N?(olHy`uVBq!ia0vp^GC-`u!3-oFS88wrDVqSF5LX}_8XB6Ln_CJ7<>lo-
zvaGDE91I|gGB7~qLS&sfKAr^X7cL3%3ua*Bvkfh(scl?!{M6~Q7oNZP@!KHa-esV4
zx~Gd{NCo5DD;tHH3<TI7+9!xyTyT8f|J}1r&F-;u`&IMyoA%Pw+dntvd#`%y_+Vy<
zYDutSNXmh|%m#rgo3oAxy4wiq%ifpxX}_<yuC;L5Gc~EaNosReKG-y+aB1w>UE5Y$
d&bspOo}JSR?K|A7V?b6ic)I$ztaD0e0ssJVY$E^w
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2495899e4dff9773f4de939491fcaf3cf5fbffa
GIT binary patch
literal 392
zc%17D@N?(olHy`uVBq!ia0vp^7C@}R!3-o{`*5BCQoR8_A+8Ky5E>d<US3{WS_&b{
zV4xffAPhJOQ2<wlsQ^<kvVoW+oRJ`RcqY&_dL==A!3<2yo?c<$we<~+%`L5M?H!$6
z-95d1{SzimnmlFdwCOWu&YC@E?!5U67B2em@y8o|?F^vK8c!F;5Rc<;uNZPQ81Oh;
zTq1Vxr1B08i;Va6eNB@myB7#8+3F<QC8Bat?F;|OFDJxWW~>vruxn1g^y3`s1>4_p
z`gJk>owR__?;CGy!-p?hf6GK&G7U~>WEJ}nykpXX3C<s##SXTy_H$IKwYqVoi|lMj
i)@@SPTKmJix!gQ4Ibl<2vThnEKs{akT-G@yGywo|HKD@*
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..fd61563444a9650294b205225ee73b401aabb448
GIT binary patch
literal 296
zc%17D@N?(olHy`uVBq!ia0vp^T0rc?!3-qDc5R6RaRPioTp3`XprD|zu&}texU8(K
zw6wImyd21YkZ7PB1|aO$&rPC%`bA5E{DK)6nIx4oGz*K0%gSfYT5#vV*Y7`nC2P;^
z04iwkba4!+xb^nhZXpK)k(P(5dWwogCw}kOYU@i3o)I`_zvpLB%ezUd<(8}3Zt+QO
z-h0Ta%6FHL!|WA00j_<=xa=nKa_I01OylYk_n+&wGH9vSRISioqN>02=gu|ETejlx
zw8blKuH3osP>^GSy}*O~^RL#Y|Lj>f-B>>G(&8!ecWJ-dkoU)P!e%y*Z#`Z8T-G@y
GGywoSa&wgc
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4b09888467c2a05b22f21079dc5983af540ffc7d
GIT binary patch
literal 554
zc%17D@N?(olHy`uVBq!ia0vp^5kPFg!3-q*_MeXdQjEnx?oJHr&dIz4avlcwgt#)m
zKuJkS85oq7mX?>7L&!2z01<(ZKm~9TO$w8QD<eiRk;>qT%fSFmJ6xn`>AKxO?|GF3
z`2{mDGI8^|dU$%3Ro6B&wY0XicXW1j_w@GlPnbAq@|3C5rq7r;YxbPE^X4yDxM=Z`
zrOTGDSh;HTnzifJZ`inL^OmjK_8vZY>hz<>KY#Uqj5`c8b)lz=V@Sl|x7QuH4jBlv
zKAi3;$g2O~5O)Rp(XGq*zHrSuFjqXlV4L!mCERPdU-kr4{8u~q%sU~BQzLYC?2#q&
zo@X1&INdx^*m?5qWWKnYIy^CJv}z78b-O*tU^;z5G}gTJ+uvQU{@uR4TWF1th3gE1
z#6vBd!fHM<3?AAa$d7N`Dqi#8LmJEZ^#zYUaNYT}V{MAOh4)+L>l=6Gs7kD!b@?%C
X*$WSeLgfp;KvC@J>gTe~DWM4fm(TU~
--- a/mobile/android/base/resources/layout/anchored_popup.xml
+++ b/mobile/android/base/resources/layout/anchored_popup.xml
@@ -2,17 +2,20 @@
 <!-- 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/. -->
 
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
               android:layout_width="match_parent"
               android:layout_height="wrap_content"
               android:background="@drawable/dropshadow"
-              android:padding="3dp">
+              android:paddingLeft="3dp"
+              android:paddingRight="3dp"
+              android:paddingTop="3dp"
+              android:paddingBottom="4dp">
 
     <ScrollView android:layout_width="match_parent"
                 android:layout_height="wrap_content"
                 android:background="@drawable/anchored_popup_bg">
 
         <LinearLayout android:id="@+id/content"
                       android:layout_width="match_parent"
                       android:layout_height="wrap_content"
--- a/mobile/android/base/resources/layout/overlay_share_button.xml
+++ b/mobile/android/base/resources/layout/overlay_share_button.xml
@@ -7,17 +7,17 @@
     <ImageView
         android:layout_width="60dp"
         android:layout_height="match_parent"
         android:id="@+id/overlaybtn_icon"
         android:padding="30dp"
         android:scaleType="center"/>
 
     <TextView
-        android:textAppearance="@style/ShareOverlayTextAppearance"
+        android:textAppearance="@style/TextAppearance.ShareOverlay"
         android:id="@+id/overlaybtn_label"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:clickable="false"
         android:enabled="false"
         android:maxLines="1"
         android:textSize="14sp"
         android:textColor="@color/primary_text_selector"/>
--- a/mobile/android/base/resources/layout/overlay_share_dialog.xml
+++ b/mobile/android/base/resources/layout/overlay_share_dialog.xml
@@ -20,29 +20,29 @@
         android:layout_gravity="bottom|center"
         android:paddingTop="8dp"
         android:orientation="vertical">
 
         <!-- Title -->
         <TextView
             android:id="@+id/title"
             style="@style/ShareOverlayTitle"
-            android:textAppearance="@style/ShareOverlayTextAppearance.Header"
+            android:textAppearance="@style/TextAppearance.ShareOverlay.Header"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="8dp"
             android:maxLines="2"
             android:textSize="20sp"
             android:ellipsize="end"/>
 
         <!-- Subtitle (url) -->
         <TextView
             android:id="@+id/subtitle"
             style="@style/ShareOverlayTitle"
-            android:textAppearance="@style/ShareOverlayTextAppearance.Header"
+            android:textAppearance="@style/TextAppearance.ShareOverlay.Header"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginBottom="20dp"
             android:textSize="12sp"
             android:scrollHorizontally="true"/>
 
         <!-- TODO: Add back drop shadow (bug 1146488)? -->
         <!-- Buttons -->
--- a/mobile/android/base/resources/values/styles.xml
+++ b/mobile/android/base/resources/values/styles.xml
@@ -779,21 +779,21 @@
     </style>
 
     <style name="ShareOverlayTitle">
         <item name="android:gravity">center_horizontal</item>
         <item name="android:paddingLeft">15dp</item>
         <item name="android:paddingRight">15dp</item>
     </style>
 
-    <style name="ShareOverlayTextAppearance">
+    <style name="TextAppearance.ShareOverlay">
         <item name="android:fontFamily">sans-serif</item>
     </style>
 
-    <style name="ShareOverlayTextAppearance.Header">
+    <style name="TextAppearance.ShareOverlay.Header">
         <item name="android:textColor">@android:color/white</item>
     </style>
 
     <style name="ShareOverlayRow">
         <item name="android:minHeight">60dp</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:background">@drawable/overlay_share_button_background</item>
         <item name="android:focusableInTouchMode">false</item>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -469,18 +469,19 @@
   <string name="updater_downloading_title">&updater_downloading_title2;</string>
   <string name="updater_downloading_title_failed">&updater_downloading_title_failed2;</string>
   <string name="updater_downloading_select">&updater_downloading_select2;</string>
   <string name="updater_downloading_retry">&updater_downloading_retry2;</string>
 
   <string name="updater_apply_title">&updater_apply_title2;</string>
   <string name="updater_apply_select">&updater_apply_select2;</string>
 
-  <!-- Search suggestions opt-in -->
+  <!-- Awesomescreen screen -->
   <string name="suggestions_prompt">&suggestions_prompt3;</string>
+  <string name="search_bar_item_desc">&search_bar_item_desc;</string>
 
   <string name="suggestion_for_engine">&suggestion_for_engine;</string>
 
   <!-- Set Image Notifications -->
   <string name="set_image_fail">&set_image_fail;</string>
   <string name="set_image_path_fail">&set_image_path_fail;</string>
   <string name="set_image_chooser_title">&set_image_chooser_title;</string>
 
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1024,18 +1024,20 @@ pref("content.sink.pending_event_mode", 
 // Disable popups from plugins by default
 //   0 = openAllowed
 //   1 = openControlled
 //   2 = openAbused
 pref("privacy.popups.disable_from_plugins", 2);
 
 // send "do not track" HTTP header, disabled by default
 pref("privacy.donottrackheader.enabled",    false);
-// Enforce tracking protection
+// Enforce tracking protection in all modes
 pref("privacy.trackingprotection.enabled",  false);
+// Enforce tracking protection in Private Browsing mode
+pref("privacy.trackingprotection.pbmode.enabled",  false);
 
 pref("dom.event.contextmenu.enabled",       true);
 pref("dom.event.clipboardevents.enabled",   true);
 #if defined(XP_WIN) && !defined(RELEASE_BUILD)
 pref("dom.event.highrestimestamp.enabled",  true);
 #else
 pref("dom.event.highrestimestamp.enabled",  false);
 #endif
--- a/netwerk/base/nsChannelClassifier.cpp
+++ b/netwerk/base/nsChannelClassifier.cpp
@@ -63,17 +63,19 @@ nsChannelClassifier::ShouldEnableTrackin
                                                     bool *result)
 {
     // Should only be called in the parent process.
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
     NS_ENSURE_ARG(result);
     *result = false;
 
-    if (!Preferences::GetBool("privacy.trackingprotection.enabled", false)) {
+    if (!Preferences::GetBool("privacy.trackingprotection.enabled", false) &&
+        (!Preferences::GetBool("privacy.trackingprotection.pbmode.enabled",
+                               false) || !NS_UsePrivateBrowsing(aChannel))) {
       return NS_OK;
     }
 
     nsresult rv;
     nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil =
         do_GetService(THIRDPARTYUTIL_CONTRACTID, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
     // Third party checks don't work for chrome:// URIs in mochitests, so just
--- a/python/mozboot/mozboot/android.py
+++ b/python/mozboot/mozboot/android.py
@@ -4,16 +4,17 @@
 
 # If we add unicode_literals, Python 2.6.1 (required for OS X 10.6) breaks.
 from __future__ import print_function
 
 import errno
 import os
 import subprocess
 
+
 # These are the platform and build-tools versions for building
 # mobile/android, respectively. Try to keep these in synch with the
 # build system and Mozilla's automation.
 ANDROID_PLATFORM = 'android-21'
 ANDROID_BUILD_TOOLS_VERSION = '21.1.2'
 
 # These are the "Android packages" needed for building Firefox for Android.
 # Use |android list sdk --extended| to see these identifiers.
@@ -77,28 +78,29 @@ ac_add_options --with-android-ndk="%s"
 
 def check_output(*args, **kwargs):
     """Run subprocess.check_output even if Python doesn't provide it."""
     from base import BaseBootstrapper
     fn = getattr(subprocess, 'check_output', BaseBootstrapper._check_output)
 
     return fn(*args, **kwargs)
 
+
 def list_missing_android_packages(android_tool, packages):
     '''
     Use the given |android| tool to return the sub-list of Android
     |packages| given that are not installed.
     '''
     missing = []
 
     # There's no obvious way to see what's been installed already,
     # but packages that are installed don't appear in the list of
     # available packages.
     lines = check_output([android_tool,
-        'list', 'sdk', '--no-ui', '--extended']).splitlines()
+                          'list', 'sdk', '--no-ui', '--extended']).splitlines()
 
     # Lines look like: 'id: 59 or "extra-google-simulators"'
     for line in lines:
         is_id_line = False
         try:
             is_id_line = line.startswith("id:")
         except:
             # Some lines contain non-ASCII characters.  Ignore them.
@@ -108,16 +110,17 @@ def list_missing_android_packages(androi
 
         for package in packages:
             if '"%s"' % package in line:
                 # Not installed!
                 missing.append(package)
 
     return missing
 
+
 def install_mobile_android_sdk_or_ndk(url, path):
     '''
     Fetch an Android SDK or NDK from |url| and unpack it into
     the given |path|.
 
     We expect wget to be installed and found on the system path.
 
     We use, and wget respects, https.  We could also include SHAs for a
@@ -153,16 +156,17 @@ def install_mobile_android_sdk_or_ndk(ur
         elif file.endswitch('.zip'):
             cmd = ['unzip']
         else:
             raise NotImplementedError("Don't know how to unpack file: %s" % file)
         subprocess.check_call(cmd + [os.path.join(download_path, file)])
     finally:
         os.chdir(old_path)
 
+
 def ensure_android_sdk_and_ndk(path, sdk_path, sdk_url, ndk_path, ndk_url):
     '''
     Ensure the Android SDK and NDK are found at the given paths.  If not, fetch
     and unpack the SDK and/or NDK from the given URLs into |path|.
     '''
 
     # It's not particularyl bad to overwrite the NDK toolchain, but it does take
     # a while to unpack, so let's avoid the disk activity if possible.  The SDK
@@ -175,16 +179,17 @@ def ensure_android_sdk_and_ndk(path, sdk
     # We don't want to blindly overwrite, since we use the |android| tool to
     # install additional parts of the Android toolchain.  If we overwrite,
     # we lose whatever Android packages the user may have already installed.
     if os.path.isdir(sdk_path):
         print(ANDROID_SDK_EXISTS % sdk_path)
     else:
         install_mobile_android_sdk_or_ndk(sdk_url, path)
 
+
 def ensure_android_packages(android_tool, packages=None):
     '''
     Use the given android tool (like 'android') to install required Android
     packages.
     '''
 
     if not packages:
         packages = ANDROID_PACKAGES
@@ -193,18 +198,19 @@ def ensure_android_packages(android_tool
     if not missing:
         print(NOT_INSTALLING_ANDROID_PACKAGES % ', '.join(packages))
         return
 
     # This tries to install all the required Android packages.  The user
     # may be prompted to agree to the Android license.
     print(INSTALLING_ANDROID_PACKAGES % ', '.join(missing))
     subprocess.check_call([android_tool,
-        'update', 'sdk', '--no-ui',
-        '--filter', ','.join(missing)])
+                           'update', 'sdk', '--no-ui',
+                           '--filter', ','.join(missing)])
 
     # Let's verify.
     failing = list_missing_android_packages(android_tool, packages=packages)
     if failing:
         raise Exception(MISSING_ANDROID_PACKAGES % (', '.join(missing), ', '.join(failing)))
 
+
 def suggest_mozconfig(sdk_path=None, ndk_path=None):
     print(MOBILE_ANDROID_MOZCONFIG_TEMPLATE % (sdk_path, ndk_path))
--- a/python/mozboot/mozboot/archlinux.py
+++ b/python/mozboot/mozboot/archlinux.py
@@ -5,16 +5,17 @@
 import os
 import sys
 import tempfile
 import subprocess
 import glob
 
 from mozboot.base import BaseBootstrapper
 
+
 class ArchlinuxBootstrapper(BaseBootstrapper):
     '''Archlinux experimental bootstrapper.'''
 
     SYSTEM_PACKAGES = [
         'autoconf2.13',
         'base-devel',
         'ccache',
         'mercurial',
@@ -69,17 +70,17 @@ class ArchlinuxBootstrapper(BaseBootstra
         self.pacman_install(*self.SYSTEM_PACKAGES)
         self.aur_install(*self.AUR_PACKAGES)
 
     def install_browser_packages(self):
         self.pacman_install(*self.BROWSER_PACKAGES)
 
     def install_mobile_android_packages(self):
         raise NotImplementedError('Bootstrap support for mobile-android is '
-                'not yet available for Archlinux')
+                                  'not yet available for Archlinux')
 
     def _update_package_manager(self):
         self.pacman_update
 
     def upgrade_mercurial(self, current):
         self.pacman_install('mercurial')
 
     def upgrade_python(self, current):
@@ -120,19 +121,19 @@ class ArchlinuxBootstrapper(BaseBootstra
         self.run(command)
         pack = glob.glob(name + '*.tar.xz')[0]
         command = ['pacman', '-U', pack]
         self.run_as_root(command)
 
     def aur_install(self, *packages):
         path = tempfile.mkdtemp()
         print('WARNING! This script requires to install packages from the AUR '
-                'This is potentially unsecure so I recommend that you carefully '
-                'read each package description and check the sources.'
-                'These packages will be built in ' + path + '.')
+              'This is potentially unsecure so I recommend that you carefully '
+              'read each package description and check the sources.'
+              'These packages will be built in ' + path + '.')
         choice = raw_input('Do you want to continue? (yes/no) [no]')
         if choice != 'yes':
             sys.exit(1)
 
         base_dir = os.getcwd()
         os.chdir(path)
         for package in packages:
             name, _, ext = package.split('/')[-1].split('.')
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -88,25 +88,26 @@ class BaseBootstrapper(object):
 
     def install_system_packages(self):
         '''
         Install packages shared by all applications. These are usually
         packages required by the development (like mercurial) or the
         build system (like autoconf).
         '''
         raise NotImplementedError('%s must implement install_system_packages()' %
-            __name__)
+                                  __name__)
 
     def install_browser_packages(self):
         '''
         Install packages required to build Firefox for Desktop (application
         'browser').
         '''
         raise NotImplementedError('Cannot bootstrap Firefox for Desktop: '
-            '%s does not yet implement install_browser_packages()' % __name__)
+                                  '%s does not yet implement install_browser_packages()' %
+                                  __name__)
 
     def suggest_browser_mozconfig(self):
         '''
         Print a message to the console detailing what the user's mozconfig
         should contain.
 
         Firefox for Desktop can in simple cases determine its build environment
         entirely from configure.
@@ -114,28 +115,29 @@ class BaseBootstrapper(object):
         pass
 
     def install_mobile_android_packages(self):
         '''
         Install packages required to build Firefox for Android (application
         'mobile/android', also known as Fennec).
         '''
         raise NotImplementedError('Cannot bootstrap Firefox for Android: '
-            '%s does not yet implement install_mobile_android_packages()' % __name__)
+                                  '%s does not yet implement install_mobile_android_packages()'
+                                  % __name__)
 
     def suggest_mobile_android_mozconfig(self):
         '''
         Print a message to the console detailing what the user's mozconfig
         should contain.
 
         Firefox for Android needs an application and an ABI set, and it needs
         paths to the Android SDK and NDK.
         '''
         raise NotImplementedError('%s does not yet implement suggest_mobile_android_mozconfig()' %
-            __name__)
+                                  __name__)
 
     def which(self, name):
         """Python implementation of which.
 
         It returns the path of an executable or None if it couldn't be found.
         """
         for path in os.environ['PATH'].split(os.pathsep):
             test = os.path.join(path, name)
@@ -271,17 +273,17 @@ class BaseBootstrapper(object):
 
         return True, our >= MODERN_MERCURIAL_VERSION, our
 
     def ensure_mercurial_modern(self):
         installed, modern, version = self.is_mercurial_modern()
 
         if not installed or modern:
             print('Your version of Mercurial (%s) is sufficiently modern.' %
-                version)
+                  version)
             return
 
         self._ensure_package_manager_updated()
         self.upgrade_mercurial(version)
 
         installed, modern, after = self.is_mercurial_modern()
 
         if installed and not modern:
@@ -300,17 +302,17 @@ class BaseBootstrapper(object):
         for test in ['python2.7', 'python']:
             python = self.which(test)
             if python:
                 break
 
         assert python
 
         info = self.check_output([python, '--version'],
-            stderr=subprocess.STDOUT)
+                                 stderr=subprocess.STDOUT)
         match = re.search('Python ([a-z0-9\.]+)', info)
         if not match:
             print('ERROR Unable to identify Python version.')
             return False, None
 
         our = LooseVersion(match.group(1))
 
         return our >= MODERN_PYTHON_VERSION, our
@@ -318,17 +320,17 @@ class BaseBootstrapper(object):
     def ensure_python_modern(self):
         modern, version = self.is_python_modern()
 
         if modern:
             print('Your version of Python (%s) is new enough.' % version)
             return
 
         print('Your version of Python (%s) is too old. Will try to upgrade.' %
-            version)
+              version)
 
         self._ensure_package_manager_updated()
         self.upgrade_python(version)
 
         modern, after = self.is_python_modern()
 
         if not modern:
             print(PYTHON_UPGRADE_FAILED % (MODERN_PYTHON_VERSION, after))
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -92,31 +92,30 @@ class Bootstrapper(object):
             cls = OSXBootstrapper
             args['version'] = osx_version
 
         elif sys.platform.startswith('openbsd'):
             cls = OpenBSDBootstrapper
             args['version'] = platform.uname()[2]
 
         elif sys.platform.startswith('dragonfly') or \
-             sys.platform.startswith('freebsd'):
+                sys.platform.startswith('freebsd'):
             cls = FreeBSDBootstrapper
             args['version'] = platform.release()
-            args['flavor']  = platform.system()
+            args['flavor'] = platform.system()
 
         if cls is None:
             raise NotImplementedError('Bootstrap support is not yet available '
                                       'for your OS.')
 
         self.instance = cls(**args)
 
-
     def bootstrap(self):
         # Like ['1. Firefox for Desktop', '2. Firefox for Android'].
-        labels = [ '%s. %s' % (i + 1, name) for (i, (name, _)) in enumerate(APPLICATIONS) ]
+        labels = ['%s. %s' % (i + 1, name) for (i, (name, _)) in enumerate(APPLICATIONS)]
         prompt = APPLICATION_CHOICE % '\n'.join(labels)
         choice = self.instance.prompt_int(prompt=prompt, low=1, high=len(APPLICATIONS))
         name, application = APPLICATIONS[choice-1]
 
         self.instance.install_system_packages()
 
         # Like 'install_browser_packages' or 'install_mobile_android_packages'.
         getattr(self.instance, 'install_%s_packages' % application)()
--- a/python/mozboot/mozboot/centos.py
+++ b/python/mozboot/mozboot/centos.py
@@ -1,17 +1,18 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import os
 import platform
 
+
 from mozboot.base import BaseBootstrapper
 
+
 class CentOSBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
         self.group_packages = [
@@ -57,9 +58,8 @@ class CentOSBootstrapper(BaseBootstrappe
         yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.i686.rpm'
         if 'x86_64' in kern[2]:
             yasm = 'http://pkgs.repoforge.org/yasm/yasm-1.1.0-1.el6.rf.x86_64.rpm'
 
         self.run_as_root(['rpm', '-ivh', yasm])
 
     def upgrade_mercurial(self, current):
         self.yum_update('mercurial')
-
--- a/python/mozboot/mozboot/debian.py
+++ b/python/mozboot/mozboot/debian.py
@@ -2,16 +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/.
 
 import os
 import sys
 
 from mozboot.base import BaseBootstrapper
 
+
 class DebianBootstrapper(BaseBootstrapper):
     # These are common packages for all Debian-derived distros (such as
     # Ubuntu).
     COMMON_PACKAGES = [
         'autoconf2.13',
         'build-essential',
         'ccache',
         'mercurial',
@@ -47,21 +48,21 @@ class DebianBootstrapper(BaseBootstrappe
     ]
 
     # Subclasses can add packages to this variable to have them installed.
     BROWSER_DISTRO_PACKAGES = []
 
     # These are common packages for building Firefox for Android
     # (mobile/android) for all Debian-derived distros (such as Ubuntu).
     MOBILE_ANDROID_COMMON_PACKAGES = [
-        'zlib1g-dev', # mobile/android requires system zlib.
+        'zlib1g-dev',  # mobile/android requires system zlib.
         'openjdk-7-jdk',
         'ant',
-        'wget', # For downloading the Android SDK and NDK.
-        'libncurses5:i386', # See comments about i386 below.
+        'wget',  # For downloading the Android SDK and NDK.
+        'libncurses5:i386',  # See comments about i386 below.
         'libstdc++6:i386',
         'zlib1g:i386',
     ]
 
     # Subclasses can add packages to this variable to have them installed.
     MOBILE_ANDROID_DISTRO_PACKAGES = []
 
     def __init__(self, version, dist_id):
@@ -106,27 +107,26 @@ class DebianBootstrapper(BaseBootstrappe
         self.ndk_path = os.environ.get('ANDROID_NDK_HOME', os.path.join(mozbuild_path, 'android-ndk-r8e'))
         self.sdk_url = 'https://dl.google.com/android/android-sdk_r24.0.1-linux.tgz'
         is_64bits = sys.maxsize > 2**32
         if is_64bits:
             self.ndk_url = 'https://dl.google.com/android/ndk/android-ndk-r8e-linux-x86_64.tar.bz2'
         else:
             self.ndk_url = 'https://dl.google.com/android/ndk/android-ndk-r8e-linux-x86.tar.bz2'
         android.ensure_android_sdk_and_ndk(path=mozbuild_path,
-            sdk_path=self.sdk_path, sdk_url=self.sdk_url,
-            ndk_path=self.ndk_path, ndk_url=self.ndk_url)
+                                           sdk_path=self.sdk_path, sdk_url=self.sdk_url,
+                                           ndk_path=self.ndk_path, ndk_url=self.ndk_url)
 
         # 3. We expect the |android| tool to at
         # ~/.mozbuild/android-sdk-linux/tools/android.
         android_tool = os.path.join(self.sdk_path, 'tools', 'android')
         android.ensure_android_packages(android_tool=android_tool)
 
     def suggest_mobile_android_mozconfig(self):
         import android
 
         # The SDK path that mozconfig wants includes platforms/android-21.
         sdk_path = os.path.join(self.sdk_path, 'platforms', android.ANDROID_PLATFORM)
         android.suggest_mozconfig(sdk_path=sdk_path,
-            ndk_path=self.ndk_path)
+                                  ndk_path=self.ndk_path)
 
     def _update_package_manager(self):
         self.run_as_root(['apt-get', 'update'])
-
--- a/python/mozboot/mozboot/fedora.py
+++ b/python/mozboot/mozboot/fedora.py
@@ -1,15 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import os
+from mozboot.base import BaseBootstrapper
 
-from mozboot.base import BaseBootstrapper
 
 class FedoraBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
--- a/python/mozboot/mozboot/freebsd.py
+++ b/python/mozboot/mozboot/freebsd.py
@@ -1,19 +1,20 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from mozboot.base import BaseBootstrapper
 
+
 class FreeBSDBootstrapper(BaseBootstrapper):
     def __init__(self, version, flavor):
         BaseBootstrapper.__init__(self)
         self.version = int(version.split('.')[0])
-        self.flavor  = flavor.lower()
+        self.flavor = flavor.lower()
 
         self.packages = [
             'autoconf213',
             'gmake',
             'mercurial',
             'pkgconf',
             'zip',
         ]
--- a/python/mozboot/mozboot/gentoo.py
+++ b/python/mozboot/mozboot/gentoo.py
@@ -1,15 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import os
+from mozboot.base import BaseBootstrapper
 
-from mozboot.base import BaseBootstrapper
 
 class GentooBootstrapper(BaseBootstrapper):
     def __init__(self, version, dist_id):
         BaseBootstrapper.__init__(self)
 
         self.version = version
         self.dist_id = dist_id
 
--- a/python/mozboot/mozboot/mach_commands.py
+++ b/python/mozboot/mozboot/mach_commands.py
@@ -1,24 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this,
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import unicode_literals
 
 from mach.decorators import (
-    CommandArgument,
     CommandProvider,
     Command,
 )
 
 
 @CommandProvider
 class Bootstrap(object):
     """Bootstrap system and mach for optimal development experience."""
 
     @Command('bootstrap', category='devenv',
-        description='Install required system packages for building.')
+             description='Install required system packages for building.')
     def bootstrap(self):
         from mozboot.bootstrap import Bootstrapper
 
         bootstrapper = Bootstrapper()
         bootstrapper.bootstrap()
--- a/python/mozboot/mozboot/openbsd.py
+++ b/python/mozboot/mozboot/openbsd.py
@@ -1,15 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
-import os
+from mozboot.base import BaseBootstrapper
 
-from mozboot.base import BaseBootstrapper
 
 class OpenBSDBootstrapper(BaseBootstrapper):
     def __init__(self, version):
         BaseBootstrapper.__init__(self)
 
         self.packages = [
             'mercurial',
             'autoconf-2.13',
--- a/python/mozboot/mozboot/osx.py
+++ b/python/mozboot/mozboot/osx.py
@@ -21,17 +21,17 @@ from mozboot.base import BaseBootstrappe
 HOMEBREW_BOOTSTRAP = 'https://raw.githubusercontent.com/Homebrew/install/master/install'
 XCODE_APP_STORE = 'macappstore://itunes.apple.com/app/id497799835?mt=12'
 XCODE_LEGACY = 'https://developer.apple.com/downloads/download.action?path=Developer_Tools/xcode_3.2.6_and_ios_sdk_4.3__final/xcode_3.2.6_and_ios_sdk_4.3.dmg'
 HOMEBREW_AUTOCONF213 = 'https://raw.github.com/Homebrew/homebrew-versions/master/autoconf213.rb'
 
 MACPORTS_URL = {'9': 'https://distfiles.macports.org/MacPorts/MacPorts-2.2.1-10.9-Mavericks.pkg',
                 '8': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.8-MountainLion.pkg',
                 '7': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.7-Lion.pkg',
-                '6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.6-SnowLeopard.pkg',}
+                '6': 'https://distfiles.macports.org/MacPorts/MacPorts-2.1.3-10.6-SnowLeopard.pkg', }
 
 MACPORTS_CLANG_PACKAGE = 'clang-3.3'
 
 RE_CLANG_VERSION = re.compile('Apple (?:clang|LLVM) version (\d+\.\d+)')
 
 APPLE_CLANG_MINIMUM_VERSION = StrictVersion('4.2')
 
 XCODE_REQUIRED = '''
@@ -161,16 +161,17 @@ this bootstrap again.
 '''
 
 JAVA_LICENSE_NOTICE = '''
 We installed a recent Java toolchain for you. We agreed to the Oracle Java
 license for you by downloading the JDK. If this is unacceptable you should
 uninstall.
 '''
 
+
 class OSXBootstrapper(BaseBootstrapper):
     def __init__(self, version):
         BaseBootstrapper.__init__(self)
 
         self.os_version = StrictVersion(version)
 
         if self.os_version < StrictVersion('10.6'):
             raise Exception('OS X 10.6 or above is required.')
@@ -205,46 +206,46 @@ class OSXBootstrapper(BaseBootstrapper):
         # still install Xcode into any arbitrary location. We honor the
         # location of Xcode as set by xcode-select. This should also pick up
         # developer preview releases of Xcode, which can be installed into
         # paths like /Applications/Xcode5-DP6.app.
         elif self.os_version >= StrictVersion('10.7'):
             select = self.which('xcode-select')
             try:
                 output = self.check_output([select, '--print-path'],
-                    stderr=subprocess.STDOUT)
+                                           stderr=subprocess.STDOUT)
             except subprocess.CalledProcessError as e:
                 # This seems to appear on fresh OS X machines before any Xcode
                 # has been installed. It may only occur on OS X 10.9 and later.
                 if 'unable to get active developer directory' in e.output:
                     print(XCODE_NO_DEVELOPER_DIRECTORY)
                     self._install_xcode_app_store()
-                    assert False # Above should exit.
+                    assert False  # Above should exit.
 
                 output = e.output
 
             # This isn't the most robust check in the world. It relies on the
             # default value not being in an application bundle, which seems to
             # hold on at least Mavericks.
             if '.app/' not in output:
                 print(XCODE_REQUIRED)
                 self._install_xcode_app_store()
-                assert False # Above should exit.
+                assert False  # Above should exit.
 
         # Once Xcode is installed, you need to agree to the license before you can
         # use it.
         try:
             output = self.check_output(['/usr/bin/xcrun', 'clang'],
-                stderr=subprocess.STDOUT)
+                                       stderr=subprocess.STDOUT)
         except subprocess.CalledProcessError as e:
             if 'license' in e.output:
                 xcodebuild = self.which('xcodebuild')
                 try:
                     subprocess.check_call([xcodebuild, '-license'],
-                        stderr=subprocess.STDOUT)
+                                          stderr=subprocess.STDOUT)
                 except subprocess.CalledProcessError as e:
                     if 'requires admin privileges' in e.output:
                         self.run_as_root([xcodebuild, '-license'])
 
         # Even then we're not done! We need to install the Xcode command line tools.
         # As of Mountain Lion, apparently the only way to do this is to go through a
         # menu dialog inside Xcode itself. We're not making this up.
         if self.os_version >= StrictVersion('10.7'):
@@ -314,42 +315,42 @@ class OSXBootstrapper(BaseBootstrapper):
         ]
         self._ensure_homebrew_packages(packages)
 
         installed = self.check_output([self.brew, 'list']).split()
         if self.os_version < StrictVersion('10.7') and 'llvm' not in installed:
             print(PACKAGE_MANAGER_OLD_CLANG % ('Homebrew',))
 
             subprocess.check_call([self.brew, '-v', 'install', 'llvm',
-                '--with-clang', '--all-targets'])
+                                   '--with-clang', '--all-targets'])
 
     def ensure_homebrew_mobile_android_packages(self):
         import android
 
         # If we're run from a downloaded bootstrap.py, then android-ndk.rb is
         # fetched into a temporary directory.  This finds that directory.
         import inspect
         path_to_android = os.path.abspath(os.path.dirname(inspect.getfile(android)))
 
         # We don't need wget because we install the Android SDK and NDK from
         # packages.  If we used the android.py module, we'd need wget.
         packages = [
             ('android-sdk', 'android-sdk'),
-            ('android-ndk', os.path.join(path_to_android, 'android-ndk.rb')), # This is a locally provided brew formula!
+            ('android-ndk', os.path.join(path_to_android, 'android-ndk.rb')),  # This is a locally provided brew formula!
             ('ant', 'ant'),
-            ('brew-cask', 'caskroom/cask/brew-cask'), # For installing Java later.
+            ('brew-cask', 'caskroom/cask/brew-cask'),  # For installing Java later.
         ]
         self._ensure_homebrew_packages(packages)
 
         casks = [
             ('java', 'java'),
         ]
         installed = self._ensure_homebrew_casks(casks)
         if installed:
-            print(JAVA_LICENSE_NOTICE) # We accepted a license agreement for the user.
+            print(JAVA_LICENSE_NOTICE)  # We accepted a license agreement for the user.
 
         # We could probably fish this path from |brew info android-sdk|.
         android_tool = '/usr/local/opt/android-sdk/tools/android'
         android.ensure_android_packages(android_tool)
 
     def suggest_homebrew_mobile_android_mozconfig(self):
         import android
         # We could probably fish this path from |brew info android-sdk|.
@@ -456,17 +457,17 @@ class OSXBootstrapper(BaseBootstrapper):
             tf.flush()
 
             subprocess.check_call(['ruby', tf.name])
 
     def install_macports(self):
         url = MACPORTS_URL.get(self.minor_version, None)
         if not url:
             raise Exception('We do not have a MacPorts install URL for your '
-                'OS X version. You will need to install MacPorts manually.')
+                            'OS X version. You will need to install MacPorts manually.')
 
         print(PACKAGE_MANAGER_INSTALL % ('MacPorts', 'MacPorts', 'MacPorts', 'port'))
         pkg = urlopen(url=url, timeout=300).read()
         with tempfile.NamedTemporaryFile(suffix='.pkg') as tf:
             tf.write(pkg)
             tf.flush()
 
             self.run_as_root(['installer', '-pkg', tf.name, '-target', '/'])
@@ -479,26 +480,25 @@ class OSXBootstrapper(BaseBootstrapper):
             self.run_as_root([self.port, 'selfupdate'])
 
     def _upgrade_package(self, package):
         self._ensure_package_manager_updated()
 
         if self.package_manager == 'homebrew':
             try:
                 subprocess.check_output([self.brew, '-v', 'upgrade', package],
-                    stderr=subprocess.STDOUT)
+                                        stderr=subprocess.STDOUT)
             except subprocess.CalledProcessError as e:
                 if 'already installed' not in e.output:
                     raise
         else:
             assert self.package_manager == 'macports'
 
             self.run_as_root([self.port, 'upgrade', package])
 
     def upgrade_mercurial(self, current):
         self._upgrade_package('mercurial')
 
     def upgrade_python(self, current):
         if self.package_manager == 'homebrew':
             self._upgrade_package('python')
         else:
             self._upgrade_package('python27')
-
--- a/python/mozboot/mozboot/ubuntu.py
+++ b/python/mozboot/mozboot/ubuntu.py
@@ -1,22 +1,24 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 
+
 from mozboot.debian import DebianBootstrapper
 
 
 MERCURIAL_PPA = '''
 Ubuntu does not provide a modern Mercurial in its package repository. So,
 we will install a PPA that does.
 '''.strip()
 
+
 # Ubuntu shares much logic with Debian, so it inherits from it.
 class UbuntuBootstrapper(DebianBootstrapper):
     DISTRO_PACKAGES = [
         # This contains add-apt-repository.
         'software-properties-common',
     ]
 
     def upgrade_mercurial(self, current):
--- a/testing/mochitest/jetpack-package-harness.js
+++ b/testing/mochitest/jetpack-package-harness.js
@@ -225,16 +225,19 @@ function testInit() {
             dump("\tPassed: " + passed + "\n" +
                  "\tFailed: " + failed + "\n" +
                  "\tTodo: 0\n");
           }
 
           if (config.closeWhenDone) {
             require("sdk/system").exit(failed == 0 ? 0 : 1);
           }
+          else {
+            loaderModule.unload(loader, "shutdown");
+          }
         }
 
         function testNextModule() {
           if (fileNames.length == 0)
             return finish();
 
           let filename = fileNames.shift();
           testModule(require, filename).then(tests => {
--- a/testing/xpcshell/head.js
+++ b/testing/xpcshell/head.js
@@ -396,17 +396,17 @@ function _setupDebuggerServer(breakpoint
       case "devtools-thread-resumed":
         // Exceptions in here aren't reported and block the debugger from
         // resuming, so...
         try {
           // Add a breakpoint for the first line in our test files.
           let threadActor = subject.wrappedJSObject;
           for (let file of breakpointFiles) {
             let sourceActor = threadActor.sources.source({originalUrl: file});
-            sourceActor._setBreakpoint(new OriginalLocation(sourceActor, 1));
+            sourceActor._getOrCreateBreakpointActor(new OriginalLocation(sourceActor, 1));
           }
         } catch (ex) {
           do_print("Failed to initialize breakpoints: " + ex + "\n" + ex.stack);
         }
         break;
       case "xpcshell-test-devtools-shutdown":
         // the debugger has shutdown before we got a resume event - nothing
         // special to do here.
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -103,17 +103,17 @@ this.SafeBrowsing = {
 
 
   readPrefs: function() {
     log("reading prefs");
 
     debug = Services.prefs.getBoolPref("browser.safebrowsing.debug");
     this.phishingEnabled = Services.prefs.getBoolPref("browser.safebrowsing.enabled");
     this.malwareEnabled = Services.prefs.getBoolPref("browser.safebrowsing.malware.enabled");
-    this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled");
+    this.trackingEnabled = Services.prefs.getBoolPref("privacy.trackingprotection.enabled") || Services.prefs.getBoolPref("privacy.trackingprotection.pbmode.enabled");
     this.updateProviderURLs();
 
     // XXX The listManager backend gets confused if this is called before the
     // lists are registered. So only call it here when a pref changes, and not
     // when doing initialization. I expect to refactor this later, so pardon the hack.
     if (this.initialized) {
       this.controlUpdateChecking();
     }
--- a/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+++ b/toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
@@ -69,16 +69,19 @@ PRLogModuleInfo *gUrlClassifierDbService
 #define CHECK_MALWARE_DEFAULT   false
 
 #define CHECK_PHISHING_PREF     "browser.safebrowsing.enabled"
 #define CHECK_PHISHING_DEFAULT  false
 
 #define CHECK_TRACKING_PREF     "privacy.trackingprotection.enabled"
 #define CHECK_TRACKING_DEFAULT  false
 
+#define CHECK_TRACKING_PB_PREF    "privacy.trackingprotection.pbmode.enabled"
+#define CHECK_TRACKING_PB_DEFAULT false
+
 #define GETHASH_NOISE_PREF      "urlclassifier.gethashnoise"
 #define GETHASH_NOISE_DEFAULT   4
 
 // Comma-separated lists
 #define MALWARE_TABLE_PREF      "urlclassifier.malwareTable"
 #define PHISH_TABLE_PREF        "urlclassifier.phishTable"
 #define TRACKING_TABLE_PREF     "urlclassifier.trackingTable"
 #define DOWNLOAD_BLOCK_TABLE_PREF "urlclassifier.downloadBlockTable"
@@ -1111,28 +1114,30 @@ nsUrlClassifierDBService::Init()
     }
   }
 
   // Retrieve all the preferences.
   mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
     CHECK_MALWARE_DEFAULT);
   mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
     CHECK_PHISHING_DEFAULT);
-  mCheckTracking = Preferences::GetBool(CHECK_TRACKING_PREF,
-    CHECK_TRACKING_DEFAULT);
+  mCheckTracking =
+    Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
+    Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
   uint32_t gethashNoise = Preferences::GetUint(GETHASH_NOISE_PREF,
     GETHASH_NOISE_DEFAULT);
   gFreshnessGuarantee = Preferences::GetInt(CONFIRM_AGE_PREF,
     CONFIRM_AGE_DEFAULT_SEC);
   ReadTablesFromPrefs();
 
   // Do we *really* need to be able to change all of these at runtime?
   Preferences::AddStrongObserver(this, CHECK_MALWARE_PREF);
   Preferences::AddStrongObserver(this, CHECK_PHISHING_PREF);
   Preferences::AddStrongObserver(this, CHECK_TRACKING_PREF);
+  Preferences::AddStrongObserver(this, CHECK_TRACKING_PB_PREF);
   Preferences::AddStrongObserver(this, GETHASH_NOISE_PREF);
   Preferences::AddStrongObserver(this, CONFIRM_AGE_PREF);
   Preferences::AddStrongObserver(this, PHISH_TABLE_PREF);
   Preferences::AddStrongObserver(this, MALWARE_TABLE_PREF);
   Preferences::AddStrongObserver(this, TRACKING_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_BLOCK_TABLE_PREF);
   Preferences::AddStrongObserver(this, DOWNLOAD_ALLOW_TABLE_PREF);
   Preferences::AddStrongObserver(this, DISALLOW_COMPLETION_TABLE_PREF);
@@ -1522,19 +1527,21 @@ nsUrlClassifierDBService::Observe(nsISup
     nsCOMPtr<nsIPrefBranch> prefs(do_QueryInterface(aSubject, &rv));
     NS_ENSURE_SUCCESS(rv, rv);
     if (NS_LITERAL_STRING(CHECK_MALWARE_PREF).Equals(aData)) {
       mCheckMalware = Preferences::GetBool(CHECK_MALWARE_PREF,
         CHECK_MALWARE_DEFAULT);
     } else if (NS_LITERAL_STRING(CHECK_PHISHING_PREF).Equals(aData)) {
       mCheckPhishing = Preferences::GetBool(CHECK_PHISHING_PREF,
         CHECK_PHISHING_DEFAULT);
-    } else if (NS_LITERAL_STRING(CHECK_TRACKING_PREF).Equals(aData)) {
-      mCheckTracking = Preferences::GetBool(CHECK_TRACKING_PREF,
-        CHECK_TRACKING_DEFAULT);
+    } else if (NS_LITERAL_STRING(CHECK_TRACKING_PREF).Equals(aData) ||
+               NS_LITERAL_STRING(CHECK_TRACKING_PB_PREF).Equals(aData)) {
+      mCheckTracking =
+        Preferences::GetBool(CHECK_TRACKING_PREF, CHECK_TRACKING_DEFAULT) ||
+        Preferences::GetBool(CHECK_TRACKING_PB_PREF, CHECK_TRACKING_PB_DEFAULT);
     } else if (
       NS_LITERAL_STRING(PHISH_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(MALWARE_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(TRACKING_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DOWNLOAD_BLOCK_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DOWNLOAD_ALLOW_TABLE_PREF).Equals(aData) ||
       NS_LITERAL_STRING(DISALLOW_COMPLETION_TABLE_PREF).Equals(aData)) {
       // Just read everything again.
@@ -1564,16 +1571,17 @@ nsUrlClassifierDBService::Shutdown()
 
   mCompleters.Clear();
 
   nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
   if (prefs) {
     prefs->RemoveObserver(CHECK_MALWARE_PREF, this);
     prefs->RemoveObserver(CHECK_PHISHING_PREF, this);
     prefs->RemoveObserver(CHECK_TRACKING_PREF, this);
+    prefs->RemoveObserver(CHECK_TRACKING_PB_PREF, this);
     prefs->RemoveObserver(PHISH_TABLE_PREF, this);
     prefs->RemoveObserver(MALWARE_TABLE_PREF, this);
     prefs->RemoveObserver(TRACKING_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_BLOCK_TABLE_PREF, this);
     prefs->RemoveObserver(DOWNLOAD_ALLOW_TABLE_PREF, this);
     prefs->RemoveObserver(DISALLOW_COMPLETION_TABLE_PREF, this);
     prefs->RemoveObserver(CONFIRM_AGE_PREF, this);
   }
--- a/toolkit/components/url-classifier/tests/mochitest/chrome.ini
+++ b/toolkit/components/url-classifier/tests/mochitest/chrome.ini
@@ -1,9 +1,11 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 support-files =
   allowlistAnnotatedFrame.html
   classifiedAnnotatedFrame.html
+  classifiedAnnotatedPBFrame.html
 
 [test_lookup_system_principal.html]
 [test_classified_annotations.html]
 [test_allowlisted_annotations.html]
+[test_privatebrowsing_trackingprotection.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+<title></title>
+
+<link id="badcss" rel="stylesheet" type="text/css" href="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.css"></link>
+
+</head>
+<body>
+
+<script id="badscript" data-touched="not sure" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/evil.js" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"></script>
+
+<!-- The image cache can cache JS handlers, so make sure we use a different URL for raptor.jpg each time -->
+<img id="badimage" data-touched="not sure" src="http://tracking.example.com/tests/toolkit/components/url-classifier/tests/mochitest/raptor.jpg?pbmode=test" onload="this.dataset.touched = 'yes';" onerror="this.dataset.touched = 'no';"/>
+
+The following should not be hidden:
+<div id="styleCheck">STYLE TEST</div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html
@@ -0,0 +1,184 @@
+<!DOCTYPE HTML>
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<html>
+<head>
+  <title>Test Tracking Protection in Private Browsing mode</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+
+<script class="testbody" type="text/javascript">
+
+var Cc = SpecialPowers.Cc;
+var Ci = SpecialPowers.Ci;
+
+var mainWindow = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIWebNavigation)
+                    .QueryInterface(Ci.nsIDocShellTreeItem)
+                    .rootTreeItem
+                    .QueryInterface(Ci.nsIInterfaceRequestor)
+                    .getInterface(Ci.nsIDOMWindow);
+var contentPage = "chrome://mochitests/content/chrome/toolkit/components/url-classifier/tests/mochitest/classifiedAnnotatedPBFrame.html"
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function whenDelayedStartupFinished(aWindow, aCallback) {
+  Services.obs.addObserver(function observer(aSubject, aTopic) {
+    if (aWindow == aSubject) {
+      Services.obs.removeObserver(observer, aTopic);
+      setTimeout(aCallback, 0);
+    }
+  }, "browser-delayed-startup-finished", false);
+}
+
+function testOnWindow(aPrivate, aCallback) {
+  var win = mainWindow.OpenBrowserWindow({private: aPrivate});
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad, false);
+    whenDelayedStartupFinished(win, function() {
+      win.addEventListener("DOMContentLoaded", function onInnerLoad() {
+        if (win.content.location.href != contentPage) {
+          win.gBrowser.loadURI(contentPage);
+          return;
+        }
+        win.removeEventListener("DOMContentLoaded", onInnerLoad, true);
+
+        win.content.addEventListener('load', function innerLoad2() {
+          win.content.removeEventListener('load', innerLoad2, false);
+          SimpleTest.executeSoon(function() { aCallback(win); });
+        }, false, true);
+      }, true);
+      SimpleTest.executeSoon(function() { win.gBrowser.loadURI(contentPage); });
+    });
+  }, true);
+}
+
+// Add some URLs to the tracking database
+var testData = "tracking.example.com/";
+var testUpdate =
+  "n:1000\ni:test-track-simple\nad:1\n" +
+  "a:524:32:" + testData.length + "\n" +
+  testData;
+
+var badids = [
+  "badscript",
+  "badimage",
+  "badcss"
+];
+
+function checkLoads(aWindow, aBlocked) {
+  var win = aWindow.content;
+  is(win.document.getElementById("badscript").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking javascript");
+  is(win.document.getElementById("badimage").dataset.touched, aBlocked ? "no" : "yes", "Should not load tracking images");
+
+  var elt = win.document.getElementById("styleCheck");
+  var style = win.document.defaultView.getComputedStyle(elt, "");
+  isnot(style.visibility, aBlocked ? "hidden" : "", "Should not load tracking css");
+
+  is(win.document.blockedTrackingNodeCount, aBlocked ? badids.length : 0, "Should identify all tracking elements");
+
+  var blockedTrackingNodes = win.document.blockedTrackingNodes;
+
+  // Make sure that every node in blockedTrackingNodes exists in the tree
+  // (that may not always be the case but do not expect any nodes to disappear
+  // from the tree here)
+  var allNodeMatch = true;
+  for (var i = 0; i < blockedTrackingNodes.length; i++) {
+    var nodeMatch = false;
+    for (var j = 0; j < badids.length && !nodeMatch; j++) {
+      nodeMatch |=
+        (blockedTrackingNodes[i] == win.document.getElementById(badids[j]));
+    }
+
+    allNodeMatch &= nodeMatch;
+  }
+  is(allNodeMatch, true, "All annotated nodes are expected in the tree");
+
+  // Make sure that every node with a badid (see badids) is found in the
+  // blockedTrackingNodes. This tells us if we are neglecting to annotate
+  // some nodes
+  allNodeMatch = true;
+  for (var j = 0; j < badids.length; j++) {
+    var nodeMatch = false;
+    for (var i = 0; i < blockedTrackingNodes.length && !nodeMatch; i++) {
+      nodeMatch |=
+        (blockedTrackingNodes[i] == win.document.getElementById(badids[j]));
+    }
+
+    allNodeMatch &= nodeMatch;
+  }
+  is(allNodeMatch, aBlocked, "All tracking nodes are expected to be annotated as such");
+}
+
+var dbService = Cc["@mozilla.org/url-classifier/dbservice;1"]
+                .getService(Ci.nsIUrlClassifierDBService);
+
+function doUpdate(update) {
+  var listener = {
+    QueryInterface: function(iid)
+    {
+      if (iid.equals(Ci.nsISupports) ||
+          iid.equals(Ci.nsIUrlClassifierUpdateObserver))
+        return this;
+
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    },
+    updateUrlRequested: function(url) { },
+    streamFinished: function(status) { },
+    updateError: function(errorCode) {
+      ok(false, "Couldn't update classifier.");
+      // Abort test.
+      SimpleTest.finish();
+    },
+    updateSuccess: function(requestedTimeout) {
+      // Normal mode, with the pref (trackers should be loaded)
+      testOnWindow(false, function(aWindow) {
+        checkLoads(aWindow, false);
+        aWindow.close();
+
+        // Private Browsing, with the pref (trackers should be blocked)
+        testOnWindow(true, function(aWindow) {
+          checkLoads(aWindow, true);
+          aWindow.close();
+
+          // Private Browsing, without the pref (trackers should be loaded)
+          SpecialPowers.setBoolPref("privacy.trackingprotection.pbmode.enabled", false);
+          testOnWindow(true, function(aWindow) {
+             checkLoads(aWindow, false);
+             aWindow.close();
+             SimpleTest.finish();
+          });
+        });
+      });
+    }
+  };
+
+  dbService.beginUpdate(listener, "test-track-simple", "");
+  dbService.beginStream("", "");
+  dbService.updateStream(update);
+  dbService.finishStream();
+  dbService.finishUpdate();
+}
+
+SpecialPowers.pushPrefEnv(
+  {"set" : [["urlclassifier.trackingTable", "test-track-simple"],
+            ["privacy.trackingprotection.enabled", false],
+            ["privacy.trackingprotection.pbmode.enabled", true],
+            ["channelclassifier.allowlist_example", true]]},
+  function() { doUpdate(testUpdate); });
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+
+</pre>
+<iframe id="testFrame" width="100%" height="100%" onload=""></iframe>
+</body>
+</html>
--- a/toolkit/content/widgets/tabbox.xml
+++ b/toolkit/content/widgets/tabbox.xml
@@ -662,18 +662,16 @@
           if (val < 0 || val >= this.childNodes.length)
             return val;
           var panel = this._selectedPanel;
           this._selectedPanel = this.childNodes[val];
           this.setAttribute("selectedIndex", val);
           if (this._selectedPanel != panel) {
             var event = document.createEvent("Events");
             event.initEvent("select", true, true);
-            event.fromTab = this.getRelatedElement(panel);
-            event.toTab = this.getRelatedElement(this._selectedPanel);
             this.dispatchEvent(event);
           }
           return val;
         ]]>
         </setter>
       </property>
 
       <property name="selectedPanel">
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -463,16 +463,18 @@ DebuggerClient.prototype = {
   listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
 
   /*
    * This function exists only to preserve DebuggerClient's interface;
    * new code should say 'client.mainRoot.listAddons()'.
    */
   listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
 
+  getTab: function (aFilter) { return this.mainRoot.getTab(aFilter); },
+
   /**
    * Attach to a tab actor.
    *
    * @param string aTabActor
    *        The actor ID for the tab to attach.
    * @param function aOnResponse
    *        Called with the response packet and a TabClient
    *        (which will be undefined on error).
@@ -617,26 +619,27 @@ DebuggerClient.prototype = {
         var traceClient = new TraceClient(this, aTraceActor);
         this.registerClient(traceClient);
       }
       aOnResponse(aResponse, traceClient);
     });
   },
 
   /**
-   * Attach to a process in order to get the form of a ChildProcessActor.
+   * Fetch the ChromeActor for the main process or ChildProcessActor for a
+   * a given child process ID.
    *
    * @param number aId
    *        The ID for the process to attach (returned by `listProcesses`).
    *        Connected to the main process if omitted, or is 0.
    */
-  attachProcess: function (aId) {
+  getProcess: function (aId) {
     let packet = {
       to: "root",
-      type: "attachProcess"
+      type: "getProcess"
     }
     if (typeof(aId) == "number") {
       packet.id = aId;
     }
     return this.request(packet);
   },
 
   /**
@@ -1402,16 +1405,61 @@ RootClient.prototype = {
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   listProcesses: DebuggerClient.requester({ type: "listProcesses" },
                                        { telemetry: "LISTPROCESSES" }),
 
   /**
+   * Fetch the TabActor for the currently selected tab, or for a specific
+   * tab given as first parameter.
+   *
+   * @param [optional] object aFilter
+   *        A dictionary object with following optional attributes:
+   *         - outerWindowID: used to match tabs in parent process
+   *         - tabId: used to match tabs in child processes
+   *         - tab: a reference to xul:tab element
+   *        If nothing is specified, returns the actor for the currently
+   *        selected tab.
+   */
+  getTab: function (aFilter) {
+    let packet = {
+      to: this.actor,
+      type: "getTab"
+    };
+
+    if (aFilter) {
+      if (typeof(aFilter.outerWindowID) == "number") {
+        packet.outerWindowID = aFilter.outerWindowID;
+      } else if (typeof(aFilter.tabId) == "number") {
+        packet.tabId = aFilter.tabId;
+      } else if ("tab" in aFilter) {
+        let browser = aFilter.tab.linkedBrowser;
+        if (browser.frameLoader.tabParent) {
+          // Tabs in child process
+          packet.tabId = browser.frameLoader.tabParent.tabId;
+        } else {
+          // Tabs in parent process
+          let windowUtils = browser.contentWindow
+            .QueryInterface(Ci.nsIInterfaceRequestor)
+            .getInterface(Ci.nsIDOMWindowUtils);
+          packet.outerWindowID = windowUtils.outerWindowID;
+        }
+      } else {
+        // Throw if a filter object have been passed but without
+        // any clearly idenfified filter.
+        throw new Error("Unsupported argument given to getTab request");
+      }
+    }
+
+    return this.request(packet);
+  },
+
+  /**
    * Description of protocol's actors and methods.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
    protocolDescription: DebuggerClient.requester({ type: "protocolDescription" },
                                                  { telemetry: "PROTOCOLDESCRIPTION" }),
 
--- a/toolkit/devtools/server/actors/chrome.js
+++ b/toolkit/devtools/server/actors/chrome.js
@@ -9,17 +9,17 @@ const Services = require("Services");
 const { DebuggerServer } = require("../main");
 const { getChildDocShells, TabActor } = require("./webbrowser");
 const makeDebugger = require("./utils/make-debugger");
 
 /**
  * Creates a TabActor for debugging all the chrome content in the
  * current process. Most of the implementation is inherited from TabActor.
  * ChromeActor is a child of RootActor, it can be instanciated via
- * RootActor.attachProcess request.
+ * RootActor.getProcess request.
  * ChromeActor exposes all tab actors via its form() request, like TabActor.
  *
  * History lecture:
  * All tab actors used to also be registered as global actors,
  * so that the root actor was also exposing tab actors for the main process.
  * Tab actors ended up having RootActor as parent actor,
  * but more and more features of the tab actors were relying on TabActor.
  * So we are now exposing a process actor that offers the same API as TabActor
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -151,19 +151,19 @@ RootActor.prototype = {
     // Whether the page style actor implements the getUsedFontFaces method
     // that returns the font faces used on a node
     getUsedFontFaces: true,
     // Trait added in Gecko 38, indicating that all features necessary for
     // grabbing allocations from the MemoryActor are available for the performance tool
     memoryActorAllocations: true,
     // Whether root actor exposes tab actors
     // if allowChromeProcess is true, you can fetch a ChromeActor instance
-    // to debug chrome and any non-content ressource via attachProcess request
+    // to debug chrome and any non-content ressource via getProcess request
     // if allocChromeProcess is defined, but not true, it means that root actor
-    // no longer expose tab actors, but also that attachProcess forbids
+    // no longer expose tab actors, but also that getProcess forbids
     // exposing actors for security reasons
     get allowChromeProcess() {
       return DebuggerServer.allowChromeProcess;
     },
   },
 
   /**
    * Return a 'hello' packet as specified by the Remote Debugging Protocol.
@@ -263,16 +263,43 @@ RootActor.prototype = {
        * changes.
        */
       tabList.onListChanged = this._onTabListChanged;
 
       return reply;
     });
   },
 
+  onGetTab: function (options) {
+    let tabList = this._parameters.tabList;
+    if (!tabList) {
+      return { error: "noTabs",
+               message: "This root actor has no browser tabs." };
+    }
+    if (!this._tabActorPool) {
+      this._tabActorPool = new ActorPool(this.conn);
+      this.conn.addActorPool(this._tabActorPool);
+    }
+    return tabList.getTab(options)
+                  .then(tabActor => {
+      tabActor.parentID = this.actorID;
+      this._tabActorPool.addActor(tabActor);
+
+      return { tab: tabActor.form() };
+    }, error => {
+      if (error.error) {
+        // Pipe expected errors as-is to the client
+        return error;
+      } else {
+        return { error: "noTab",
+                 message: "Unexpected error while calling getTab(): " + error };
+      }
+    });
+  },
+
   onTabListChanged: function () {
     this.conn.send({ from: this.actorID, type:"tabListChanged" });
     /* It's a one-shot notification; no need to watch any more. */
     this._parameters.tabList.onListChanged = null;
   },
 
   onListAddons: function () {
     let addonList = this._parameters.addonList;
@@ -314,24 +341,24 @@ RootActor.prototype = {
         id: i, // XXX: may not be a perfect id, but process message manager doesn't expose anything...
         parent: i == 0, // XXX Weak, but appear to be stable
         tabCount: undefined, // TODO: exposes process message manager on frameloaders in order to compute this
       });
     }
     return { processes: processes };
   },
 
-  onAttachProcess: function (aRequest) {
+  onGetProcess: function (aRequest) {
     if (!DebuggerServer.allowChromeProcess) {
       return { error: "forbidden",
                message: "You are not allowed to debug chrome." };
     }
     if (("id" in aRequest) && typeof(aRequest.id) != "number") {
       return { error: "wrongParameter",
-               message: "attachProcess requires a valid `id` attribute." };
+               message: "getProcess requires a valid `id` attribute." };
     }
     // If the request doesn't contains id parameter or id is 0
     // (id == 0, based on onListProcesses implementation)
     if ((!("id" in aRequest)) || aRequest.id === 0) {
       if (!this._chromeActor) {
         // Create a ChromeActor for the parent process
         let { ChromeActor } = require("devtools/server/actors/chrome");
         this._chromeActor = new ChromeActor(this.conn);
@@ -386,16 +413,17 @@ RootActor.prototype = {
       }
       delete this._extraActors[aName];
     }
    }
 };
 
 RootActor.prototype.requestTypes = {
   "listTabs": RootActor.prototype.onListTabs,
+  "getTab": RootActor.prototype.onGetTab,
   "listAddons": RootActor.prototype.onListAddons,
   "listProcesses": RootActor.prototype.onListProcesses,
-  "attachProcess": RootActor.prototype.onAttachProcess,
+  "getProcess": RootActor.prototype.onGetProcess,
   "echo": RootActor.prototype.onEcho,
   "protocolDescription": RootActor.prototype.onProtocolDescription
 };
 
 exports.RootActor = RootActor;
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -1216,17 +1216,17 @@ ThreadActor.prototype = {
     for (let line = 0, n = offsets.length; line < n; line++) {
       if (offsets[line]) {
         // N.B. Hidden breakpoints do not have an original location, and are not
         // stored in the breakpoint actor map.
         let actor = new BreakpointActor(this);
         this.threadLifetimePool.addActor(actor);
         let scripts = this.scripts.getScriptsBySourceAndLine(script.source, line);
         let entryPoints = findEntryPointsForLine(scripts, line);
-        setBreakpointForActorAtEntryPoints(actor, entryPoints);
+        setBreakpointAtEntryPoints(actor, entryPoints);
         this._hiddenBreakpoints.set(actor.actorID, actor);
         break;
       }
     }
   },
 
   /**
    * Helper method that returns the next frame when stepping.
@@ -2041,25 +2041,28 @@ ThreadActor.prototype = {
     let endLine = aScript.startLine + aScript.lineCount - 1;
     for (let _actor of this.breakpointActorMap.findActors()) {
       // XXX bug 1142115: We do async work in here, so we need to
       // create a fresh binding because for/of does not yet do that in
       // SpiderMonkey
       let actor = _actor;
 
       if (actor.isPending) {
-        promises.push(sourceActor._setBreakpointForActor(actor));
+        promises.push(sourceActor._setBreakpoint(actor));
       } else {
         promises.push(this.sources.getGeneratedLocation(actor.originalLocation)
                                   .then((generatedLocation) => {
           // Limit the search to the line numbers contained in the new script.
           if (generatedLocation.generatedSourceActor.actorID === sourceActor.actorID &&
               generatedLocation.generatedLine >= aScript.startLine &&
               generatedLocation.generatedLine <= endLine) {
-            sourceActor._setBreakpointForActorAtLocation(actor, generatedLocation);
+            sourceActor._setBreakpointAtGeneratedLocation(
+              actor,
+              generatedLocation
+            );
           }
         }));
       }
     }
 
     if (promises.length > 0) {
       this.synchronize(Promise.all(promises));
     }
@@ -2295,16 +2298,17 @@ SourceActor.prototype = {
   _addonPath: null,
 
   get isSourceMapped() {
     return this._originalURL || this._generatedSource ||
            this.threadActor.sources.isPrettyPrinted(this.url);
   },
 
   get threadActor() { return this._threadActor; },
+  get sources() { return this._threadActor.sources; },
   get dbg() { return this.threadActor.dbg; },
   get scripts() { return this.threadActor.scripts; },
   get source() { return this._source; },
   get generatedSource() { return this._generatedSource; },
   get breakpointActorMap() { return this.threadActor.breakpointActorMap; },
   get url() {
     if (this.source) {
       return getSourceURL(this.source);
@@ -2738,17 +2742,20 @@ SourceActor.prototype = {
       return {
         error: "wrongState",
         message: "Cannot set breakpoint while debuggee is running."
       };
     }
 
     let { location: { line, column }, condition } = request;
     let location = new OriginalLocation(this, line, column);
-    return this._setBreakpoint(location, condition).then((actor) => {
+    return this._getOrCreateBreakpointActor(
+      location,
+      condition
+    ).then((actor) => {
       if (actor.isPending) {
         return {
           error: "noCodeAtLocation",
           actor: actor.actorID
         };
       } else {
         let response = {
           actor: actor.actorID
@@ -2774,27 +2781,27 @@ SourceActor.prototype = {
    *        the original source.
    * @param String condition
    *        A string that is evaluated whenever the breakpoint is hit. If the
    *        string evaluates to false, the breakpoint is ignored.
    *
    * @returns BreakpointActor
    *          A BreakpointActor representing the breakpoint.
    */
-  _setBreakpoint: function (originalLocation, condition) {
+  _getOrCreateBreakpointActor: function (originalLocation, condition) {
     let actor = this.breakpointActorMap.getActor(originalLocation);
     if (!actor) {
       actor = new BreakpointActor(this.threadActor, originalLocation);
       this.threadActor.threadLifetimePool.addActor(actor);
       this.breakpointActorMap.setActor(originalLocation, actor);
     }
 
     actor.condition = condition;
 
-    return this._setBreakpointForActor(actor);
+    return this._setBreakpoint(actor);
   },
 
   /*
    * Ensure the given BreakpointActor is set as a breakpoint handler on all
    * scripts that match its location in the original source.
    *
    * If there are no scripts that match the location of the BreakpointActor,
    * we slide its location to the next closest line (for line breakpoints) or
@@ -2807,154 +2814,190 @@ SourceActor.prototype = {
    * a pending breakpoint. Whenever a new script is introduced, this method is
    * called again for each pending breakpoint.
    *
    * @param BreakpointActor actor
    *        The BreakpointActor to be set as a breakpoint handler.
    *
    * @returns A Promise that resolves to the given BreakpointActor.
    */
-  _setBreakpointForActor: function (actor) {
+  _setBreakpoint: function (actor) {
     let { originalLocation } = actor;
-
-    if (this.isSourceMapped) {
-      // TODO: Refactor breakpoint sliding for source mapped sources.
-      return this.threadActor.sources.getGeneratedLocation(originalLocation)
-                                     .then((generatedLocation) => {
-        return generatedLocation.generatedSourceActor
-                                ._setBreakpointForActorAtLocationWithSliding(
-          actor,
-          generatedLocation
-        );
-      });
-    } else {
-      // If this is a non-source mapped source, the original location and
-      // generated location are the same, so we can safely convert between them.
-      let generatedLocation = GeneratedLocation.fromOriginalLocation(originalLocation);
-      let { generatedColumn } = generatedLocation;
-
-      // Try to set the breakpoint on the generated location directly. If this
-      // succeeds, we can avoid the more expensive breakpoint sliding algorithm
-      // below.
-      if (this._setBreakpointForActorAtLocation(actor, generatedLocation)) {
-        return Promise.resolve(actor);
+    let { originalColumn } = originalLocation;
+
+    return this._setBreakpointAtOriginalLocation(actor, originalLocation)
+               .then((actualLocation) => {
+      if (actualLocation) {
+        return actualLocation;
       }
 
-      // There were no scripts that matched the given location, so we need to
-      // perform breakpoint sliding.
-      if (generatedColumn === undefined) {
-        // To perform breakpoint sliding for line breakpoints, we need to build
-        // a map from line numbers to a list of entry points for each line,
-        // implemented as a sparse array. An entry point is a (script, offsets)
-        // pair, and represents all offsets in that script that are entry points
-        // for the corresponding line.
-        let lineToEntryPointsMap = [];
-
-        // Iterate over all scripts that correspond to this source actor.
-        let scripts = this.scripts.getScriptsBySourceActor(this);
-        for (let script of scripts) {
-          // Get all offsets for each line in the current script. This returns
-          // a map from line numbers fo a list of offsets for each line,
-          // implemented as a sparse array.
-          let lineToOffsetsMap = script.getAllOffsets();
-
-          // Iterate over each line, and add their list of offsets to the map
-          // from line numbers to entry points by forming a (script, offsets)
-          // pair, where script is the current script, and offsets is the list
-          // of offsets for the current line.
-          for (let line = 0; line < lineToOffsetsMap.length; ++line) {
-            let offsets = lineToOffsetsMap[line];
-            if (offsets) {
-              let entryPoints = lineToEntryPointsMap[line];
-              if (!entryPoints) {
-                // We dont have a list of entry points for the current line
-                // number yet, so create it and add it to the map.
-                entryPoints = [];
-                lineToEntryPointsMap[line] = entryPoints;
+      if (!this.isSourceMapped) {
+        // There were no scripts that matched the given location, so we need to
+        // perform breakpoint sliding.
+        if (originalColumn === undefined) {
+          // To perform breakpoint sliding for line breakpoints, we need to build
+          // a map from line numbers to a list of entry points for each line,
+          // implemented as a sparse array. An entry point is a (script, offsets)
+          // pair, and represents all offsets in that script that are entry points
+          // for the corresponding line.
+          let lineToEntryPointsMap = [];
+
+          // Iterate over all scripts that correspond to this source actor.
+          let scripts = this.scripts.getScriptsBySourceActor(this);
+          for (let script of scripts) {
+            // Get all offsets for each line in the current script. This returns
+            // a map from line numbers fo a list of offsets for each line,
+            // implemented as a sparse array.
+            let lineToOffsetsMap = script.getAllOffsets();
+
+            // Iterate over each line, and add their list of offsets to the map
+            // from line numbers to entry points by forming a (script, offsets)
+            // pair, where script is the current script, and offsets is the list
+            // of offsets for the current line.
+            for (let line = 0; line < lineToOffsetsMap.length; ++line) {
+              let offsets = lineToOffsetsMap[line];
+              if (offsets) {
+                let entryPoints = lineToEntryPointsMap[line];
+                if (!entryPoints) {
+                  // We dont have a list of entry points for the current line
+                  // number yet, so create it and add it to the map.
+                  entryPoints = [];
+                  lineToEntryPointsMap[line] = entryPoints;
+                }
+                entryPoints.push({ script, offsets });
               }
-              entryPoints.push({ script, offsets });
             }
           }
-        }
-
-        let {
-          originalSourceActor,
-          originalLine,
-          originalColumn
-        } = originalLocation;
-
-        // Now that we have a map from line numbers to a list of entry points
-        // for each line, we can use it to perform breakpoint sliding. Start
-        // at the original line of the breakpoint actor, and keep incrementing
-        // it by one, until either we find a line that has at least one entry
-        // point, or we go past the last line in the map.
-        //
-        // Note that by computing the entire map up front, and implementing it
-        // as a sparse array, we can easily tell when we went past the last line
-        // in the map.
-        let actualLine = originalLine;
-        while (actualLine < lineToEntryPointsMap.length) {
-          let entryPoints = lineToEntryPointsMap[actualLine];
-          if (entryPoints) {
-            setBreakpointForActorAtEntryPoints(actor, entryPoints);
-            break;
+
+          let {
+            originalSourceActor,
+            originalLine,
+            originalColumn
+          } = originalLocation;
+
+          // Now that we have a map from line numbers to a list of entry points
+          // for each line, we can use it to perform breakpoint sliding. Start
+          // at the original line of the breakpoint actor, and keep incrementing
+          // it by one, until either we find a line that has at least one entry
+          // point, or we go past the last line in the map.
+          //
+          // Note that by computing the entire map up front, and implementing it
+          // as a sparse array, we can easily tell when we went past the last line
+          // in the map.
+          let actualLine = originalLine;
+          while (actualLine < lineToEntryPointsMap.length) {
+            let entryPoints = lineToEntryPointsMap[actualLine];
+            if (entryPoints) {
+              setBreakpointAtEntryPoints(actor, entryPoints);
+              break;
+            }
+            ++actualLine;
           }
-          ++actualLine;
-        }
-        if (actualLine === lineToEntryPointsMap.length) {
-          // We went past the last line in the map, so breakpoint sliding
-          // failed. Keep the BreakpointActor in the BreakpointActorMap as a
-          // pending breakpoint, so we can try again whenever a new script is
-          // introduced.
-          return Promise.resolve(actor);
-        }
-
-        // If the actual line on which the BreakpointActor was set differs from
-        // the original line that was requested, the BreakpointActor and the
-        // BreakpointActorMap need to be updated accordingly.
-        if (actualLine !== originalLine) {
-          let actualLocation = new OriginalLocation(
+          if (actualLine === lineToEntryPointsMap.length) {
+            // We went past the last line in the map, so breakpoint sliding
+            // failed. Keep the BreakpointActor in the BreakpointActorMap as a
+            // pending breakpoint, so we can try again whenever a new script is
+            // introduced.
+            return originalLocation;
+          }
+
+          return new OriginalLocation(
             originalSourceActor,
             actualLine
           );
-          let existingActor = this.breakpointActorMap.getActor(actualLocation);
-          if (existingActor) {
-            actor.onDelete();
-            this.breakpointActorMap.deleteActor(originalLocation);
-            actor = existingActor;
-          } else {
-            this.breakpointActorMap.deleteActor(originalLocation);
-            actor.originalLocation = actualLocation;
-            this.breakpointActorMap.setActor(actualLocation, actor);
-          }
+        } else {
+          // TODO: Implement breakpoint sliding for column breakpoints
+          return originalLocation;
+        }
+      } else {
+        // TODO: Refactor breakpoint sliding for source mapped sources.
+        return this.threadActor.sources.getGeneratedLocation(originalLocation)
+                                       .then((generatedLocation) => {
+          return generatedLocation.generatedSourceActor
+                                  ._setBreakpointAtLocationWithSliding(
+            actor,
+            generatedLocation
+          );
+        });
+      }
+    }).then((actualLocation) => {
+      // If the actual location on which the BreakpointActor ended up being
+      // set differs from the original line that was requested, both the
+      // BreakpointActor and the BreakpointActorMap need to be updated
+      // accordingly.
+      if (!actualLocation.equals(originalLocation)) {
+        let existingActor = this.breakpointActorMap.getActor(actualLocation);
+        if (existingActor) {
+          actor.onDelete();
+          this.breakpointActorMap.deleteActor(originalLocation);
+          actor = existingActor;
+        } else {
+          this.breakpointActorMap.deleteActor(originalLocation);
+          actor.originalLocation = actualLocation;
+          this.breakpointActorMap.setActor(actualLocation, actor);
         }
-
-        return Promise.resolve(actor);
-      } else {
-        // TODO: Implement breakpoint sliding for column breakpoints
-        return Promise.resolve(actor);
+      }
+
+      return actor;
+    });
+  },
+
+  _setBreakpointAtOriginalLocation: function (actor, originalLocation) {
+    if (!this.isSourceMapped) {
+      if (!this._setBreakpointAtGeneratedLocation(
+        actor,
+        GeneratedLocation.fromOriginalLocation(originalLocation)
+      )) {
+        return Promise.resolve(null);
       }
-    }
+
+      return Promise.resolve(originalLocation);
+    } else {
+      return this.sources.getAllGeneratedLocations(originalLocation)
+                         .then((generatedLocations) => {
+        if (!this._setBreakpointAtAllGeneratedLocations(
+          actor,
+          generatedLocations
+        )) {
+          return null;
+        }
+
+        return this.threadActor.sources.getOriginalLocation(generatedLocations[0]);
+      });
+    }
+  },
+
+  _setBreakpointAtAllGeneratedLocations: function (actor, generatedLocations) {
+    let success = false;
+    for (let generatedLocation of generatedLocations) {
+      if (this._setBreakpointAtGeneratedLocation(
+        actor,
+        generatedLocation
+      )) {
+        success = true;
+      }
+    }
+    return success;
   },
 
   /*
    * Ensure the given BreakpointActor is set as breakpoint handler on all
    * scripts that match the given location in the generated source.
    *
    * @param BreakpointActor actor
    *        The BreakpointActor to be set as a breakpoint handler.
    * @param GeneratedLocation generatedLocation
    *        A GeneratedLocation representing the location in the generated
    *        source for which the given BreakpointActor is to be set as a
    *        breakpoint handler.
    *
    * @returns A Boolean that is true if the BreakpointActor was set as a
    *          breakpoint handler on at least one script, and false otherwise.
    */
-  _setBreakpointForActorAtLocation: function (actor, generatedLocation) {
+  _setBreakpointAtGeneratedLocation: function (actor, generatedLocation) {
     let { generatedLine, generatedColumn } = generatedLocation;
 
     // Find all scripts that match the given source actor and line number.
     let scripts = this.scripts.getScriptsBySourceActorAndLine(
       this,
       generatedLine
     ).filter((script) => !actor.hasScript(script));
 
@@ -2985,44 +3028,44 @@ SourceActor.prototype = {
           }
         }
       }
     }
 
     if (entryPoints.length === 0) {
       return false;
     }
-    setBreakpointForActorAtEntryPoints(actor, entryPoints);
+    setBreakpointAtEntryPoints(actor, entryPoints);
     return true;
   },
 
   /*
    * Ensure the given BreakpointActor is set as breakpoint handler on all
    * scripts that match the given location in the generated source.
    *
    * TODO: This method is bugged, because it performs breakpoint sliding on
    * generated locations. Breakpoint sliding should be performed on original
    * locations, because there is no guarantee that the next line in the
    * generated source corresponds to the next line in an original source.
    *
-   * The only place this method is still used is from setBreakpointForActor
+   * The only place this method is still used is from setBreakpoint
    * when called for a source mapped source. Once that code has been refactored,
    * this method can be removed.
    *
    * @param BreakpointActor actor
    *        The BreakpointActor to be set as a breakpoint handler.
    * @param GeneratedLocation generatedLocation
    *        A GeneratedLocation representing the location in the generated
    *        source for which the given BreakpointActor is to be set as a
    *        breakpoint handler.
    *
    * @returns A Boolean that is true if the BreakpointActor was set as a
    *          breakpoint handler on at least one script, and false otherwise.
    */
-  _setBreakpointForActorAtLocationWithSliding: function (actor, generatedLocation) {
+  _setBreakpointAtLocationWithSliding: function (actor, generatedLocation) {
     let originalLocation = actor.originalLocation;
     let { generatedLine, generatedColumn } = generatedLocation;
 
     // Find all scripts matching the given location. We will almost always have
     // a `source` object to query, but multiple inline HTML scripts are all
     // represented by a single SourceActor even though they have separate source
     // objects, so we need to query based on the url of the page for them.
     let scripts = this.scripts.getScriptsBySourceActorAndLine(this, generatedLine);
@@ -3077,45 +3120,25 @@ SourceActor.prototype = {
           generatedLocation.generatedSourceActor,
           result.line,
           generatedLocation.generatedColumn
         );
       } else {
         actualGeneratedLocation = generatedLocation;
       }
 
-      setBreakpointForActorAtEntryPoints(actor, result.entryPoints);
+      setBreakpointAtEntryPoints(actor, result.entryPoints);
     }
 
     return Promise.resolve().then(() => {
       if (actualGeneratedLocation.generatedSourceActor.source) {
         return this.threadActor.sources.getOriginalLocation(actualGeneratedLocation);
       } else {
         return OriginalLocation.fromGeneratedLocation(actualGeneratedLocation);
       }
-    }).then((actualOriginalLocation) => {
-      if (!actualOriginalLocation.equals(originalLocation)) {
-        // Check whether we already have a breakpoint actor for the actual
-        // location. If we do have an existing actor, then the actor we created
-        // above is redundant and must be destroyed. If we do not have an existing
-        // actor, we need to update the breakpoint store with the new location.
-
-        let existingActor = this.breakpointActorMap.getActor(actualOriginalLocation);
-        if (existingActor) {
-          actor.onDelete();
-          this.breakpointActorMap.deleteActor(originalLocation);
-          actor = existingActor;
-        } else {
-          actor.originalLocation = actualOriginalLocation;
-          this.breakpointActorMap.deleteActor(originalLocation);
-          this.breakpointActorMap.setActor(actualOriginalLocation, actor);
-        }
-      }
-
-      return actor;
     });
   },
 
   /**
    * Set breakpoints at the offsets closest to our target location's column.
    *
    * @param Array scripts
    *        The set of Debugger.Script instances to consider.
@@ -5427,16 +5450,16 @@ function findEntryPointsForLine(scripts,
  * Set breakpoints on all the given entry points with the given
  * BreakpointActor as the handler.
  *
  * @param BreakpointActor actor
  *        The actor handling the breakpoint hits.
  * @param Array entryPoints
  *        An array of objects of the form `{ script, offsets }`.
  */
-function setBreakpointForActorAtEntryPoints(actor, entryPoints) {
+function setBreakpointAtEntryPoints(actor, entryPoints) {
   for (let { script, offsets } of entryPoints) {
     actor.addScript(script);
     for (let offset of offsets) {
       script.setBreakpoint(offset, actor);
     }
   }
 }
--- a/toolkit/devtools/server/actors/utils/TabSources.js
+++ b/toolkit/devtools/server/actors/utils/TabSources.js
@@ -585,16 +585,27 @@ TabSources.prototype = {
         );
       }
 
       // No source map
       return OriginalLocation.fromGeneratedLocation(generatedLocation);
     });
   },
 
+  getAllGeneratedLocations: function (originalLocation) {
+    return this.getGeneratedLocation(originalLocation)
+               .then((generatedLocation) => {
+      if (generatedLocation.generatedLine === null &&
+          generatedLocation.generatedColumn === null) {
+        return [];
+      }
+      return [generatedLocation];
+    });
+  },
+
   /**
    * Returns a promise of the location in the generated source corresponding to
    * the original source and line given.
    *
    * When we pass a script S representing generated code to `sourceMap`,
    * above, that returns a promise P. The process of resolving P populates
    * the tables this function uses; thus, it won't know that S's original
    * source URLs map to S until P is resolved.
--- a/toolkit/devtools/server/actors/webbrowser.js
+++ b/toolkit/devtools/server/actors/webbrowser.js
@@ -325,55 +325,108 @@ BrowserTabList.prototype.getList = funct
   let selectedBrowser = null;
   if (topXULWindow) {
     selectedBrowser = this._getSelectedBrowser(topXULWindow);
   }
 
   // As a sanity check, make sure all the actors presently in our map get
   // picked up when we iterate over all windows' tabs.
   let initialMapSize = this._actorByBrowser.size;
-  let foundCount = 0;
+  this._foundCount = 0;
 
   // To avoid mysterious behavior if tabs are closed or opened mid-iteration,
   // we update the map first, and then make a second pass over it to yield
   // the actors. Thus, the sequence yielded is always a snapshot of the
   // actors that were live when we began the iteration.
 
   let actorPromises = [];
 
   for (let browser of this._getBrowsers()) {
-    // Do we have an existing actor for this browser? If not, create one.
-    let actor = this._actorByBrowser.get(browser);
-    if (actor) {
-      actorPromises.push(actor.update());
-      foundCount++;
-    } else if (this._isRemoteBrowser(browser)) {
-      actor = new RemoteBrowserTabActor(this._connection, browser);
-      this._actorByBrowser.set(browser, actor);
-      actorPromises.push(actor.connect());
-    } else {
-      actor = new BrowserTabActor(this._connection, browser,
-                                  browser.getTabBrowser());
-      this._actorByBrowser.set(browser, actor);
-      actorPromises.push(promise.resolve(actor));
-    }
-
-    // Set the 'selected' properties on all actors correctly.
-    actor.selected = browser === selectedBrowser;
+    let selected = browser === selectedBrowser;
+    actorPromises.push(
+      this._getActorForBrowser(browser)
+          .then(actor => {
+            // Set the 'selected' properties on all actors correctly.
+            actor.selected = selected;
+            return actor;
+          })
+    );
   }
 
-  if (this._testing && initialMapSize !== foundCount)
+  if (this._testing && initialMapSize !== this._foundCount)
     throw Error("_actorByBrowser map contained actors for dead tabs");
 
   this._mustNotify = true;
   this._checkListening();
 
   return promise.all(actorPromises);
 };
 
+BrowserTabList.prototype._getActorForBrowser = function(browser) {
+  // Do we have an existing actor for this browser? If not, create one.
+  let actor = this._actorByBrowser.get(browser);
+  if (actor) {
+    this._foundCount++;
+    return actor.update();
+  } else if (this._isRemoteBrowser(browser)) {
+    actor = new RemoteBrowserTabActor(this._connection, browser);
+    this._actorByBrowser.set(browser, actor);
+    this._checkListening();
+    return actor.connect();
+  } else {
+    actor = new BrowserTabActor(this._connection, browser,
+                                browser.getTabBrowser());
+    this._actorByBrowser.set(browser, actor);
+    this._checkListening();
+    return promise.resolve(actor);
+  }
+};
+
+BrowserTabList.prototype.getTab = function({ outerWindowID, tabId }) {
+  if (typeof(outerWindowID) == "number") {
+    // Tabs in parent process
+    for (let browser of this._getBrowsers()) {
+      if (browser.contentWindow) {
+        let windowUtils = browser.contentWindow
+          .QueryInterface(Ci.nsIInterfaceRequestor)
+          .getInterface(Ci.nsIDOMWindowUtils);
+        if (windowUtils.outerWindowID === outerWindowID) {
+          return this._getActorForBrowser(browser);
+        }
+      }
+    }
+    return promise.reject({
+      error: "noTab",
+      message: "Unable to find tab with outerWindowID '" + outerWindowID + "'"
+    });
+  } else if (typeof(tabId) == "number") {
+    // Tabs OOP
+    for (let browser of this._getBrowsers()) {
+      if (browser.frameLoader.tabParent &&
+          browser.frameLoader.tabParent.tabId === tabId) {
+        return this._getActorForBrowser(browser);
+      }
+    }
+    return promise.reject({
+      error: "noTab",
+      message: "Unable to find tab with tabId '" + tabId + "'"
+    });
+  }
+
+  let topXULWindow = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+  if (topXULWindow) {
+    let selectedBrowser = this._getSelectedBrowser(topXULWindow);
+    return this._getActorForBrowser(selectedBrowser);
+  }
+  return promise.reject({
+    error: "noTab",
+    message: "Unable to find any selected browser"
+  });
+};
+
 Object.defineProperty(BrowserTabList.prototype, 'onListChanged', {
   enumerable: true, configurable:true,
   get: function() { return this._onListChanged; },
   set: function(v) {
     if (v !== null && typeof v !== 'function') {
       throw Error("onListChanged property may only be set to 'null' or a function");
     }
     this._onListChanged = v;
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -72,13 +72,13 @@ skip-if = buildapp == 'mulet'
 [test_memory_attach_01.html]
 [test_memory_attach_02.html]
 [test_memory_census.html]
 [test_memory_gc_01.html]
 [test_preference.html]
 [test_settings.html]
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
-[test_attachProcess.html]
+[test_getProcess.html]
 skip-if = buildapp == 'mulet'
 [test_director.html]
 [test_director_connectToChild.html]
 skip-if = buildapp == 'mulet'
rename from toolkit/devtools/server/tests/mochitest/test_attachProcess.html
rename to toolkit/devtools/server/tests/mochitest/test_getProcess.html
--- a/toolkit/devtools/server/tests/mochitest/test_attachProcess.html
+++ b/toolkit/devtools/server/tests/mochitest/test_getProcess.html
@@ -1,12 +1,12 @@
 <SDOCTYPv HTM.>
 <html>
 <!--
-Bug 1060093 - Test DebuggerServer.attachProcess
+Bug 1060093 - Test DebuggerServer.getProcess
 -->
 <head>
   <meta charset="utf-8">
   <title>Mozilla Bug</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 </head>
 <body>
@@ -57,17 +57,17 @@ function runTests() {
     let client = new DebuggerClient(transport);
     client.connect(() => {
     client.mainRoot.listProcesses(response => {
       ok(response.processes.length >= 2, "Got at least the parent process and one child");
 
       // Connect to the first content processe available
       let content = response.processes.filter(p => (!p.parent))[0];
 
-      client.attachProcess(content.id).then(response => {
+      client.getProcess(content.id).then(response => {
         let actor = response.form;
         ok(actor.consoleActor, "Got the console actor");
         ok(actor.chromeDebugger, "Got the thread actor");
 
         // Ensure sending at least one request to an actor...
         client.request({
           to: actor.consoleActor,
           type: "evaluateJS",
--- a/toolkit/devtools/server/tests/unit/head_dbg.js
+++ b/toolkit/devtools/server/tests/unit/head_dbg.js
@@ -234,17 +234,17 @@ function get_chrome_actors(callback)
   if (!DebuggerServer.initialized) {
     DebuggerServer.init();
     DebuggerServer.addBrowserActors();
   }
   DebuggerServer.allowChromeProcess = true;
 
   let client = new DebuggerClient(DebuggerServer.connectPipe());
   client.connect(() => {
-    client.attachProcess().then(response => {
+    client.getProcess().then(response => {
       callback(client, response.form);
     });
   });
 }
 
 /**
  * Takes a relative file path and returns the absolute file url for it.
  */
--- a/toolkit/devtools/server/tests/unit/test_xpcshell_debugging.js
+++ b/toolkit/devtools/server/tests/unit/test_xpcshell_debugging.js
@@ -9,17 +9,17 @@ function run_test() {
 
   // _setupDebuggerServer is from xpcshell-test's head.js
   let testResumed = false;
   let DebuggerServer = _setupDebuggerServer([testFile.path], () => testResumed = true);
   let transport = DebuggerServer.connectPipe();
   let client = new DebuggerClient(transport);
   client.connect(() => {
     // Even though we have no tabs, listTabs gives us the chromeDebugger.
-    client.attachProcess().then(response => {
+    client.getProcess().then(response => {
       let actor = response.form.actor;
       client.attachTab(actor, (response, tabClient) => {
         tabClient.attachThread(null, (response, threadClient) => {
           threadClient.addOneTimeListener("paused", (event, packet) => {
           equal(packet.why.type, "breakpoint",
                 "yay - hit the breakpoint at the first line in our script");
             // Resume again - next stop should be our "debugger" statement.
             threadClient.addOneTimeListener("paused", (event, packet) => {
--- a/toolkit/devtools/webconsole/test/common.js
+++ b/toolkit/devtools/webconsole/test/common.js
@@ -78,17 +78,17 @@ function attachConsole(aListeners, aCall
           return;
         }
         let consoleActor = aResponse.tabs[aResponse.selected].consoleActor;
         aState.actor = consoleActor;
         aState.dbgClient.attachConsole(consoleActor, aListeners,
                                        _onAttachConsole.bind(null, aState));
       });
     } else {
-      aState.dbgClient.attachProcess().then(response => {
+      aState.dbgClient.getProcess().then(response => {
         let consoleActor = response.form.consoleActor;
         aState.actor = consoleActor;
         aState.dbgClient.attachConsole(consoleActor, aListeners,
                                        _onAttachConsole.bind(null, aState));
       });
     }
   });
 }
--- a/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties
+++ b/toolkit/locales/en-US/chrome/global/devtools/styleinspector.properties
@@ -63,16 +63,20 @@ helpLinkTitle=Read the documentation for
 # entered into the rule view a warning icon is displayed. This text is used for
 # the title attribute of the warning icon.
 rule.warning.title=Invalid property value
 
 # LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
 # first opened and there's no node selected in the rule view.
 rule.empty=No element selected.
 
+# LOCALIZATION NOTE (ruleView.selectorHighlighter.tooltip): Text displayed in a
+# tooltip when the mouse is over a selector highlighter icon in the rule view.
+rule.selectorHighlighter.tooltip=Highlight all elements matching this selector
+
 # LOCALIZATION NOTE (ruleView.contextmenu.selectAll): Text displayed in the
 # rule view context menu.
 ruleView.contextmenu.selectAll=Select all
 
 # LOCALIZATION NOTE (ruleView.contextmenu.selectAll.accessKey): Access key for
 # the rule view context menu "Select all" entry.
 ruleView.contextmenu.selectAll.accessKey=A
 
--- a/tools/mercurial/hgsetup/wizard.py
+++ b/tools/mercurial/hgsetup/wizard.py
@@ -385,17 +385,17 @@ class MercurialSetupWizard(object):
     def can_use_extension(self, c, name, path=None):
         # Load extension to hg and search stdout for printed exceptions
         if not path:
             path = os.path.join(self.vcs_tools_dir, 'hgext', name)
         result = subprocess.check_output(['hg',
              '--config', 'extensions.testmodule=%s' % path,
              '--config', 'ui.traceback=true'],
             stderr=subprocess.STDOUT)
-        return "Traceback" not in result
+        return b"Traceback" not in result
 
     def prompt_external_extension(self, c, name, prompt_text, path=None):
         # Ask the user if the specified extension should be enabled. Defaults
         # to treating the extension as one in version-control-tools/hgext/
         # in a directory with the same name as the extension and thus also
         # flagging the version-control-tools repo as needing an update.
         if name not in c.extensions:
             if not self.can_use_extension(c, name, path):