merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 16 Feb 2015 15:48:09 +0100
changeset 229058 2a6a0c025c7bf06c235b75ec6498de351b31cab5
parent 229027 e0cb32a0b1aa2b24db865d2cb0456861c07b430a (current diff)
parent 229057 f0048ba132131de0a7816b4173db52914b058f95 (diff)
child 229108 dfc80e670462bee5ab2b497e0055bd8fff6d7d3d
push id28280
push usercbook@mozilla.com
push dateMon, 16 Feb 2015 14:48:41 +0000
treeherdermozilla-central@2a6a0c025c7b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -410,8 +410,15 @@
                 accesskey="&bidiSwitchPageDirectionItem.accesskey;"
                 oncommand="gContextMenu.switchPageDirection();"/>
       <menuseparator id="inspect-separator" hidden="true"/>
       <menuitem id="context-inspect"
                 hidden="true"
                 label="&inspectContextMenu.label;"
                 accesskey="&inspectContextMenu.accesskey;"
                 oncommand="gContextMenu.inspectNode();"/>
+      <menuseparator id="context-media-eme-separator" hidden="true"/>
+      <menuitem id="context-media-eme-learnmore"
+                class="menuitem-iconic"
+                hidden="true"
+                label="&emeLearnMoreContextMenu.label;"
+                accesskey="&emeLearnMoreContextMenu.accesskey;"
+                onclick="gContextMenu.drmLearnMore(event);"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -473,16 +473,18 @@ nsContextMenu.prototype = {
     this.showItem("context-media-unmute", onMedia && this.target.muted);
     this.showItem("context-media-playbackrate", onMedia);
     this.showItem("context-media-showcontrols", onMedia && !this.target.controls);
     this.showItem("context-media-hidecontrols", onMedia && this.target.controls);
     this.showItem("context-video-fullscreen", this.onVideo && this.target.ownerDocument.mozFullScreenElement == null);
     var statsShowing = this.onVideo && this.target.mozMediaStatisticsShowing;
     this.showItem("context-video-showstats", this.onVideo && this.target.controls && !statsShowing);
     this.showItem("context-video-hidestats", this.onVideo && this.target.controls && statsShowing);
+    this.showItem("context-media-eme-learnmore", this.onDRMMedia);
+    this.showItem("context-media-eme-separator", this.onDRMMedia);
 
     // Disable them when there isn't a valid media source loaded.
     if (onMedia) {
       this.setItemAttr("context-media-playbackrate-050x", "checked", this.target.playbackRate == 0.5);
       this.setItemAttr("context-media-playbackrate-100x", "checked", this.target.playbackRate == 1.0);
       this.setItemAttr("context-media-playbackrate-150x", "checked", this.target.playbackRate == 1.5);
       this.setItemAttr("context-media-playbackrate-200x", "checked", this.target.playbackRate == 2.0);
       var hasError = this.target.error != null ||
@@ -494,17 +496,17 @@ nsContextMenu.prototype = {
       this.setItemAttr("context-media-playbackrate", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-050x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-100x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-150x", "disabled", hasError);
       this.setItemAttr("context-media-playbackrate-200x", "disabled", hasError);
       this.setItemAttr("context-media-showcontrols", "disabled", hasError);
       this.setItemAttr("context-media-hidecontrols", "disabled", hasError);
       if (this.onVideo) {
-        let canSaveSnapshot = this.target.readyState >= this.target.HAVE_CURRENT_DATA;
+        let canSaveSnapshot = !this.onDRMMedia && this.target.readyState >= this.target.HAVE_CURRENT_DATA;
         this.setItemAttr("context-video-saveimage",  "disabled", !canSaveSnapshot);
         this.setItemAttr("context-video-fullscreen", "disabled", hasError);
         this.setItemAttr("context-video-showstats", "disabled", hasError);
         this.setItemAttr("context-video-hidestats", "disabled", hasError);
       }
     }
     this.showItem("context-media-sep-commands",  onMedia);
   },
@@ -557,16 +559,17 @@ nsContextMenu.prototype = {
     // Initialize contextual info.
     this.onImage           = false;
     this.onLoadedImage     = false;
     this.onCompletedImage  = false;
     this.imageDescURL      = "";
     this.onCanvas          = false;
     this.onVideo           = false;
     this.onAudio           = false;
+    this.onDRMMedia        = false;
     this.onTextInput       = false;
     this.onNumeric         = false;
     this.onKeywordField    = false;
     this.mediaURL          = "";
     this.onLink            = false;
     this.onMailtoLink      = false;
     this.onSaveableLink    = false;
     this.link              = null;
@@ -635,32 +638,38 @@ nsContextMenu.prototype = {
       else if (this.target instanceof HTMLCanvasElement) {
         this.onCanvas = true;
       }
       else if (this.target instanceof HTMLVideoElement) {
         let mediaURL = this.target.currentSrc || this.target.src;
         if (this.isMediaURLReusable(mediaURL)) {
           this.mediaURL = mediaURL;
         }
+        if (this.target.isEncrypted) {
+          this.onDRMMedia = true;
+        }
         // Firefox always creates a HTMLVideoElement when loading an ogg file
         // directly. If the media is actually audio, be smarter and provide a
         // context menu with audio operations.
         if (this.target.readyState >= this.target.HAVE_METADATA &&
             (this.target.videoWidth == 0 || this.target.videoHeight == 0)) {
           this.onAudio = true;
         } else {
           this.onVideo = true;
         }
       }
       else if (this.target instanceof HTMLAudioElement) {
         this.onAudio = true;
         let mediaURL = this.target.currentSrc || this.target.src;
         if (this.isMediaURLReusable(mediaURL)) {
           this.mediaURL = mediaURL;
         }
+        if (this.target.isEncrypted) {
+          this.onDRMMedia = true;
+        }
       }
       else if (editFlags & (SpellCheckHelper.INPUT | SpellCheckHelper.TEXTAREA)) {
         this.onTextInput = (editFlags & SpellCheckHelper.TEXTINPUT) !== 0;
         this.onNumeric = (editFlags & SpellCheckHelper.NUMERIC) !== 0;
         this.onEditableArea = (editFlags & SpellCheckHelper.EDITABLE) !== 0;
         if (this.onEditableArea) {
           if (this.isRemote) {
             InlineSpellCheckerUI.initFromRemote(gContextMenuContentData.spellInfo);
@@ -1693,16 +1702,27 @@ nsContextMenu.prototype = {
   },
 
   copyMediaLocation : function () {
     var clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].
                     getService(Ci.nsIClipboardHelper);
     clipboard.copyString(this.mediaURL, document);
   },
 
+  drmLearnMore: function(aEvent) {
+    let drmInfoURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "drm-content";
+    let dest = whereToOpenLink(aEvent);
+    // Don't ever want this to open in the same tab as it'll unload the
+    // DRM'd video, which is going to be a bad idea in most cases.
+    if (dest == "current") {
+      dest = "tab";
+    }
+    openUILinkIn(drmInfoURL, dest);
+  },
+
   get imageURL() {
     if (this.onImage)
       return this.mediaURL;
     return "";
   },
 
   // Formats the 'Search <engine> for "<selection or link text>"' context menu.
   formatSearchContextItem: function() {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -56,16 +56,17 @@ support-files =
   file_double_close_tab.html
   file_favicon_change.html
   file_favicon_change_not_in_document.html
   file_fullscreen-window-open.html
   get_user_media.html
   head.js
   healthreport_testRemoteCommands.html
   moz.png
+  navigating_window_with_download.html
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
   parsingTestHelpers.jsm
   pinning_headers.sjs
   pinning_reports.sjs
   popup_blocker.html
   print_postdata.sjs
@@ -77,16 +78,18 @@ support-files =
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
   test_wyciwyg_copying.html
   title_test.svg
+  unknownContentType_file.pif
+  unknownContentType_file.pif^headers^
   video.ogg
   web_video.html
   web_video1.ogv
   web_video1.ogv^headers^
   zoom_test.html
   test_no_mcb_on_http_site_img.html
   test_no_mcb_on_http_site_img.css
   test_no_mcb_on_http_site_font.html
@@ -382,16 +385,18 @@ skip-if = e10s
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
 [browser_save_link-perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
+[browser_save_link_when_window_navigates.js]
+skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_save_link_when_window_navigates.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+
+const SAVE_PER_SITE_PREF = "browser.download.lastDir.savePerSite";
+const ALWAYS_DOWNLOAD_DIR_PREF = "browser.download.useDownloadDir";
+const UCT_URI = "chrome://mozapps/content/downloads/unknownContentType.xul";
+
+Cc["@mozilla.org/moz/jssubscript-loader;1"]
+  .getService(Ci.mozIJSSubScriptLoader)
+  .loadSubScript("chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+                 this);
+
+function createTemporarySaveDirectory() {
+  var saveDir = Cc["@mozilla.org/file/directory_service;1"]
+                  .getService(Ci.nsIProperties)
+                  .get("TmpD", Ci.nsIFile);
+  saveDir.append("testsavedir");
+  if (!saveDir.exists()) {
+    info("create testsavedir!");
+    saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+  }
+  info("return from createTempSaveDir: " + saveDir.path);
+  return saveDir;
+}
+
+function triggerSave(aWindow, aCallback) {
+  info("started triggerSave, persite downloads: " + (Services.prefs.getBoolPref(SAVE_PER_SITE_PREF) ? "on" : "off"));
+  var fileName;
+  let testBrowser = aWindow.gBrowser.selectedBrowser;
+  let testURI = "http://mochi.test:8888/browser/browser/base/content/test/general/navigating_window_with_download.html";
+  windowObserver.setCallback(onUCTDialog);
+  testBrowser.loadURI(testURI);
+
+  // Create the folder the link will be saved into.
+  var destDir = createTemporarySaveDirectory();
+  var destFile = destDir.clone();
+
+  MockFilePicker.displayDirectory = destDir;
+  MockFilePicker.showCallback = function(fp) {
+    info("showCallback");
+    fileName = fp.defaultString;
+    info("fileName: " + fileName);
+    destFile.append (fileName);
+    MockFilePicker.returnFiles = [destFile];
+    MockFilePicker.filterIndex = 1; // kSaveAsType_URL
+    info("done showCallback");
+  };
+
+  mockTransferCallback = function(downloadSuccess) {
+    info("mockTransferCallback");
+    onTransferComplete(aWindow, downloadSuccess, destDir);
+    destDir.remove(true);
+    ok(!destDir.exists(), "Destination dir should be removed");
+    ok(!destFile.exists(), "Destination file should be removed");
+    mockTransferCallback = null;
+    info("done mockTransferCallback");
+  }
+
+  function onUCTDialog(dialog) {
+    function doLoad() {
+      content.document.querySelector('iframe').remove();
+    }
+    testBrowser.messageManager.loadFrameScript("data:,(" + doLoad.toString() + ")()", false);
+    executeSoon(continueDownloading);
+  }
+
+  function continueDownloading() {
+    let windows = Services.wm.getEnumerator("");
+    while (windows.hasMoreElements()) {
+      let win = windows.getNext();
+      if (win.location && win.location.href == UCT_URI) {
+        win.document.documentElement._fireButtonEvent("accept");
+        win.close();
+        return;
+      }
+    }
+    ok(false, "No Unknown Content Type dialog yet?");
+  }
+
+  function onTransferComplete(aWindow, downloadSuccess, destDir) {
+    ok(downloadSuccess, "Link should have been downloaded successfully");
+    aWindow.close();
+
+    executeSoon(aCallback);
+  }
+}
+
+
+let windowObserver = {
+  setCallback: function(aCallback) {
+    if (this._callback) {
+      ok(false, "Should only be dealing with one callback at a time.");
+    }
+    this._callback = aCallback;
+  },
+  observe: function(aSubject, aTopic, aData) {
+    if (aTopic != "domwindowopened") {
+      return;
+    }
+
+    let win = aSubject.QueryInterface(Ci.nsIDOMEventTarget);
+
+    win.addEventListener("load", function onLoad(event) {
+      win.removeEventListener("load", onLoad, false);
+
+      if (win.location == UCT_URI) {
+        SimpleTest.executeSoon(function() {
+          if (windowObserver._callback) {
+            windowObserver._callback(win);
+            delete windowObserver._callback;
+          } else {
+            ok(false, "Unexpected UCT dialog!");
+          }
+        });
+      }
+    }, false);
+  }
+};
+
+Services.ww.registerNotification(windowObserver);
+
+function test() {
+  waitForExplicitFinish();
+
+  function testOnWindow(options, callback) {
+    info("testOnWindow(" + options + ")");
+    var win = OpenBrowserWindow(options);
+    info("got " + win);
+    whenDelayedStartupFinished(win, () => callback(win));
+  }
+
+  function whenDelayedStartupFinished(aWindow, aCallback) {
+    info("whenDelayedStartupFinished");
+    Services.obs.addObserver(function observer(aSubject, aTopic) {
+      info("whenDelayedStartupFinished, got topic: " + aTopic + ", got subject: " + aSubject + ", waiting for " + aWindow);
+      if (aWindow == aSubject) {
+        Services.obs.removeObserver(observer, aTopic);
+        executeSoon(aCallback);
+        info("whenDelayedStartupFinished found our window");
+      }
+    }, "browser-delayed-startup-finished", false);
+  }
+
+  mockTransferRegisterer.register();
+
+  registerCleanupFunction(function () {
+    info("Running the cleanup code");
+    mockTransferRegisterer.unregister();
+    MockFilePicker.cleanup();
+    Services.ww.unregisterNotification(windowObserver);
+    Services.prefs.clearUserPref(ALWAYS_DOWNLOAD_DIR_PREF);
+    Services.prefs.clearUserPref(SAVE_PER_SITE_PREF);
+    info("Finished running the cleanup code");
+  });
+ 
+  Services.prefs.setBoolPref(ALWAYS_DOWNLOAD_DIR_PREF, false);
+  testOnWindow(undefined, function(win) {
+    let windowGonePromise = promiseWindowWillBeClosed(win);
+    Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, true);
+    triggerSave(win, function() {
+      windowGonePromise.then(function() {
+        Services.prefs.setBoolPref(SAVE_PER_SITE_PREF, false);
+        testOnWindow(undefined, function(win) {
+          triggerSave(win, finish);
+        });
+      });
+    });
+  });
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/navigating_window_with_download.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+  <head><title>This window will navigate while you're downloading something</title></head>
+  <body>
+    <iframe src="http://mochi.test:8888/browser/browser/base/content/test/general/unknownContentType_file.pif"></iframe>
+  </body>
+</html>
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif
copy to browser/base/content/test/general/unknownContentType_file.pif
copy from toolkit/mozapps/downloads/tests/chrome/unknownContentType_dialog_layout_data.pif^headers^
copy to browser/base/content/test/general/unknownContentType_file.pif^headers^
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -290,40 +290,64 @@ loop.standaloneRoomViews = (function(moz
      * Specifically updates the local camera stream size and position, depending
      * on the size and position of the remote video stream.
      * This method gets called from `updateVideoContainer`, which is defined in
      * the `MediaSetupMixin`.
      *
      * @param  {Object} ratio Aspect ratio of the local camera stream
      */
     updateLocalCameraPosition: function(ratio) {
+      // The local stream is a quarter of the remote stream.
+      var LOCAL_STREAM_SIZE = 0.25;
+      // The local stream overlaps the remote stream by a quarter of the local stream.
+      var LOCAL_STREAM_OVERLAP = 0.25;
+      // The minimum size of video height/width allowed by the sdk css.
+      var SDK_MIN_SIZE = 48;
+
       var node = this._getElement(".local");
-      var parent = node.offsetParent || this._getElement(".media");
-      // The local camera view should be a sixth of the size of its offset parent
-      // and positioned to overlap with the remote stream at a quarter of its width.
-      var parentWidth = parent.offsetWidth;
-      var targetWidth = parentWidth / 6;
+      var targetWidth;
 
       node.style.right = "auto";
       if (window.matchMedia && window.matchMedia("screen and (max-width:640px)").matches) {
+        // For reduced screen widths, we just go for a fixed size and no overlap.
         targetWidth = 180;
+        node.style.width = (targetWidth * ratio.width) + "px";
+        node.style.height = (targetWidth * ratio.height) + "px";
         node.style.left = "auto";
       } else {
+        // The local camera view should be a quarter of the size of the remote stream
+        // and positioned to overlap with the remote stream at a quarter of its width.
+
         // Now position the local camera view correctly with respect to the remote
         // video stream.
         var remoteVideoDimensions = this.getRemoteVideoDimensions();
+        targetWidth = remoteVideoDimensions.streamWidth * LOCAL_STREAM_SIZE;
+
+        var realWidth = targetWidth * ratio.width;
+        var realHeight = targetWidth * ratio.height;
+
+        // If we've hit the min size limits, then limit at the minimum.
+        if (realWidth < SDK_MIN_SIZE) {
+          realWidth = SDK_MIN_SIZE;
+          realHeight = realWidth / ratio.width * ratio.height;
+        }
+        if (realHeight < SDK_MIN_SIZE) {
+          realHeight = SDK_MIN_SIZE;
+          realWidth = realHeight / ratio.height * ratio.width;
+        }
+
         var offsetX = (remoteVideoDimensions.streamWidth + remoteVideoDimensions.offsetX);
         // The horizontal offset of the stream, and the width of the resulting
         // pillarbox, is determined by the height exponent of the aspect ratio.
         // Therefore we multiply the width of the local camera view by the height
         // ratio.
-        node.style.left = (offsetX - ((targetWidth * ratio.height) / 4)) + "px";
+        node.style.left = (offsetX - (realWidth * LOCAL_STREAM_OVERLAP)) + "px";
+        node.style.width = realWidth + "px";
+        node.style.height = realHeight + "px";
       }
-      node.style.width = (targetWidth * ratio.width) + "px";
-      node.style.height = (targetWidth * ratio.height) + "px";
     },
 
     /**
      * Checks if current room is active.
      *
      * @return {Boolean}
      */
     _roomIsActive: function() {
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -138,16 +138,118 @@ describe("loop.standaloneRoomViews", fun
         sinon.assert.calledOnce(dispatch);
         sinon.assert.calledWithExactly(dispatch, new sharedActions.SetMute({
           type: "video",
           enabled: true
         }));
       });
     });
 
+    describe("Local Stream Size Position", function() {
+      var view, localElement;
+
+      beforeEach(function() {
+        sandbox.stub(window, "matchMedia").returns({
+          matches: false
+        });
+        view = mountTestComponent();
+        localElement = view._getElement(".local");
+      });
+
+      it("should be a quarter of the width of the main stream", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.height).eql("120px");
+      });
+
+      it("should be a quarter of the width reduced for aspect ratio", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.height).eql("160px");
+      });
+
+      it("should ensure the height is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("64px");
+        expect(localElement.style.height).eql("48px");
+      });
+
+      it("should ensure the width is a minimum of 48px", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 180,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("48px");
+        expect(localElement.style.height).eql("64px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 1,
+          height: 0.75
+        });
+
+        expect(localElement.style.width).eql("160px");
+        expect(localElement.style.left).eql("600px");
+      });
+
+      it("should position the stream to overlap the main stream by a quarter when the aspect ratio is vertical", function() {
+        sandbox.stub(view, "getRemoteVideoDimensions").returns({
+          streamWidth: 640,
+          offsetX: 0
+        });
+
+        view.updateLocalCameraPosition({
+          width: 0.75,
+          height: 1
+        });
+
+        expect(localElement.style.width).eql("120px");
+        expect(localElement.style.left).eql("610px");
+      });
+    });
+
     describe("#render", function() {
       var view;
 
       beforeEach(function() {
         view = mountTestComponent();
       });
 
       describe("Empty room message", function() {
--- a/browser/components/migration/content/aboutWelcomeBack.xhtml
+++ b/browser/components/migration/content/aboutWelcomeBack.xhtml
@@ -61,17 +61,19 @@
             <treecol cycler="true" id="restore" type="checkbox" label="&restorepage.restoreHeader;"/>
             <splitter class="tree-splitter"/>
             <treecol primary="true" id="title" label="&restorepage.listHeader;" flex="1"/>
           </treecols>
           <treechildren flex="1"/>
         </tree>
 
         <hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="button-container">
-          <button class="primary" label="&welcomeback2.restoreButton;"
+          <button class="primary"
+                  id="errorTryAgain"
+                  label="&welcomeback2.restoreButton;"
                   accesskey="&welcomeback2.restoreButton.access;"
                   oncommand="restoreSession();"/>
         </hbox>
 
         <input type="text" id="sessionData" style="display: none;"/>
       </div>
     </div>
   </body>
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -133,16 +133,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/FormValidationHandler.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "WebChannel",
                                   "resource://gre/modules/WebChannel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
                                   "resource:///modules/ReaderParent.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
+                                  "resource://gre/modules/AddonWatcher.jsm");
+
 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // Seconds of idle before trying to create a bookmarks backup.
 const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60;
 // Minimum interval between backups.  We try to not create more than one backup
 // per interval.
 const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
@@ -559,16 +562,86 @@ BrowserGlue.prototype = {
   },
 
   _onAppDefaults: function BG__onAppDefaults() {
     // apply distribution customizations (prefs)
     // other customizations are applied in _finalUIStartup()
     this._distributionCustomizer.applyPrefDefaults();
   },
 
+  _notifySlowAddon: function BG_notifySlowAddon(addonId) {
+    let addonCallback = function(addon) {
+      if (!addon) {
+        Cu.reportError("couldn't look up addon: " + addonId);
+        return;
+      }
+      let win = RecentWindow.getMostRecentBrowserWindow();
+
+      if (!win) {
+        return;
+      }
+
+      let brandBundle = win.document.getElementById("bundle_brand");
+      let brandShortName = brandBundle.getString("brandShortName");
+      let message = win.gNavigatorBundle.getFormattedString("addonwatch.slow", [addon.name, brandShortName]);
+      let notificationBox = win.document.getElementById("global-notificationbox");
+      let notificationId = 'addon-slow:' + addonId;
+      let notification = notificationBox.getNotificationWithValue(notificationId);
+      if(notification) {
+        notification.label = message;
+      } else {
+        let buttons = [
+          {
+            label: win.gNavigatorBundle.getFormattedString("addonwatch.disable.label", [addon.name]),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.disable.accesskey"),
+            callback: function() {
+              addon.userDisabled = true;
+              if (addon.pendingOperations != addon.PENDING_NONE) {
+                let restartMessage = win.gNavigatorBundle.getFormattedString("addonwatch.restart.message", [addon.name, brandShortName]);
+                let restartButton = [
+                  {
+                    label: win.gNavigatorBundle.getFormattedString("addonwatch.restart.label", [brandShortName]),
+                    accessKey: win.gNavigatorBundle.getString("addonwatch.restart.accesskey"),
+                    callback: function() {
+                      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+                        .getService(Ci.nsIAppStartup);
+                      appStartup.quit(appStartup.eForceQuit | appStartup.eRestart);
+                    }
+                  }
+                ];
+                const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+                notificationBox.appendNotification(restartMessage, "restart-" + addonId, "",
+                                                   priority, restartButton);
+              }
+            }
+          },
+          {
+            label: win.gNavigatorBundle.getString("addonwatch.ignoreSession.label"),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.ignoreSession.accesskey"),
+            callback: function() {
+              AddonWatcher.ignoreAddonForSession(addonId);
+            }
+          },
+          {
+            label: win.gNavigatorBundle.getString("addonwatch.ignorePerm.label"),
+            accessKey: win.gNavigatorBundle.getString("addonwatch.ignorePerm.accesskey"),
+            callback: function() {
+              AddonWatcher.ignoreAddonPermanently(addonId);
+            }
+          },
+        ];
+
+        const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+        notificationBox.appendNotification(message, notificationId, "",
+                                             priority, buttons);
+      }
+    };
+    AddonManager.getAddonByID(addonId, addonCallback);
+  },
+
   // runs on startup, before the first command line handler is invoked
   // (i.e. before the first window is opened)
   _finalUIStartup: function BG__finalUIStartup() {
     this._sanitizer.onStartup();
     // check if we're in safe mode
     if (Services.appinfo.inSafeMode) {
       Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul", 
                              "_blank", "chrome,centerscreen,modal,resizable=no", null);
@@ -607,16 +680,18 @@ BrowserGlue.prototype = {
     LoginManagerParent.init();
     ReaderParent.init();
 
 #ifdef NIGHTLY_BUILD
     Services.prefs.addObserver(POLARIS_ENABLED, this, false);
 #endif
 
     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
+
+    AddonWatcher.init(this._notifySlowAddon);
   },
 
   _checkForOldBuildUpdates: function () {
     // check for update if our build is old
     if (Services.prefs.getBoolPref("app.update.enabled") &&
         Services.prefs.getBoolPref("app.update.checkInstallTime")) {
 
       let buildID = Services.appinfo.appBuildID;
--- a/browser/components/preferences/cookies.xul
+++ b/browser/components/preferences/cookies.xul
@@ -26,17 +26,17 @@
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
     <key key="&focusSearch1.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
     <key key="&focusSearch2.key;" modifiers="accel" oncommand="gCookiesWindow.focusFilterBox();"/>
   </keyset>
 
-  <vbox flex="1" class="contentPane">
+  <vbox flex="1" class="contentPane largeDialogContainer">
     <hbox align="center">
       <label accesskey="&filter.accesskey;" control="filter">&filter.label;</label>
       <textbox type="search" id="filter" flex="1"
                aria-controls="cookiesList"
                oncommand="gCookiesWindow.filter();"/>
     </hbox>
     <separator class="thin"/>
     <label control="cookiesList" id="cookiesIntro" value="&cookiesonsystem.label;"/>
--- a/browser/components/preferences/fonts.xul
+++ b/browser/components/preferences/fonts.xul
@@ -17,16 +17,17 @@
             title="&fontsDialog.title;"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
             style="">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="FontsDialogPane"
+            class="largeDialogContainer"
             helpTopic="prefs-fonts-and-colors">
   
     <preferences id="fontPreferences">
       <preference id="font.language.group"  name="font.language.group"  type="wstring"/>
       <preference id="browser.display.use_document_fonts"
                   name="browser.display.use_document_fonts"
                   type="int"/>
       <preference id="intl.charset.fallback.override" name="intl.charset.fallback.override" type="string"/>
--- a/browser/components/preferences/in-content/subdialogs.js
+++ b/browser/components/preferences/in-content/subdialogs.js
@@ -32,16 +32,22 @@ let gSubDialog = {
     chromeBrowser.addEventListener("DOMTitleChanged", this.updateTitle, true);
 
     // Similarly DOMFrameContentLoaded only fires on the top window
     window.addEventListener("DOMFrameContentLoaded", this._onContentLoaded.bind(this), true);
 
     // Wait for the stylesheets injected during DOMContentLoaded to load before showing the dialog
     // otherwise there is a flicker of the stylesheet applying.
     this._frame.addEventListener("load", this._onLoad.bind(this));
+
+    chromeBrowser.addEventListener("unload", function(aEvent) {
+      if (aEvent.target.location.href != "about:blank") {
+        this.close();
+      }
+    }.bind(this), true);
   },
 
   uninit: function() {
     let chromeBrowser = window.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebNavigation)
                               .QueryInterface(Ci.nsIDocShell)
                               .chromeEventHandler;
     chromeBrowser.removeEventListener("DOMTitleChanged", gSubDialog.updateTitle, true);
@@ -87,16 +93,18 @@ let gSubDialog = {
     }
 
     this._overlay.style.visibility = "";
     // Clear the sizing inline styles.
     this._frame.removeAttribute("style");
     // Clear the sizing attributes
     this._box.removeAttribute("width");
     this._box.removeAttribute("height");
+    this._box.style.removeProperty("min-height");
+    this._box.style.removeProperty("min-width");
 
     setTimeout(() => {
       // Unload the dialog after the event listeners run so that the load of about:blank isn't
       // cancelled by the ESC <key>.
       this._frame.loadURI("about:blank");
     }, 0);
   },
 
@@ -139,20 +147,50 @@ let gSubDialog = {
 
   _onLoad: function(aEvent) {
     if (aEvent.target.contentWindow.location == "about:blank")
       return;
 
     // Do this on load to wait for the CSS to load and apply before calculating the size.
     let docEl = this._frame.contentDocument.documentElement;
 
-    // padding-bottom doesn't seem to be included in the scrollHeight of the document element in XUL
-    // so add it ourselves.
-    let paddingBottom = parseFloat(this._frame.contentWindow.getComputedStyle(docEl).paddingBottom);
+    let groupBoxTitle = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-title");
+    let groupBoxTitleHeight = groupBoxTitle.clientHeight +
+                              parseFloat(getComputedStyle(groupBoxTitle).borderBottomWidth);
+
+    let groupBoxBody = document.getAnonymousElementByAttribute(this._box, "class", "groupbox-body");
+    let boxVerticalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingTop);
+    let boxHorizontalPadding = 2 * parseFloat(getComputedStyle(groupBoxBody).paddingLeft);
+    let frameWidth = docEl.style.width || docEl.scrollWidth + "px";
+    let frameHeight = docEl.style.height || docEl.scrollHeight + "px";
+    let boxVerticalBorder = 2 * parseFloat(getComputedStyle(this._box).borderTopWidth);
+    let boxHorizontalBorder = 2 * parseFloat(getComputedStyle(this._box).borderLeftWidth);
+
+    let frameRect = this._frame.getBoundingClientRect();
+    let boxRect = this._box.getBoundingClientRect();
+    let frameSizeDifference = (frameRect.top - boxRect.top) + (boxRect.bottom - frameRect.bottom);
 
-    this._frame.style.width = docEl.style.width || docEl.scrollWidth + "px";
-    this._frame.style.height = docEl.style.height || (docEl.scrollHeight + paddingBottom) + "px";
+    // Now check if the frame height we calculated is possible at this window size,
+    // accounting for titlebar, padding/border and some spacing.
+    let maxHeight = window.innerHeight - frameSizeDifference - 30;
+    if (frameHeight > maxHeight) {
+      // If not, we should probably let the dialog scroll:
+      frameHeight = maxHeight;
+      let containers = this._frame.contentDocument.querySelectorAll('.largeDialogContainer');
+      for (let container of containers) {
+        container.classList.add("doScroll");
+      }
+    }
+
+    this._frame.style.width = frameWidth;
+    this._frame.style.height = frameHeight;
+    this._box.style.minHeight = "calc(" +
+                                (boxVerticalBorder + groupBoxTitleHeight + boxVerticalPadding) +
+                                "px + " + frameHeight + ")";
+    this._box.style.minWidth = "calc(" +
+                               (boxHorizontalBorder + boxHorizontalPadding) +
+                               "px + " + frameWidth + ")";
 
     this._overlay.style.visibility = "visible";
     this._frame.focus();
     this._overlay.style.opacity = ""; // XXX: focus hack continued from _onContentLoaded
   },
 };
--- a/browser/components/preferences/in-content/tests/browser_subdialogs.js
+++ b/browser/components/preferences/in-content/tests/browser_subdialogs.js
@@ -71,17 +71,17 @@ let gTests = [{
     let dialog = yield dialogPromise;
 
     let closingPromise = promiseDialogClosing(dialog);
 
     info("cancelling the dialog");
     dialog.document.documentElement.cancelDialog();
 
     let closingEvent = yield closingPromise;
-    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'accept'");
+    ise(closingEvent.detail.button, "cancel", "closing event should indicate button was 'cancel'");
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
   desc: "Check window.close on the dialog",
   run: function* () {
@@ -114,16 +114,36 @@ let gTests = [{
     yield EventUtils.synthesizeMouseAtCenter(content.document.getElementById("dialogClose"), {},
                                              content.window);
 
     yield deferredClose.promise;
     ise(rv.acceptCount, 0, "return value should NOT have been updated");
   },
 },
 {
+  desc: "Check that 'back' navigation will close the dialog",
+  run: function* () {
+    let rv = { acceptCount: 0 };
+    let deferredClose = Promise.defer();
+    let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
+                                             (aEvent) => dialogClosingCallback(deferredClose, aEvent));
+    let dialog = yield dialogPromise;
+
+    // XXX Without the call to promiseDialogClosing the test causes
+    //     intermittent failures in browser_change_app_handler.js.
+    let unusedClosingPromise = promiseDialogClosing(dialog);
+
+    info("cancelling the dialog");
+    content.gSubDialog._frame.goBack();
+
+    yield deferredClose.promise;
+    ise(rv.acceptCount, 0, "return value should NOT have been updated");
+  },
+},
+{
   desc: "Hitting escape in the dialog",
   run: function* () {
     let rv = { acceptCount: 0 };
     let deferredClose = Promise.defer();
     let dialogPromise = openAndLoadSubDialog(gDialogURL, null, rv,
                                              (aEvent) => dialogClosingCallback(deferredClose, aEvent));
     let dialog = yield dialogPromise;
 
--- a/browser/components/preferences/languages.xul
+++ b/browser/components/preferences/languages.xul
@@ -17,16 +17,17 @@
             title="&languages.customize.Header;"
             dlgbuttons="accept,cancel,help"
             ondialoghelp="openPrefsHelp()"
             style="width: &window.width;;">
 
   <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
 
   <prefpane id="LanguagesDialogPane"
+            class="largeDialogContainer"
             onpaneload="gLanguagesDialog.init();"
             helpTopic="prefs-languages">
 
     <preferences>
       <preference id="intl.accept_languages" name="intl.accept_languages" type="wstring"/>
       <preference id="pref.browser.language.disable_button.up"
                   name="pref.browser.language.disable_button.up"
                   type="bool"/>
--- a/browser/components/preferences/permissions.xul
+++ b/browser/components/preferences/permissions.xul
@@ -24,17 +24,17 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
-  <vbox class="contentPane" flex="1">
+  <vbox class="contentPane largeDialogContainer" flex="1">
     <description id="permissionsText" control="url"/>
     <separator class="thin"/>
     <label id="urlLabel" control="url" value="&address.label;" accesskey="&address.accesskey;"/>
     <hbox align="start">
       <textbox id="url" flex="1" 
                oninput="gPermissionManager.onHostInput(event.target);"
                onkeypress="gPermissionManager.onHostKeyPress(event);"/>
     </hbox>
--- a/browser/components/preferences/sanitize.xul
+++ b/browser/components/preferences/sanitize.xul
@@ -77,18 +77,18 @@
           </row>
         </rows>
       </grid>
     </groupbox>
     <groupbox orient="horizontal">
       <caption label="&dataSection.label;"/>
       <grid flex="1">
         <columns>
-          <column dialogWidth="&column.width2;"
-                  subdialogWidth="&inContentColumn.width;"/>
+          <column dialogWidth="&sanitizePrefs2.column.width;"
+                  subdialogWidth="&sanitizePrefs2.inContent.column.width;"/>
           <column flex="1"/>
         </columns>
         <rows>
           <row>
             <checkbox label="&itemPasswords.label;"
                       accesskey="&itemPasswords.accesskey;"
                       preference="privacy.clearOnShutdown.passwords"/>
             <checkbox label="&itemOfflineApps.label;"
--- a/browser/components/preferences/translation.xul
+++ b/browser/components/preferences/translation.xul
@@ -23,55 +23,57 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <keyset>
     <key key="&windowClose.key;" modifiers="accel" oncommand="window.close();"/>
   </keyset>
 
-  <vbox class="contentPane" flex="1">
-    <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
-    <separator class="thin"/>
-    <tree id="languagesTree" flex="1" style="height: 12em;"
-          hidecolumnpicker="true"
-          onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
-          onselect="gTranslationExceptions.onLanguageSelected();">
-      <treecols>
-        <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
-      </treecols>
-      <treechildren/>
-    </tree>
-  </vbox>
-  <hbox align="end">
-    <hbox class="actionButtons" flex="1">
-      <button id="removeLanguage" disabled="true"
-              accesskey="&removeLanguage.accesskey;"
-              icon="remove" label="&removeLanguage.label;"
-              oncommand="gTranslationExceptions.onLanguageDeleted();"/>
-      <button id="removeAllLanguages"
-              icon="clear" label="&removeAllLanguages.label;"
-              accesskey="&removeAllLanguages.accesskey;"
-              oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
-      <spacer flex="1"/>
+  <vbox class="largeDialogContainer">
+    <vbox class="contentPane" flex="1">
+      <label id="languagesLabel" control="permissionsTree">&noTranslationForLanguages.label;</label>
+      <separator class="thin"/>
+      <tree id="languagesTree" flex="1" style="height: 12em;"
+            hidecolumnpicker="true"
+            onkeypress="gTranslationExceptions.onLanguageKeyPress(event)"
+            onselect="gTranslationExceptions.onLanguageSelected();">
+        <treecols>
+          <treecol id="languageCol" label="&treehead.languageName.label;" flex="1"/>
+        </treecols>
+        <treechildren/>
+      </tree>
+    </vbox>
+    <hbox align="end">
+      <hbox class="actionButtons" flex="1">
+        <button id="removeLanguage" disabled="true"
+                accesskey="&removeLanguage.accesskey;"
+                icon="remove" label="&removeLanguage.label;"
+                oncommand="gTranslationExceptions.onLanguageDeleted();"/>
+        <button id="removeAllLanguages"
+                icon="clear" label="&removeAllLanguages.label;"
+                accesskey="&removeAllLanguages.accesskey;"
+                oncommand="gTranslationExceptions.onAllLanguagesDeleted();"/>
+        <spacer flex="1"/>
+      </hbox>
     </hbox>
-  </hbox>
-  <separator/>
-  <vbox class="contentPane" flex="1">
-    <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
-    <separator class="thin"/>
-    <tree id="sitesTree" flex="1" style="height: 12em;"
-          hidecolumnpicker="true"
-          onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
-          onselect="gTranslationExceptions.onSiteSelected();">
-      <treecols>
-        <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
-      </treecols>
-      <treechildren/>
-    </tree>
+    <separator/>
+    <vbox class="contentPane" flex="1">
+      <label id="languagesLabel" control="permissionsTree">&noTranslationForSites.label;</label>
+      <separator class="thin"/>
+      <tree id="sitesTree" flex="1" style="height: 12em;"
+            hidecolumnpicker="true"
+            onkeypress="gTranslationExceptions.onSiteKeyPress(event)"
+            onselect="gTranslationExceptions.onSiteSelected();">
+        <treecols>
+          <treecol id="siteCol" label="&treehead.siteName.label;" flex="1"/>
+        </treecols>
+        <treechildren/>
+      </tree>
+    </vbox>
   </vbox>
   <hbox align="end">
     <hbox class="actionButtons" flex="1">
       <button id="removeSite" disabled="true"
               accesskey="&removeSite.accesskey;"
               icon="remove" label="&removeSite.label;"
               oncommand="gTranslationExceptions.onSiteDeleted();"/>
       <button id="removeAllSites"
--- a/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
+++ b/browser/components/search/test/browser_hiddenOneOffs_cleanup.js
@@ -33,19 +33,19 @@ add_task(function* test_remove() {
   info("Removing testEngine_dupe.xml");
   Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
 
   let hiddenOneOffs =
     Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
 
   is(hiddenOneOffs.length, 1,
      "hiddenOneOffs has the correct engine count post removal.");
-  is(hiddenOneOffs.includes("FooDupe"), false,
+  is(hiddenOneOffs.some(x => x == "FooDupe"), false,
      "Removed Engine is not in hiddenOneOffs after removal");
-  is(hiddenOneOffs.includes("Foo"), true,
+  is(hiddenOneOffs.some(x => x == "Foo"), true,
      "Current hidden engine is not affected by removal.");
 
   info("Removing testEngine.xml");
   Services.search.removeEngine(Services.search.getEngineByName("Foo"));
 
   is(Services.prefs.getCharPref("browser.search.hiddenOneOffs"), "",
      "hiddenOneOffs is empty after removing all hidden engines.");
 });
@@ -56,19 +56,19 @@ add_task(function* test_add() {
   Services.prefs.setCharPref("browser.search.hiddenOneOffs", testPref);
   yield promiseNewEngine("testEngine_dupe.xml");
 
   let hiddenOneOffs =
     Services.prefs.getCharPref("browser.search.hiddenOneOffs").split(",");
 
   is(hiddenOneOffs.length, 1,
      "hiddenOneOffs has the correct number of hidden engines present post add.");
-  is(hiddenOneOffs.includes("FooDupe"), false,
+  is(hiddenOneOffs.some(x => x == "FooDupe"), false,
      "Added engine is not present in hidden list.");
-  is(hiddenOneOffs.includes("Foo"), true,
+  is(hiddenOneOffs.some(x => x == "Foo"), true,
      "Adding an engine does not remove engines from hidden list.");
 });
 
 registerCleanupFunction(() => {
   info("Removing testEngine.xml");
   Services.search.removeEngine(Services.search.getEngineByName("Foo"));
   info("Removing testEngine_dupe.xml");
   Services.search.removeEngine(Services.search.getEngineByName("FooDupe"));
--- a/browser/devtools/animationinspector/test/head.js
+++ b/browser/devtools/animationinspector/test/head.js
@@ -154,17 +154,22 @@ let openAnimationInspector = Task.async(
   inspector.sidebar.select("animationinspector");
 
   info("Waiting for the inspector and sidebar to be ready");
   yield promise.all(initPromises);
 
   let win = inspector.sidebar.getWindowForTab("animationinspector");
   let {AnimationsController, AnimationsPanel} = win;
 
-  yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+  info("Waiting for the animation controller and panel to be ready");
+  if (AnimationsPanel.initialized) {
+    yield AnimationsPanel.initialized;
+  } else {
+    yield AnimationsPanel.once(AnimationsPanel.PANEL_INITIALIZED);
+  }
 
   return {
     toolbox: toolbox,
     inspector: inspector,
     controller: AnimationsController,
     panel: AnimationsPanel,
     window: win
   };
--- a/browser/devtools/netmonitor/netmonitor.xul
+++ b/browser/devtools/netmonitor/netmonitor.xul
@@ -494,30 +494,30 @@
                         flex="1">
                     <vbox id="security-info-connection"
                           class="tabpanel-summary-container">
                       <label class="plain tabpanel-summary-label"
                              value="&netmonitorUI.security.connection;"/>
                       <vbox class="security-info-section">
                         <hbox id="security-protocol-version"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.protocolVersion;"/>
                           <label id="security-protocol-version-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                           <image class="security-warning-icon"
                                  id="security-warning-sslv3"
                                  tooltiptext="&netmonitorUI.security.warning.sslv3;" />
                         </hbox>
                         <hbox id="security-ciphersuite"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.cipherSuite;"/>
                           <label id="security-ciphersuite-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                           <image class="security-warning-icon"
                                  id="security-warning-cipher"
@@ -527,27 +527,27 @@
                     </vbox>
                     <vbox id="security-info-domain"
                           class="tabpanel-summary-container">
                       <label class="plain tabpanel-summary-label"
                              id="security-info-host-header"/>
                       <vbox class="security-info-section">
                         <hbox id="security-http-strict-transport-security"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.hsts;"/>
                           <label id="security-http-strict-transport-security-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                         </hbox>
                         <hbox id="security-public-key-pinning"
                               class="tabpanel-summary-container"
-                              align="center">
+                              align="baseline">
                           <label class="plain tabpanel-summary-label"
                                  value="&netmonitorUI.security.hpkp;"/>
                           <label id="security-public-key-pinning-value"
                                  class="plain tabpanel-summary-value devtools-monospace"
                                  crop="end"
                                  flex="1"/>
                         </hbox>
                       </vbox>
@@ -558,116 +558,116 @@
                                value="&netmonitorUI.security.certificate;"/>
                       <vbox class="security-info-section">
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.subjectinfo.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.cn;:"/>
                             <label id="security-cert-subject-cn"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.o;:"/>
                             <label id="security-cert-subject-o"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.ou;:"/>
                             <label id="security-cert-subject-ou"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.issuerinfo.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.cn;:"/>
                             <label id="security-cert-issuer-cn"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.o;:"/>
                             <label id="security-cert-issuer-o"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.ou;:"/>
                             <label id="security-cert-issuer-ou"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.periodofvalidity.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.begins;:"/>
                             <label id="security-cert-validity-begins"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.expires;:"/>
                             <label id="security-cert-validity-expires"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
                         <vbox class="tabpanel-summary-container">
                           <label class="plain tabpanel-summary-label"
                                  value="&certmgr.fingerprints.label;" flex="1"/>
                         </vbox>
                         <vbox class="security-info-section">
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.sha256fingerprint;:"/>
                             <label id="security-cert-sha256-fingerprint"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                           <hbox class="tabpanel-summary-container"
-                                align="center">
+                                align="baseline">
                             <label class="plain tabpanel-summary-label"
                                    value="&certmgr.certdetail.sha1fingerprint;:"/>
                             <label id="security-cert-sha1-fingerprint"
                                    class="plain tabpanel-summary-value devtools-monospace"
                                    crop="end"
                                    flex="1"/>
                           </hbox>
                         </vbox>
--- a/browser/devtools/performance/modules/compatibility.js
+++ b/browser/devtools/performance/modules/compatibility.js
@@ -76,17 +76,21 @@ function createMockAllocations () {
  */
 function memoryActorSupported (target) {
   // This `target` property is used only in tests to test
   // instances where the memory actor is not available.
   if (target.TEST_MOCK_MEMORY_ACTOR) {
     return false;
   }
 
-  return !!target.getTrait("memoryActorAllocations");
+  // We need to test that both the root actor has `memoryActorAllocations`,
+  // which is in Gecko 38+, and also that the target has a memory actor. There
+  // are scenarios, like addon debugging, where memoryActorAllocations is available,
+  // but no memory actor (like addon debugging in Gecko 38+)
+  return !!target.getTrait("memoryActorAllocations") && target.hasActor("memory");
 }
 exports.memoryActorSupported = Task.async(memoryActorSupported);
 
 /**
  * Takes a TabTarget, and checks existence of a TimelineActor on
  * the server, or if TEST_MOCK_TIMELINE_ACTOR is to be used.
  *
  * @param {TabTarget} target
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -35,17 +35,17 @@ support-files =
 [browser_perf-front-profiler-03.js]
 [browser_perf-front-profiler-04.js]
 #[browser_perf-front-profiler-05.js] bug 1077464
 #[browser_perf-front-profiler-06.js]
 [browser_perf-front.js]
 [browser_perf-jump-to-debugger-01.js]
 [browser_perf-jump-to-debugger-02.js]
 [browser_perf-options-01.js]
-[browser_perf-options-02.js]
+# [browser_perf-options-02.js] bug 1133230
 [browser_perf-options-invert-call-tree-01.js]
 [browser_perf-options-invert-call-tree-02.js]
 [browser_perf-options-invert-flame-graph-01.js]
 [browser_perf-options-invert-flame-graph-02.js]
 [browser_perf-options-flatten-tree-recursion-01.js]
 [browser_perf-options-flatten-tree-recursion-02.js]
 [browser_perf-options-show-platform-data-01.js]
 [browser_perf-options-show-platform-data-02.js]
--- a/browser/devtools/performance/test/browser_perf-compatibility-02.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-02.js
@@ -1,33 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and the correct views are shown.
  */
 
 const WAIT_TIME = 1000;
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true,
     TEST_MOCK_TIMELINE_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
-  let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+  let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, JsCallTreeView } = panel.panelWin;
 
   let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
   ok(memoryMock, "memory should be mocked.");
   ok(timelineMock, "timeline should be mocked.");
 
-  yield startRecording(panel);
+  yield startRecording(panel, { waitForOverview: false });
   busyWait(WAIT_TIME); // allow the profiler module to sample some cpu activity
-  yield stopRecording(panel);
+  yield stopRecording(panel, { waitForOverview: false });
 
   let {
     label, duration, markers, frames, memory, ticks, allocations, profile
   } = PerformanceController.getCurrentRecording().getAllData();
 
   is(label, "", "Empty label for mock.");
   is(typeof duration, "number", "duration is a number");
   ok(duration > 0, "duration is not 0");
@@ -53,16 +53,33 @@ let test = Task.async(function*() {
         ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
+  is($("#overview-pane").hidden, true,
+    "overview pane hidden when timeline mocked.");
+
+  is($("#select-waterfall-view").hidden, true,
+    "waterfall view button hidden when timeline mocked");
+  is($("#select-js-calltree-view").hidden, false,
+    "jscalltree view button not hidden when timeline/memory mocked");
+  is($("#select-js-flamegraph-view").hidden, true,
+    "jsflamegraph view button hidden when timeline mocked");
+  is($("#select-memory-calltree-view").hidden, true,
+    "memorycalltree view button hidden when memory mocked");
+  is($("#select-memory-flamegraph-view").hidden, true,
+    "memoryflamegraph view button hidden when memory mocked");
+
+  ok(DetailsView.isViewSelected(JsCallTreeView),
+    "JS Call Tree view selected by default when timeline/memory mocked.");
+
   yield teardown(panel);
   finish();
 });
 
 function isEmptyArray (array, name) {
   ok(Array.isArray(array), `${name} is an array`);
   is(array.length, 0, `${name} is empty`);
 }
--- a/browser/devtools/performance/test/browser_perf-compatibility-04.js
+++ b/browser/devtools/performance/test/browser_perf-compatibility-04.js
@@ -1,24 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that the recording model is populated correctly when using timeline
- * and memory actor mocks.
+ * and memory actor mocks, and that the correct button/overview displays are shown.
  */
 
 const WAIT_TIME = 1000;
 
 let test = Task.async(function*() {
   let { target, panel, toolbox } = yield initPerformance(SIMPLE_URL, "performance", {
     TEST_MOCK_MEMORY_ACTOR: true
   });
   Services.prefs.setBoolPref(MEMORY_PREF, true);
-  let { EVENTS, gFront, PerformanceController, PerformanceView } = panel.panelWin;
+  let { EVENTS, $, gFront, PerformanceController, PerformanceView, DetailsView, WaterfallView } = panel.panelWin;
 
 
   let { memory: memoryMock, timeline: timelineMock } = gFront.getMocksInUse();
   ok(memoryMock, "memory should be mocked.");
   ok(!timelineMock, "timeline should not be mocked.");
 
   yield startRecording(panel);
   yield busyWait(100);
@@ -52,16 +52,33 @@ let test = Task.async(function*() {
         ok(false, "The sample " + sample.toSource() + " doesn't have a root node.");
       }
     }
   }
 
   ok(sampleCount > 0,
     "At least some samples have been iterated over, checking for root nodes.");
 
+  is($("#overview-pane").hidden, false,
+    "overview pane not hidden when only memory mocked.");
+
+  is($("#select-waterfall-view").hidden, false,
+    "waterfall view button not hidden when memory mocked");
+  is($("#select-js-calltree-view").hidden, false,
+    "jscalltree view button not hidden when memory mocked");
+  is($("#select-js-flamegraph-view").hidden, false,
+    "jsflamegraph view button not hidden when memory mocked");
+  is($("#select-memory-calltree-view").hidden, true,
+    "memorycalltree view button hidden when memory mocked");
+  is($("#select-memory-flamegraph-view").hidden, true,
+    "memoryflamegraph view button hidden when memory mocked");
+
+  ok(DetailsView.isViewSelected(WaterfallView),
+    "Waterfall view selected by default when memory mocked.");
+
   yield teardown(panel);
   finish();
 });
 
 function isEmptyArray (array, name) {
   ok(Array.isArray(array), `${name} is an array`);
   is(array.length, 0, `${name} is empty`);
 }
--- a/browser/devtools/performance/test/head.js
+++ b/browser/devtools/performance/test/head.js
@@ -261,17 +261,17 @@ function command (button) {
 function click (win, button) {
   EventUtils.sendMouseEvent({ type: "click" }, button, win);
 }
 
 function mousedown (win, button) {
   EventUtils.sendMouseEvent({ type: "mousedown" }, button, win);
 }
 
-function* startRecording(panel) {
+function* startRecording(panel, options={}) {
   let win = panel.panelWin;
   let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_START_RECORDING);
   let willStart = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_START);
   let hasStarted = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STARTED);
   let button = win.$("#main-record-button");
 
   ok(!button.hasAttribute("checked"),
     "The record button should not be checked yet.");
@@ -285,31 +285,31 @@ function* startRecording(panel) {
     "The record button should now be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
   yield willStart;
   let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
 
   yield hasStarted;
-  let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+  let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
 
   yield stateChanged;
   yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recording",
     "The current state is 'recording'.");
 
   ok(button.hasAttribute("checked"),
     "The record button should still be checked.");
   ok(!button.hasAttribute("locked"),
     "The record button should not be locked.");
 }
 
-function* stopRecording(panel) {
+function* stopRecording(panel, options={}) {
   let win = panel.panelWin;
   let clicked = panel.panelWin.PerformanceView.once(win.EVENTS.UI_STOP_RECORDING);
   let willStop = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_WILL_STOP);
   let hasStopped = panel.panelWin.PerformanceController.once(win.EVENTS.RECORDING_STOPPED);
   let button = win.$("#main-record-button");
 
   ok(button.hasAttribute("checked"),
     "The record button should already be checked.");
@@ -323,17 +323,17 @@ function* stopRecording(panel) {
     "The record button should not be checked.");
   ok(button.hasAttribute("locked"),
     "The record button should be locked.");
 
   yield willStop;
   let stateChanged = once(win.PerformanceView, win.EVENTS.UI_STATE_CHANGED);
 
   yield hasStopped;
-  let overviewRendered = once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED);
+  let overviewRendered = options.waitForOverview ? once(win.OverviewView, win.EVENTS.OVERVIEW_RENDERED) : Promise.resolve();
 
   yield stateChanged;
   yield overviewRendered;
 
   is(win.PerformanceView.getState(), "recorded",
     "The current state is 'recorded'.");
 
   ok(!button.hasAttribute("checked"),
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -1,29 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const DEFAULT_DETAILS_SUBVIEW = "waterfall";
-
 /**
  * Details view containing profiler call tree and markers waterfall. Manages
  * subviews and toggles visibility between them.
  */
 let DetailsView = {
   /**
    * Name to node+object mapping of subviews.
    */
   components: {
-    "waterfall": { id: "waterfall-view", view: WaterfallView },
+    "waterfall": { id: "waterfall-view", view: WaterfallView, requires: ["timeline"] },
     "js-calltree": { id: "js-calltree-view", view: JsCallTreeView },
-    "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView },
-    "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, pref: "enable-memory" },
-    "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, pref: "enable-memory" }
+    "js-flamegraph": { id: "js-flamegraph-view", view: JsFlameGraphView, requires: ["timeline"] },
+    "memory-calltree": { id: "memory-calltree-view", view: MemoryCallTreeView, requires: ["memory"], pref: "enable-memory" },
+    "memory-flamegraph": { id: "memory-flamegraph-view", view: MemoryFlameGraphView, requires: ["memory", "timeline"], pref: "enable-memory" }
   },
 
   /**
    * Sets up the view with event binding, initializes subviews.
    */
   initialize: Task.async(function *() {
     this.el = $("#details-pane");
     this.toolbar = $("#performance-toolbar-controls-detail-views");
@@ -31,17 +29,17 @@ let DetailsView = {
     this._onViewToggle = this._onViewToggle.bind(this);
     this._onRecordingStoppedOrSelected = this._onRecordingStoppedOrSelected.bind(this);
     this.setAvailableViews = this.setAvailableViews.bind(this);
 
     for (let button of $$("toolbarbutton[data-view]", this.toolbar)) {
       button.addEventListener("command", this._onViewToggle);
     }
 
-    yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+    yield this.selectDefaultView();
     yield this.setAvailableViews();
 
     PerformanceController.on(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.on(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
   /**
@@ -57,32 +55,37 @@ let DetailsView = {
     }
 
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingStoppedOrSelected);
     PerformanceController.off(EVENTS.PREF_CHANGED, this.setAvailableViews);
   }),
 
   /**
-   * Sets the possible views based off of prefs by hiding/showing the
+   * Sets the possible views based off of prefs and server actor support by hiding/showing the
    * buttons that select them and going to default view if currently selected.
    * Called when a preference changes in `devtools.performance.ui.`.
    */
   setAvailableViews: Task.async(function* () {
-    for (let [name, { view, pref }] of Iterator(this.components)) {
+    let mocks = gFront.getMocksInUse();
+    for (let [name, { view, pref, requires }] of Iterator(this.components)) {
       let recording = PerformanceController.getCurrentRecording();
 
       let isRecorded = recording && !recording.isRecording();
+      // View is enabled view prefs
       let isEnabled = !pref || PerformanceController.getPref(pref);
-      $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !isEnabled;
+      // View is supported by the server actor, and the requried actor is not being mocked
+      let isSupported = !requires || requires.every(r => !mocks[r]);
+
+      $(`toolbarbutton[data-view=${name}]`).hidden = !isRecorded || !(isEnabled && isSupported);
 
       // If the view is currently selected and not enabled, go back to the
       // default view.
       if (!isEnabled && this.isViewSelected(view)) {
-        yield this.selectView(DEFAULT_DETAILS_SUBVIEW);
+        yield this.selectDefaultView();
       }
     }
   }),
 
   /**
    * Select one of the DetailView's subviews to be rendered,
    * hiding the others.
    *
@@ -102,16 +105,32 @@ let DetailsView = {
         button.removeAttribute("checked");
       }
     }
 
     this.emit(EVENTS.DETAILS_VIEW_SELECTED, viewName);
   }),
 
   /**
+   * Selects a default view based off of protocol support
+   * and preferences enabled.
+   */
+  selectDefaultView: function () {
+    let { timeline: mockTimeline } = gFront.getMocksInUse();
+    // If timelines are mocked, the first view available is the js-calltree.
+    if (mockTimeline) {
+      return this.selectView("js-calltree");
+    } else {
+      // In every other scenario with preferences and mocks, waterfall will
+      // be the default view.
+      return this.selectView("waterfall");
+    }
+  },
+
+  /**
    * Checks if the provided view is currently selected.
    *
    * @param object viewObject
    * @return boolean
    */
   isViewSelected: function(viewObject) {
     let selectedPanel = this.el.selectedPanel;
     let selectedId = selectedPanel.id;
--- a/browser/devtools/performance/views/overview.js
+++ b/browser/devtools/performance/views/overview.js
@@ -21,16 +21,19 @@ const MEMORY_GRAPH_HEIGHT = 30; // px
  * View handler for the overview panel's time view, displaying
  * framerate, markers and memory over time.
  */
 let OverviewView = {
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
+    if (gFront.getMocksInUse().timeline) {
+      this.disable();
+    }
     this._onRecordingWillStart = this._onRecordingWillStart.bind(this);
     this._onRecordingStarted = this._onRecordingStarted.bind(this);
     this._onRecordingWillStop = this._onRecordingWillStop.bind(this);
     this._onRecordingStopped = this._onRecordingStopped.bind(this);
     this._onRecordingSelected = this._onRecordingSelected.bind(this);
     this._onRecordingTick = this._onRecordingTick.bind(this);
     this._onGraphSelecting = this._onGraphSelecting.bind(this);
     this._onPrefChanged = this._onPrefChanged.bind(this);
@@ -56,42 +59,70 @@ let OverviewView = {
     PerformanceController.off(EVENTS.RECORDING_WILL_START, this._onRecordingWillStart);
     PerformanceController.off(EVENTS.RECORDING_STARTED, this._onRecordingStarted);
     PerformanceController.off(EVENTS.RECORDING_WILL_STOP, this._onRecordingWillStop);
     PerformanceController.off(EVENTS.RECORDING_STOPPED, this._onRecordingStopped);
     PerformanceController.off(EVENTS.RECORDING_SELECTED, this._onRecordingSelected);
   },
 
   /**
+   * Disabled in the event we're using a Timeline mock, so we'll have no
+   * markers, ticks or memory data to show, so just block rendering and hide
+   * the panel.
+   */
+  disable: function () {
+    this._disabled = true;
+    $("#overview-pane").hidden = true;
+  },
+
+  /**
+   * Returns the disabled status.
+   *
+   * @return boolean
+   */
+  isDisabled: function () {
+    return this._disabled;
+  },
+
+  /**
    * Sets the time interval selection for all graphs in this overview.
    *
    * @param object interval
    *        The { starTime, 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.");
     }
     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 { starTime, endTime }, in milliseconds.
+   *         The { startTime, endTime }, in milliseconds.
    */
   getTimeInterval: function() {
     let recording = PerformanceController.getCurrentRecording();
+
+    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 };
   },
@@ -167,16 +198,19 @@ let OverviewView = {
 
   /**
    * Method for handling all the set up for rendering the overview graphs.
    *
    * @param number resolution
    *        The fps graph resolution. @see Graphs.jsm
    */
   render: Task.async(function *(resolution) {
+    if (this.isDisabled()) {
+      return;
+    }
     let recording = PerformanceController.getCurrentRecording();
     let duration = recording.getDuration();
     let markers = recording.getMarkers();
     let memory = recording.getMemory();
     let timestamps = recording.getTicks();
 
     // Empty or older recordings might yield no markers, memory or timestamps.
     if (markers && (yield this._markersGraphAvailable())) {
--- a/browser/devtools/webide/modules/runtimes.js
+++ b/browser/devtools/webide/modules/runtimes.js
@@ -1,18 +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/. */
 
 const {Cu, Ci} = require("chrome");
 const {Devices} = Cu.import("resource://gre/modules/devtools/Devices.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
-const {Simulator} = Cu.import("resource://gre/modules/devtools/Simulator.jsm");
-const {ConnectionManager, Connection} = require("devtools/client/connection-manager");
+const {Connection} = require("devtools/client/connection-manager");
 const {DebuggerServer} = require("resource://gre/modules/devtools/dbg-server.jsm");
+const {Simulators} = require("devtools/webide/simulators");
 const discovery = require("devtools/toolkit/discovery/discovery");
 const EventEmitter = require("devtools/toolkit/event-emitter");
 const promise = require("promise");
 loader.lazyRequireGetter(this, "AuthenticationResult",
   "devtools/toolkit/security/auth", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/toolkit/DevToolsUtils");
 
@@ -188,36 +188,36 @@ exports.RuntimeScanners = RuntimeScanner
 /* SCANNERS */
 
 let SimulatorScanner = {
 
   _runtimes: [],
 
   enable() {
     this._updateRuntimes = this._updateRuntimes.bind(this);
-    Simulator.on("register", this._updateRuntimes);
-    Simulator.on("unregister", this._updateRuntimes);
+    Simulators.on("updated", this._updateRuntimes);
     this._updateRuntimes();
   },
 
   disable() {
-    Simulator.off("register", this._updateRuntimes);
-    Simulator.off("unregister", this._updateRuntimes);
+    Simulators.off("updated", this._updateRuntimes);
   },
 
   _emitUpdated() {
     this.emit("runtime-list-updated");
   },
 
   _updateRuntimes() {
-    this._runtimes = [];
-    for (let name of Simulator.availableNames()) {
-      this._runtimes.push(new SimulatorRuntime(name));
-    }
-    this._emitUpdated();
+    Simulators.getAll().then(simulators => {
+      this._runtimes = [];
+      for (let simulator of simulators) {
+        this._runtimes.push(new SimulatorRuntime(simulator));
+      }
+      this._emitUpdated();
+    });
   },
 
   scan() {
     return promise.resolve();
   },
 
   listRuntimes: function() {
     return this._runtimes;
@@ -537,38 +537,36 @@ WiFiRuntime.prototype = {
       }
     };
   }
 };
 
 // For testing use only
 exports._WiFiRuntime = WiFiRuntime;
 
-function SimulatorRuntime(name) {
-  this.name = name;
+function SimulatorRuntime(simulator) {
+  this.simulator = simulator;
 }
 
 SimulatorRuntime.prototype = {
   type: RuntimeTypes.SIMULATOR,
   connect: function(connection) {
-    let port = ConnectionManager.getFreeTCPPort();
-    let simulator = Simulator.getByName(this.name);
-    if (!simulator || !simulator.launch) {
-      return promise.reject(new Error("Can't find simulator: " + this.name));
-    }
-    return simulator.launch({port: port}).then(() => {
+    return this.simulator.launch().then(port => {
       connection.host = "localhost";
       connection.port = port;
       connection.keepConnecting = true;
-      connection.once(Connection.Events.DISCONNECTED, simulator.close);
+      connection.once(Connection.Events.DISCONNECTED, e => this.simulator.kill());
       connection.connect();
     });
   },
   get id() {
-    return this.name;
+    return this.simulator.id;
+  },
+  get name() {
+    return this.simulator.name;
   },
 };
 
 // For testing use only
 exports._SimulatorRuntime = SimulatorRuntime;
 
 let gLocalRuntime = {
   type: RuntimeTypes.LOCAL,
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulator-process.js
@@ -0,0 +1,272 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+'use strict';
+
+const { Cc, Ci, Cu } = require("chrome");
+
+const Environment = require("sdk/system/environment").env;
+const Subprocess = require("sdk/system/child_process/subprocess");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let platform = Services.appShell.hiddenDOMWindow.navigator.platform;
+let OS = "";
+if (platform.indexOf("Win") != -1) {
+  OS = "win32";
+} else if (platform.indexOf("Mac") != -1) {
+  OS = "mac64";
+} else if (platform.indexOf("Linux") != -1) {
+  if (platform.indexOf("x86_64") != -1) {
+    OS = "linux64";
+  } else {
+    OS = "linux32";
+  }
+}
+
+function SimulatorProcess() {}
+SimulatorProcess.prototype = {
+
+  // Check if B2G is running.
+  get isRunning() !!this.process,
+
+  // Start the process and connect the debugger client.
+  run() {
+
+    // Resolve B2G binary.
+    let b2g = this.b2gBinary;
+    if (!b2g || !b2g.exists()) {
+      throw Error("B2G executable not found.");
+    }
+
+    this.once("stdout", function () {
+      if (OS == "mac64") {
+        console.debug("WORKAROUND run osascript to show b2g-desktop window on OS=='mac64'");
+        // Escape double quotes and escape characters for use in AppleScript.
+        let path = b2g.path.replace(/\\/g, "\\\\").replace(/\"/g, '\\"');
+
+        Subprocess.call({
+          command: "/usr/bin/osascript",
+          arguments: ["-e", 'tell application "' + path + '" to activate'],
+        });
+      }
+    });
+
+    this.on("stdout", (e, data) => this.log(e, data.trim()));
+    this.on("stderr", (e, data) => this.log(e, data.trim()));
+
+    let environment;
+    if (OS.indexOf("linux") > -1) {
+      environment = ["TMPDIR=" + Services.dirsvc.get("TmpD", Ci.nsIFile).path];
+      if ("DISPLAY" in Environment) {
+        environment.push("DISPLAY=" + Environment.DISPLAY);
+      }
+    }
+
+    // Spawn a B2G instance.
+    this.process = Subprocess.call({
+      command: b2g,
+      arguments: this.args,
+      environment: environment,
+      stdout: data => this.emit("stdout", data),
+      stderr: data => this.emit("stderr", data),
+      // On B2G instance exit, reset tracked process, remote debugger port and
+      // shuttingDown flag, then finally emit an exit event.
+      done: result => {
+        console.log("B2G terminated with " + result.exitCode);
+        this.process = null;
+        this.emit("exit", result.exitCode);
+      }
+    });
+  },
+
+  // Request a B2G instance kill.
+  kill() {
+    let deferred = promise.defer();
+    if (this.process) {
+      this.once("exit", (e, exitCode) => {
+        this.shuttingDown = false;
+        deferred.resolve(exitCode);
+      });
+      if (!this.shuttingDown) {
+        this.shuttingDown = true;
+        this.emit("kill", null);
+        this.process.kill();
+      }
+      return deferred.promise;
+    } else {
+      return promise.resolve(undefined);
+    }
+  },
+
+  // Maybe log output messages.
+  log(level, message) {
+    if (!Services.prefs.getBoolPref("devtools.webide.logSimulatorOutput")) {
+      return;
+    }
+    if (level === "stderr" || level === "error") {
+      console.error(message);
+      return;
+    }
+    console.log(message);
+  },
+
+  // Compute B2G CLI arguments.
+  get args() {
+    let args = [];
+
+    let gaia = this.gaiaProfile;
+    if (!gaia || !gaia.exists()) {
+      throw Error("Gaia profile directory not found.");
+    }
+    args.push("-profile", gaia.path);
+
+    args.push("-start-debugger-server", "" + this.options.port);
+
+    // Ignore eventual zombie instances of b2g that are left over.
+    args.push("-no-remote");
+
+    return args;
+  },
+};
+
+EventEmitter.decorate(SimulatorProcess.prototype);
+
+
+function CustomSimulatorProcess(options) {
+  this.options = options;
+}
+
+let CSPp = CustomSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(CSPp, "b2gBinary", {
+  get: function() {
+    let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.options.b2gBinary);
+    return file;
+  }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(CSPp, "gaiaProfile", {
+  get: function() {
+    let file = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+    file.initWithPath(this.options.gaiaProfile);
+    return file;
+  }
+});
+
+exports.CustomSimulatorProcess = CustomSimulatorProcess;
+
+
+function AddonSimulatorProcess(addon, options) {
+  this.addon = addon;
+  this.options = options;
+}
+
+let ASPp = AddonSimulatorProcess.prototype = Object.create(SimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(ASPp, "b2gBinary", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".customRuntime";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      file.append("b2g");
+      let binaries = {
+        win32: "b2g-bin.exe",
+        mac64: "B2G.app/Contents/MacOS/b2g-bin",
+        linux32: "b2g-bin",
+        linux64: "b2g-bin",
+      };
+      binaries[OS].split("/").forEach(node => file.append(node));
+    }
+    return file;
+  }
+});
+
+// Compute Gaia profile file handle.
+Object.defineProperty(ASPp, "gaiaProfile", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".gaiaProfile";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      file.append("profile");
+    }
+    return file;
+  }
+});
+
+exports.AddonSimulatorProcess = AddonSimulatorProcess;
+
+
+function OldAddonSimulatorProcess(addon, options) {
+  this.addon = addon;
+  this.options = options;
+}
+
+let OASPp = OldAddonSimulatorProcess.prototype = Object.create(AddonSimulatorProcess.prototype);
+
+// Compute B2G binary file handle.
+Object.defineProperty(OASPp, "b2gBinary", {
+  get: function() {
+    let file;
+    try {
+      let pref = "extensions." + this.addon.id + ".customRuntime";
+      file = Services.prefs.getComplexValue(pref, Ci.nsIFile);
+    } catch(e) {}
+
+    if (!file) {
+      file = this.addon.getResourceURI().QueryInterface(Ci.nsIFileURL).file;
+      let version = this.addon.name.match(/\d+\.\d+/)[0].replace(/\./, "_");
+      file.append("resources");
+      file.append("fxos_" + version + "_simulator");
+      file.append("data");
+      file.append(OS == "linux32" ? "linux" : OS);
+      let binaries = {
+        win32: "b2g/b2g-bin.exe",
+        mac64: "B2G.app/Contents/MacOS/b2g-bin",
+        linux32: "b2g/b2g-bin",
+        linux64: "b2g/b2g-bin",
+      };
+      binaries[OS].split("/").forEach(node => file.append(node));
+    }
+    return file;
+  }
+});
+
+// Compute B2G CLI arguments.
+Object.defineProperty(OASPp, "args", {
+  get: function() {
+    let args = [];
+
+    let gaia = this.gaiaProfile;
+    if (!gaia || !gaia.exists()) {
+      throw Error("Gaia profile directory not found.");
+    }
+    args.push("-profile", gaia.path);
+
+    args.push("-dbgport", "" + this.options.port);
+
+    // Ignore eventual zombie instances of b2g that are left over.
+    args.push("-no-remote");
+
+    return args;
+  }
+});
+
+exports.OldAddonSimulatorProcess = OldAddonSimulatorProcess;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webide/modules/simulators.js
@@ -0,0 +1,96 @@
+/* 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/. */
+
+const { Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm");
+const { EventEmitter } = Cu.import("resource://gre/modules/devtools/event-emitter.js");
+const { ConnectionManager } = require("devtools/client/connection-manager");
+const { AddonSimulatorProcess, OldAddonSimulatorProcess } = require("devtools/webide/simulator-process");
+const promise = require("promise");
+
+const SimulatorRegExp = new RegExp(Services.prefs.getCharPref("devtools.webide.simulatorAddonRegExp"));
+
+let Simulators = {
+  // TODO (Bug 1090949) Don't generate this list from installed simulator
+  // addons, but instead implement a persistent list of user-configured
+  // simulators.
+  getAll() {
+    let deferred = promise.defer();
+    AddonManager.getAllAddons(addons => {
+      let simulators = [];
+      for (let addon of addons) {
+        if (SimulatorRegExp.exec(addon.id)) {
+          simulators.push(new Simulator(addon));
+        }
+      }
+      // Sort simulators alphabetically by name.
+      simulators.sort((a, b) => {
+        return a.name.toLowerCase().localeCompare(b.name.toLowerCase())
+      });
+      deferred.resolve(simulators);
+    });
+    return deferred.promise;
+  },
+}
+EventEmitter.decorate(Simulators);
+exports.Simulators = Simulators;
+
+function update() {
+  Simulators.emit("updated");
+}
+AddonManager.addAddonListener({
+  onEnabled: update,
+  onDisabled: update,
+  onInstalled: update,
+  onUninstalled: update
+});
+
+
+function Simulator(addon) {
+  this.addon = addon;
+}
+
+Simulator.prototype = {
+  launch() {
+    // Close already opened simulation.
+    if (this.process) {
+      return this.kill().then(this.launch.bind(this));
+    }
+
+    let options = {
+      port: ConnectionManager.getFreeTCPPort()
+    };
+
+    if (this.version <= "1.3") {
+      // Support older simulator addons.
+      this.process = new OldAddonSimulatorProcess(this.addon, options);
+    } else {
+      this.process = new AddonSimulatorProcess(this.addon, options);
+    }
+    this.process.run();
+
+    return promise.resolve(options.port);
+  },
+
+  kill() {
+    let process = this.process;
+    if (!process) {
+      return promise.resolve();
+    }
+    this.process = null;
+    return process.kill();
+  },
+
+  get id() {
+    return this.addon.id;
+  },
+
+  get name() {
+    return this.addon.name.replace(" Simulator", "");
+  },
+
+  get version() {
+    return this.name.match(/\d+\.\d+/)[0];
+  },
+};
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -15,16 +15,18 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
 
 EXTRA_JS_MODULES.devtools.webide += [
     'modules/addons.js',
     'modules/app-manager.js',
     'modules/build.js',
     'modules/config-view.js',
     'modules/remote-resources.js',
     'modules/runtimes.js',
+    'modules/simulator-process.js',
+    'modules/simulators.js',
     'modules/tab-store.js',
     'modules/utils.js'
 ]
 
 JS_PREFERENCE_FILES += [
     'webide-prefs.js',
 ]
 
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -8,23 +8,25 @@ pref("devtools.webide.templatesURL", "ht
 pref("devtools.webide.autoinstallADBHelper", true);
 pref("devtools.webide.autoinstallFxdtAdapters", true);
 pref("devtools.webide.autoConnectRuntime", true);
 pref("devtools.webide.restoreLastProject", true);
 pref("devtools.webide.enableLocalRuntime", false);
 pref("devtools.webide.addonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/index.json");
 pref("devtools.webide.simulatorAddonsURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/#VERSION#/#OS#/fxos_#SLASHED_VERSION#_simulator-#OS#-latest.xpi");
 pref("devtools.webide.simulatorAddonID", "fxos_#SLASHED_VERSION#_simulator@mozilla.org");
+pref("devtools.webide.simulatorAddonRegExp", "fxos_(.*)_simulator@mozilla\.org$");
 pref("devtools.webide.adbAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxos-simulator/adb-helper/#OS#/adbhelper-#OS#-latest.xpi");
 pref("devtools.webide.adbAddonID", "adbhelper@mozilla.org");
 pref("devtools.webide.adaptersAddonURL", "https://ftp.mozilla.org/pub/mozilla.org/labs/fxdt-adapters/#OS#/fxdt-adapters-#OS#-latest.xpi");
 pref("devtools.webide.adaptersAddonID", "fxdevtools-adapters@mozilla.org");
 pref("devtools.webide.monitorWebSocketURL", "ws://localhost:9000");
 pref("devtools.webide.lastConnectedRuntime", "");
 pref("devtools.webide.lastSelectedProject", "");
+pref("devtools.webide.logSimulatorOutput", false);
 pref("devtools.webide.widget.autoinstall", true);
 #ifdef MOZ_DEV_EDITION
 pref("devtools.webide.widget.enabled", true);
 pref("devtools.webide.widget.inNavbarByDefault", true);
 #else
 pref("devtools.webide.widget.enabled", false);
 pref("devtools.webide.widget.inNavbarByDefault", false);
 #endif
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -832,8 +832,11 @@ just addresses the organization to follo
 <!ENTITY processHang.terminateScript.label        "Stop Script">
 <!ENTITY processHang.terminateScript.accessKey    "S">
 <!ENTITY processHang.debugScript.label            "Debug Script">
 <!ENTITY processHang.debugScript.accessKey        "D">
 <!ENTITY processHang.terminatePlugin.label        "Kill Plugin">
 <!ENTITY processHang.terminatePlugin.accessKey    "P">
 <!ENTITY processHang.terminateProcess.label       "Kill Web Process">
 <!ENTITY processHang.terminateProcess.accessKey   "K">
+
+<!ENTITY emeLearnMoreContextMenu.label            "Learn more about DRM…">
+<!ENTITY emeLearnMoreContextMenu.accesskey        "D">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -35,16 +35,27 @@ xpinstallDisabledButton.accesskey=n
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # Also see https://bugzilla.mozilla.org/show_bug.cgi?id=570012 for mockups
 addonDownloading=Add-on downloading;Add-ons downloading
 addonDownloadCancelled=Add-on download cancelled.;Add-on downloads cancelled.
 addonDownloadRestart=Restart Download;Restart Downloads
 addonDownloadRestart.accessKey=R
 addonDownloadCancelTooltip=Cancel
 
+addonwatch.slow=%S might be making %S run slowly
+addonwatch.disable.label=Disable %S
+addonwatch.disable.accesskey=D
+addonwatch.ignoreSession.label=Ignore for now
+addonwatch.ignoreSession.accesskey=I
+addonwatch.ignorePerm.label=Ignore permanently
+addonwatch.ignorePerm.accesskey=p
+addonwatch.restart.message=To disable %S you must restart %S
+addonwatch.restart.label=Restart %s
+addonwatch.restart.accesskey=R
+
 # LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
 # Semicolon-separated list of plural forms. See:
 # http://developer.mozilla.org/en/docs/Localization_and_Plurals
 # #1 first add-on's name, #2 number of add-ons, #3 application name
 addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
 addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
 addonInstallRestartButton=Restart Now
 addonInstallRestartButton.accesskey=R
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -664,16 +664,17 @@ this.BrowserUITelemetry = {
     "keywordfield", "searchselect", "shareselect", "frame", "showonlythisframe",
     "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
     "printframe", "viewframesource", "viewframeinfo",
     "viewpartialsource-selection", "viewpartialsource-mathml",
     "viewsource", "viewinfo", "spell-check-enabled",
     "spell-add-dictionaries-main", "spell-dictionaries",
     "spell-dictionaries-menu", "spell-add-dictionaries",
     "bidi-text-direction-toggle", "bidi-page-direction-toggle", "inspect",
+    "media-eme-learn-more"
   ]),
 
   _contextMenuInteractions: {},
 
   registerContextMenuInteraction: function(keys, itemID) {
     if (itemID) {
       if (!this._contextMenuItemWhitelist.has(itemID)) {
         itemID = "other-item";
--- a/browser/themes/shared/aboutSessionRestore.css
+++ b/browser/themes/shared/aboutSessionRestore.css
@@ -6,17 +6,17 @@
 
 .title {
   background-image: url("chrome://browser/skin/session-restore.svg");
 }
 
 treechildren::-moz-tree-image(icon),
 treechildren::-moz-tree-image(noicon) {
   padding-right: 2px;
-  margin: 0px 2px;
+  margin: 0 2px;
   width: 16px;
   height: 16px;
 }
 
 treechildren::-moz-tree-image(noicon) {
   list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
 }
 treechildren::-moz-tree-image(container, noicon) {
--- a/browser/themes/shared/aboutWelcomeBack.css
+++ b/browser/themes/shared/aboutWelcomeBack.css
@@ -15,8 +15,37 @@
 */
 #tabList {
   display: none;
 }
 
 #tabList[available] {
   display: -moz-box;
 }
+
+treechildren::-moz-tree-image(icon),
+treechildren::-moz-tree-image(noicon) {
+  padding-right: 2px;
+  margin: 0 2px;
+  width: 16px;
+  height: 16px;
+}
+
+treechildren::-moz-tree-image(noicon) {
+  list-style-image: url("chrome://mozapps/skin/places/defaultFavicon.png");
+}
+treechildren::-moz-tree-image(container, noicon) {
+  list-style-image: url("chrome://browser/skin/aboutSessionRestore-window-icon.png");
+}
+
+treechildren::-moz-tree-image(checked) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check");
+}
+treechildren::-moz-tree-image(checked, selected) {
+  list-style-image: url("chrome://global/skin/in-content/check.svg#check-inverted");
+}
+
+treechildren::-moz-tree-image(partial) {
+  list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial");
+}
+treechildren::-moz-tree-image(partial, selected) {
+  list-style-image: url("chrome://global/skin/in-content/check-partial.svg#check-partial-inverted");
+}
\ No newline at end of file
--- a/browser/themes/shared/contextmenu.inc.css
+++ b/browser/themes/shared/contextmenu.inc.css
@@ -74,8 +74,12 @@
   transform: scaleX(-1);
 }
 
 #context-navigation > .menuitem-iconic > .menu-iconic-left > .menu-iconic-icon {
   width: 16px;
   height: 16px;
   margin: 7px;
 }
+
+#context-media-eme-learnmore {
+  list-style-image: url("chrome://browser/skin/drm-icon.svg#chains");
+}
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -345,16 +345,21 @@ description > html|a {
 
 #dialogFrame {
   -moz-box-flex: 1;
   /* Default dialog dimensions */
   height: 20em;
   width: 66ch;
 }
 
+.largeDialogContainer.doScroll {
+  overflow-y: auto;
+  -moz-box-flex: 1;
+}
+
 /**
  * End Dialog
  */
 
 /**
  * Sync migration
  */
 #sync-migrate-upgrade-description {
--- a/browser/themes/shared/tabbrowser/crashed.svg
+++ b/browser/themes/shared/tabbrowser/crashed.svg
@@ -1,16 +1,11 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="22 22 16 16" enable-background="new 22 22 16 16" xml:space="preserve">
-<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
-	<stop  offset="0" style="stop-color:#E63B2E"/>
-	<stop  offset="1" style="stop-color:#C33931"/>
-</linearGradient>
-<circle fill="url(#SVGID_1_)" cx="30" cy="30" r="7"/>
-<g>
-	<path fill="#FFFFFF" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092
-		c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z
-		"/>
-</g>
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     viewBox="22 22 16 16">
+  <linearGradient id="gradient" gradientUnits="userSpaceOnUse" x1="30" y1="23" x2="30" y2="37">
+    <stop offset="0" style="stop-color: #e63b2e"/>
+    <stop offset="1" style="stop-color: #c33931"/>
+  </linearGradient>
+  <circle fill="url(#gradient)" cx="30" cy="30" r="7"/>
+  <path fill="#fff" d="M31.03,33.304c0,0.6-0.479,1.092-1.091,1.092c-0.6,0-1.079-0.492-1.079-1.092 c0-0.588,0.479-1.079,1.079-1.079C30.551,32.225,31.03,32.716,31.03,33.304z M29.171,31.133l-0.24-5.253h2.015l-0.24,5.253H29.171z"/>
 </svg>
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -275,22 +275,24 @@ pref("browser.search.suggest.prompted", 
 pref("browser.search.loadFromJars", true);
 pref("browser.search.jarURIs", "chrome://browser/locale/searchplugins/");
 
 // tell the search service that we don't really expose the "current engine"
 pref("browser.search.noCurrentEngine", true);
 
 // Control media casting & mirroring features
 pref("browser.casting.enabled", true);
-pref("browser.mirroring.enabled", true);
 #ifdef RELEASE_BUILD
 // Roku does not yet support mirroring in production
 pref("browser.mirroring.enabled.roku", false);
+// Chromecast mirroring is broken (bug 1131084)
+pref("browser.mirroring.enabled", false);
 #else
 pref("browser.mirroring.enabled.roku", true);
+pref("browser.mirroring.enabled", true);
 #endif
 
 // Enable sparse localization by setting a few package locale overrides
 pref("chrome.override_package.global", "browser");
 pref("chrome.override_package.mozapps", "browser");
 pref("chrome.override_package.passwordmgr", "browser");
 
 // enable xul error pages
--- a/mobile/android/base/tests/robocop.ini
+++ b/mobile/android/base/tests/robocop.ini
@@ -9,16 +9,17 @@ skip-if = android_version == "10"
 # disabled on 2.3; bug 941624, bug 1063509, bug 1073374, bug 1087221, bug 1088023, bug 1088027, bug 1090206
 skip-if = android_version == "10" || processor == "x86"
 [testAddSearchEngine]
 # disabled on Android 2.3; bug 979552
 skip-if = android_version == "10"
 [testAdobeFlash]
 skip-if = processor == "x86"
 [testANRReporter]
+[testAppConstants]
 [testAwesomebar]
 [testAxisLocking]
 # disabled on x86 only; bug 927476
 skip-if = processor == "x86"
 # [testBookmark] # see bug 915350
 [testBookmarksPanel]
 # disabled on 2.3; bug 979615
 skip-if = android_version == "10"
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.java
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.tests;
+
+public class testAppConstants extends JavascriptTest {
+    public testAppConstants() {
+        super("testAppConstants.js");
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/tests/testAppConstants.js
@@ -0,0 +1,15 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/AppConstants.jsm");
+
+add_task(function* testAppConstants() {
+  let packageName = AppConstants.ANDROID_PACKAGE_NAME
+  do_check_neq(packageName, "@ANDROID_PACKAGE_NAME@");
+  do_check_true(packageName.length > 0);
+});
+
+
+run_next_test();
--- a/mobile/android/chrome/content/FindHelper.js
+++ b/mobile/android/chrome/content/FindHelper.js
@@ -145,27 +145,14 @@ var FindHelper = {
           // this should never happen
           Cu.reportError("Warning: selected tab changed during find!");
           // fall through and restore viewport on the initial tab anyway
         }
         this._targetTab.setViewport(JSON.parse(this._initialViewport));
         this._targetTab.sendViewportUpdate();
       }
     } else {
-      // Defines the space around the highlighted element as a factor of the element's size.
-      const spacingFactor = 6;
-
-      // We replace the start of the zoom rect to keep the highlighted word in the middle.
-      // We divide this offset by two to consider a spacing on each side of the rect.
-      let x = aData.rect.x + (aData.rect.width * (1 - spacingFactor)) / 2;
-      let y = aData.rect.y + (aData.rect.height * (1 - spacingFactor)) / 2;
-
-      let rect = new Rect(Math.max(x, 0),
-                          Math.max(y, 0),
-                          // we use a bigger viewport than just the highlighted word
-                          aData.rect.width * spacingFactor,
-                          aData.rect.height * spacingFactor);
-
-      ZoomHelper.zoomToRect(rect);
+      // Disabled until bug 1014113 is fixed
+      // ZoomHelper.zoomToRect(aData.rect);
       this._viewportChanged = true;
     }
   }
 };
--- a/mobile/android/chrome/content/ZoomHelper.js
+++ b/mobile/android/chrome/content/ZoomHelper.js
@@ -118,21 +118,16 @@ var ZoomHelper = {
     }
   },
 
   /* Zoom to a specific part of the screen defined by a rect,
    * optionally keeping a particular part of it in view
    * if it is really tall.
    */
   zoomToRect: function(aRect, aClickY = -1) {
-    if(aRect.isEmpty()) {
-      // Protect from empty or negative-sized rects & potentials NaN in following calculations
-      return;
-    }
-
     let viewport = BrowserApp.selectedTab.getViewport();
 
     let rect = {
       x: aRect.x,
       y: aRect.y,
       w: aRect.width,
       h: Math.min(aRect.width * viewport.cssHeight / viewport.cssWidth, aRect.height)
     };
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4461,16 +4461,22 @@ pref("dom.mozSettings.SettingsManager.ve
 pref("dom.mozSettings.SettingsRequestManager.verbose.enabled", false);
 pref("dom.mozSettings.SettingsService.verbose.enabled", false);
 
 // Controlling whether we want to allow forcing some Settings
 // IndexedDB transactions to be opened as readonly or keep everything as
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
+// The interval at which to check for slow running addons
+pref("browser.addon-watch.interval", 120000);
+pref("browser.addon-watch.ignore", "[\"mochikit@mozilla.org\",\"special-powers@mozilla.org\"]");
+// the percentage of time addons are allowed to use without being labeled slow
+pref("browser.addon-watch.percentage-limit", 1);
+
 // RequestSync API is disabled by default.
 pref("dom.requestSync.enabled", false);
 
 // Search service settings
 pref("browser.search.log", false);
 pref("browser.search.update", true);
 pref("browser.search.update.log", false);
 pref("browser.search.update.interval", 21600);
--- a/toolkit/components/viewconfig/content/config.js
+++ b/toolkit/components/viewconfig/content/config.js
@@ -90,17 +90,17 @@ var view = {
         index = gPrefView.length - index - 1;
     }
     else {
       var pref = null;
       if (index >= 0)
         pref = gPrefView[index];
 
       var old = document.getElementById(gSortedColumn);
-      old.setAttribute("sortDirection", "");
+      old.removeAttribute("sortDirection");
       gPrefArray.sort(gSortFunction = gSortFunctions[col.id]);
       if (gPrefView != gPrefArray)
         gPrefView.sort(gSortFunction);
       gSortedColumn = col.id;
       if (pref)
         index = getViewIndexOfPref(pref);
     }
     col.element.setAttribute("sortDirection", gSortDirection > 0 ? "ascending" : "descending");
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/AddonWatcher.jsm
@@ -0,0 +1,83 @@
+// -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["AddonWatcher"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+let AddonWatcher = {
+  _lastAddonTime: {},
+  _timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer),
+  _callback: null,
+  _interval: 1500,
+  _ignoreList: null,
+  init: function(callback) {
+    if (!callback) {
+      return;
+    }
+
+    if (this._callback) {
+      return;
+    }
+
+    this._callback = callback;
+    try {
+      this._ignoreList = new Set(JSON.parse(Preferences.get("browser.addon-watch.ignore", null)));
+    } catch (ex) {
+      // probably some malformed JSON, ignore and carry on
+      this._ignoreList = new Set();
+    }
+    this._interval = Preferences.get("browser.addon-watch.interval", 15000);
+    this._timer.initWithCallback(this._checkAddons.bind(this), this._interval, Ci.nsITimer.TYPE_REPEATING_SLACK);
+  },
+  _checkAddons: function() {
+    let compartmentInfo = Cc["@mozilla.org/compartment-info;1"]
+      .getService(Ci.nsICompartmentInfo);
+    let compartments = compartmentInfo.getCompartments();
+    let count = compartments.length;
+    let addons = {};
+    for (let i = 0; i < count; i++) {
+      let compartment = compartments.queryElementAt(i, Ci.nsICompartment);
+      if (compartment.addonId) {
+        if (addons[compartment.addonId]) {
+          addons[compartment.addonId] += compartment.time;
+        } else {
+          addons[compartment.addonId] = compartment.time;
+        }
+      }
+    }
+    let limit = this._interval * Preferences.get("browser.addon-watch.percentage-limit", 75) * 10;
+    for (let addonId in addons) {
+      if (!this._ignoreList.has(addonId)) {
+        if (this._lastAddonTime[addonId] && (addons[addonId] - this._lastAddonTime[addonId]) > limit) {
+          this._callback(addonId);
+        }
+        this._lastAddonTime[addonId] = addons[addonId];
+      }
+    }
+  },
+  ignoreAddonForSession: function(addonid) {
+    this._ignoreList.add(addonid);
+  },
+  ignoreAddonPermanently: function(addonid) {
+    this._ignoreList.add(addonid);
+    try {
+      let ignoreList = JSON.parse(Preferences.get("browser.addon-watch.ignore", "[]"))
+      if (!ignoreList.includes(addonid)) {
+        ignoreList.push(addonid);
+        Preferences.set("browser.addon-watch.ignore", JSON.stringify(ignoreList));
+      }
+    } catch (ex) {
+      Preferences.set("browser.addon-watch.ignore", JSON.stringify([addonid]));
+    }
+  }
+};
--- a/toolkit/modules/ResetProfile.jsm
+++ b/toolkit/modules/ResetProfile.jsm
@@ -29,42 +29,16 @@ this.ResetProfile = {
         ("@mozilla.org/profile/migrator;1?app=" + MOZ_BUILD_APP + "&type=" + MOZ_APP_NAME in Cc);
     } catch (e) {
       // Catch exception when there is no selected profile.
       Cu.reportError(e);
     }
     return false;
   },
 
-  getMigratedData: function() {
-    Cu.import("resource:///modules/MigrationUtils.jsm");
-
-    // From migration.properties
-    const MIGRATED_TYPES = [
-      128,// Windows/Tabs
-      4,  // History and Bookmarks
-      16, // Passwords
-      8,  // Form History
-      2,  // Cookies
-    ];
-
-    // Loop over possible data to migrate to give the user a list of what will be preserved.
-    let dataTypes = [];
-    for (let itemID of MIGRATED_TYPES) {
-      try {
-        let typeName = MigrationUtils.getLocalizedString(itemID + "_" + MOZ_APP_NAME);
-        dataTypes.push(typeName);
-      } catch (x) {
-        // Catch exceptions when the string for a data type doesn't exist.
-        Cu.reportError(x);
-      }
-    }
-    return dataTypes;
-  },
-
   /**
    * Ask the user if they wish to restart the application to reset the profile.
    */
   openConfirmationDialog: function(window) {
     // Prompt the user to confirm.
     let params = {
       reset: false,
     };
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -7,16 +7,17 @@
 XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini']
 BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
 
 SPHINX_TREES['toolkit_modules'] = 'docs'
 
 EXTRA_JS_MODULES += [
+    'AddonWatcher.jsm',
     'Battery.jsm',
     'BinarySearch.jsm',
     'BrowserUtils.jsm',
     'CharsetMenu.jsm',
     'debug.js',
     'DeferredTask.jsm',
     'Deprecated.jsm',
     'Dict.jsm',
--- a/toolkit/mozapps/downloads/nsHelperAppDlg.js
+++ b/toolkit/mozapps/downloads/nsHelperAppDlg.js
@@ -1,17 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /*
 # 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/.
 */
 
-Components.utils.import("resource://gre/modules/Services.jsm");
+const {utils: Cu, interfaces: Ci, classes: Cc, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
 
 ///////////////////////////////////////////////////////////////////////////////
 //// Helper Functions
 
 /**
  * Determines if a given directory is able to be used to download to.
  *
  * @param aDirectory
@@ -137,16 +138,24 @@ nsUnknownContentTypeDialog.prototype = {
   // show: Open XUL dialog using window watcher.  Since the dialog is not
   //       modal, it needs to be a top level window and the way to open
   //       one of those is via that route).
   show: function(aLauncher, aContext, aReason)  {
     this.mLauncher = aLauncher;
     this.mContext  = aContext;
     this.mReason   = aReason;
 
+    // Cache some information in case this context goes away:
+    try {
+      let parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+      this._mDownloadDir = new downloadModule.DownloadLastDir(parent);
+    } catch (ex) {
+      Cu.reportError("Missing window information when showing nsIHelperAppLauncherDialog: " + ex);
+    }
+
     const nsITimer = Components.interfaces.nsITimer;
     this._showTimer = Components.classes["@mozilla.org/timer;1"]
                                 .createInstance(nsITimer);
     this._showTimer.initWithCallback(this, 0, nsITimer.TYPE_ONE_SHOT);
   },
 
   // When opening from new tab, if tab closes while dialog is opening,
   // (which is a race condition on the XUL file being cached and the timer
@@ -201,16 +210,32 @@ nsUnknownContentTypeDialog.prototype = {
     this.mLauncher = aLauncher;
 
     let prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
     let bundle =
       Services.strings
               .createBundle("chrome://mozapps/locale/downloads/unknownContentType.properties");
 
+    let parent;
+    let gDownloadLastDir;
+    try {
+      parent = aContext.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindow);
+    } catch (ex) {}
+
+    if (parent) {
+      gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
+    } else {
+      // Use the cached download info, but pick an arbitrary parent window
+      // because the original one is definitely gone (and nsIFilePicker doesn't like
+      // a null parent):
+      gDownloadLastDir = this._mDownloadDir;
+      parent = Services.wm.getMostRecentWindow("");
+    }
+
     Task.spawn(function() {
       if (!aForcePrompt) {
         // Check to see if the user wishes to auto save to the default download
         // folder without prompting. Note that preference might not be set.
         let autodownload = false;
         try {
           autodownload = prefs.getBoolPref(PREF_BD_USEDOWNLOADDIR);
         } catch (e) { }
@@ -236,22 +261,19 @@ nsUnknownContentTypeDialog.prototype = {
           }
         }
       }
 
       // Use file picker to show dialog.
       var nsIFilePicker = Components.interfaces.nsIFilePicker;
       var picker = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
       var windowTitle = bundle.GetStringFromName("saveDialogTitle");
-      var parent = aContext.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);
       picker.init(parent, windowTitle, nsIFilePicker.modeSave);
       picker.defaultString = aDefaultFile;
 
-      let gDownloadLastDir = new downloadModule.DownloadLastDir(parent);
-
       if (aSuggestedFileExtension) {
         // aSuggestedFileExtension includes the period, so strip it
         picker.defaultExtension = aSuggestedFileExtension.substring(1);
       }
       else {
         try {
           picker.defaultExtension = this.mLauncher.MIMEInfo.primaryExtension;
         }
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -200,17 +200,17 @@ xul|colorpicker[type="button"] {
 }
 
 xul|button > xul|*.button-box,
 xul|menulist > xul|*.menulist-label-box {
   padding-right: 10px !important;
   padding-left: 10px !important;
 }
 
-xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon {
+xul|menulist > xul|*.menulist-label-box > xul|*.menulist-icon[src] {
   -moz-margin-end: 5px;
 }
 
 xul|button[type="menu"] > xul|*.button-box > xul|*.button-menu-dropmarker {
   -moz-appearance: none;
   margin: 1px 0;
   -moz-margin-start: 10px;
   padding: 0;
--- a/toolkit/themes/windows/global/in-content/common.css
+++ b/toolkit/themes/windows/global/in-content/common.css
@@ -58,14 +58,34 @@ xul|*.checkbox-icon {
 }
 
 html|a:-moz-focusring,
 xul|*.text-link:-moz-focusring,
 xul|*.inline-link:-moz-focusring {
   border: 1px dotted -moz-DialogText;
 }
 
+/* Don't draw a transparent border for the focusring because when page
+   colors are disabled, the border is drawn in -moz-DialogText */
+xul|*.text-link:not(:-moz-focusring),
+xul|button:not(:-moz-focusring) > xul|*.button-box,
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box,
 xul|radio:not([focused="true"]) > xul|*.radio-label-box,
 xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
   border-width: 0;
   margin: 1px;
+}
+
+xul|*.text-link:not(:-moz-focusring) {
+  margin-top: 2px;
+  margin-right: 1px !important;
+  margin-left: 1px !important;
+  margin-bottom: 3px;
+}
+
+xul|menulist:not(:-moz-focusring) > xul|*.menulist-label-box {
+  margin: 2px;
+}
+
+xul|radio:not([focused="true"]) > xul|*.radio-label-box,
+xul|checkbox:not(:-moz-focusring) > xul|*.checkbox-label-box {
   -moz-margin-start: 0;
 }