Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 30 Aug 2015 22:09:02 -0400
changeset 279966 f2518b8a7b97b5bb477e94bc9131584007aac887
parent 279949 975b92a32949438ae8b4739ce3df15ae34a1dcfb (current diff)
parent 279965 852042d67f3892293b52a0b251173c9dd5cf818f (diff)
child 279967 34893b393686be3adac2791da1a53f5727041ca4
child 280002 3b5192222029cfa069505d2c664c61ece1dfb5fc
child 280015 00c13d486a117525eecd1702e416afad22fedd6f
push idunknown
push userunknown
push dateunknown
reviewersmerge
milestone43.0a1
first release with
nightly linux32
f2518b8a7b97 / 43.0a1 / 20150831030209 / files
nightly linux64
f2518b8a7b97 / 43.0a1 / 20150831030209 / files
nightly mac
f2518b8a7b97 / 43.0a1 / 20150831030209 / files
nightly win32
f2518b8a7b97 / 43.0a1 / 20150831030209 / files
nightly win64
f2518b8a7b97 / 43.0a1 / 20150831030209 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c. a=merge
browser/devtools/performance/test/browser_perf-options-enable-optimizations.js
browser/devtools/performance/views/details-optimizations.js
browser/devtools/performance/views/frames-list.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -223,16 +223,18 @@ pref("general.autoScroll", false);
 #else
 pref("general.autoScroll", true);
 #endif
 
 // At startup, check if we're the default browser and prompt user if not.
 pref("browser.shell.checkDefaultBrowser", true);
 pref("browser.shell.shortcutFavicons",true);
 pref("browser.shell.mostRecentDateSetAsDefault", "");
+pref("browser.shell.skipDefaultBrowserCheck", true);
+pref("browser.shell.defaultBrowserCheckCount", 0);
 
 // 0 = blank, 1 = home (browser.startup.homepage), 2 = last visited page, 3 = resume previous browser session
 // The behavior of option 3 is detailed at: http://wiki.mozilla.org/Session_Restore
 pref("browser.startup.page",                1);
 pref("browser.startup.homepage",            "chrome://branding/locale/browserconfig.properties");
 
 pref("browser.slowStartup.notificationDisabled", false);
 pref("browser.slowStartup.timeThreshold", 40000);
--- a/browser/components/build/moz.build
+++ b/browser/components/build/moz.build
@@ -19,20 +19,24 @@ LOCAL_INCLUDES += [
     '../dirprovider',
     '../feeds',
     '../migration',
     '../shell',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     OS_LIBS += [
+        'esent',
         'ole32',
         'shell32',
         'shlwapi',
         'version',
     ]
+    DELAYLOAD_DLLS += [
+        'esent.dll',
+    ]
 
 # Mac: Need to link with CoreFoundation for Mac Migrators (PList reading code)
 # GTK2: Need to link with glib for GNOME shell service
 if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'gtk2', 'gtk3'):
     OS_LIBS += CONFIG['TK_LIBS']
 
 FAIL_ON_WARNINGS = True
--- a/browser/components/build/nsBrowserCompsCID.h
+++ b/browser/components/build/nsBrowserCompsCID.h
@@ -5,16 +5,22 @@
 /////////////////////////////////////////////////////////////////////////////
 
 #ifdef XP_WIN
 #define NS_WINIEHISTORYENUMERATOR_CID \
 { 0x93480624, 0x806e, 0x4756, { 0xb7, 0xcb, 0x0f, 0xb7, 0xdd, 0x74, 0x6a, 0x8f } }
 
 #define NS_IEHISTORYENUMERATOR_CONTRACTID \
   "@mozilla.org/profile/migrator/iehistoryenumerator;1"
+
+#define NS_EDGEREADINGLISTEXTRACTOR_CID \
+{ 0xeeff77b1, 0xdb98, 0x4241, { 0x94, 0x36, 0x14, 0xf7, 0xa2, 0x28, 0x84, 0xc1 } }
+
+#define NS_EDGEREADINGLISTEXTRACTOR_CONTRACTID \
+  "@mozilla.org/profile/migrator/edgereadinglistextractor;1"
 #endif
 
 #define NS_SHELLSERVICE_CID \
 { 0x63c7b9f4, 0xcc8, 0x43f8, { 0xb6, 0x66, 0xa, 0x66, 0x16, 0x55, 0xcb, 0x73 } }
 
 #define NS_SHELLSERVICE_CONTRACTID \
   "@mozilla.org/browser/shell-service;1"
 
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -13,16 +13,17 @@
 #elif defined(XP_MACOSX)
 #include "nsMacShellService.h"
 #elif defined(MOZ_WIDGET_GTK)
 #include "nsGNOMEShellService.h"
 #endif
 
 #if defined(XP_WIN)
 #include "nsIEHistoryEnumerator.h"
+#include "nsEdgeReadingListExtractor.h"
 #endif
 
 #include "rdf.h"
 #include "nsFeedSniffer.h"
 #include "AboutRedirector.h"
 #include "nsIAboutModule.h"
 
 #include "nsNetCID.h"
@@ -37,45 +38,48 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsWindows
 #elif defined(XP_MACOSX)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsMacShellService)
 #elif defined(MOZ_WIDGET_GTK)
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init)
 #endif
 
 #if defined(XP_WIN)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator)
+NS_GENERIC_FACTORY_CONSTRUCTOR(nsEdgeReadingListExtractor)
 #endif
 
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer)
 
 NS_DEFINE_NAMED_CID(NS_BROWSERDIRECTORYPROVIDER_CID);
 #if defined(XP_WIN)
 NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
 #elif defined(MOZ_WIDGET_GTK)
 NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_FEEDSNIFFER_CID);
 NS_DEFINE_NAMED_CID(NS_BROWSER_ABOUT_REDIRECTOR_CID);
 #if defined(XP_WIN)
 NS_DEFINE_NAMED_CID(NS_WINIEHISTORYENUMERATOR_CID);
+NS_DEFINE_NAMED_CID(NS_EDGEREADINGLISTEXTRACTOR_CID);
 #elif defined(XP_MACOSX)
 NS_DEFINE_NAMED_CID(NS_SHELLSERVICE_CID);
 #endif
 
 static const mozilla::Module::CIDEntry kBrowserCIDs[] = {
     { &kNS_BROWSERDIRECTORYPROVIDER_CID, false, nullptr, DirectoryProviderConstructor },
 #if defined(XP_WIN)
     { &kNS_SHELLSERVICE_CID, false, nullptr, nsWindowsShellServiceConstructor },
 #elif defined(MOZ_WIDGET_GTK)
     { &kNS_SHELLSERVICE_CID, false, nullptr, nsGNOMEShellServiceConstructor },
 #endif
     { &kNS_FEEDSNIFFER_CID, false, nullptr, nsFeedSnifferConstructor },
     { &kNS_BROWSER_ABOUT_REDIRECTOR_CID, false, nullptr, AboutRedirector::Create },
 #if defined(XP_WIN)
     { &kNS_WINIEHISTORYENUMERATOR_CID, false, nullptr, nsIEHistoryEnumeratorConstructor },
+    { &kNS_EDGEREADINGLISTEXTRACTOR_CID, false, nullptr, nsEdgeReadingListExtractorConstructor },
 #elif defined(XP_MACOSX)
     { &kNS_SHELLSERVICE_CID, false, nullptr, nsMacShellServiceConstructor },
 #endif
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kBrowserContracts[] = {
     { NS_BROWSERDIRECTORYPROVIDER_CONTRACTID, &kNS_BROWSERDIRECTORYPROVIDER_CID },
@@ -114,16 +118,17 @@ static const mozilla::Module::ContractID
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "customizing", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "looppanel", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "loopconversation", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "pocket-saved", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "pocket-signup", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #if defined(XP_WIN)
     { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
+    { NS_EDGEREADINGLISTEXTRACTOR_CONTRACTID, &kNS_EDGEREADINGLISTEXTRACTOR_CID },
 #elif defined(XP_MACOSX)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #endif
     { nullptr }
 };
 
 static const mozilla::Module::CategoryEntry kBrowserCategories[] = {
     { XPCOM_DIRECTORY_PROVIDER_CATEGORY, "browser-directory-provider", NS_BROWSERDIRECTORYPROVIDER_CONTRACTID },
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -1,30 +1,30 @@
-/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource:///modules/MigrationUtils.jsm");
-Cu.import("resource:///modules/MSMigrationUtils.jsm");
-
-function EdgeProfileMigrator() {
-}
-
-EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
-
-EdgeProfileMigrator.prototype.getResources = function() {
-  let resources = [
-    MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
-    MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
-  ];
-  return resources.filter(r => r.exists);
-};
-
-EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
-EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
-EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EdgeProfileMigrator]);
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+Cu.import("resource:///modules/MSMigrationUtils.jsm");
+
+function EdgeProfileMigrator() {
+}
+
+EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+EdgeProfileMigrator.prototype.getResources = function() {
+  let resources = [
+    MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+    MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+  ];
+  return resources.filter(r => r.exists);
+};
+
+EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
+EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
+EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([EdgeProfileMigrator]);
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -79,17 +79,18 @@ History.prototype = {
       if (title.length == 0) {
         continue;
       }
 
       // The typed urls are already fixed-up, so we can use them for comparison.
       let transitionType = this._typedURLs[uri.spec] ?
                              Ci.nsINavHistoryService.TRANSITION_TYPED :
                              Ci.nsINavHistoryService.TRANSITION_LINK;
-      let lastVisitTime = entry.get("time");
+      // use the current date if we have no visits for this entry
+      let lastVisitTime = entry.get("time") || Date.now();
 
       places.push(
         { uri: uri,
           title: title,
           visits: [{ transitionType: transitionType,
                      visitDate: lastVisitTime }]
         }
       );
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -1,485 +1,563 @@
-/* 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 = ["MSMigrationUtils"];
-
-const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/Task.jsm");
-Cu.import("resource:///modules/MigrationUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
-                                  "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
-                                  "resource://gre/modules/WindowsRegistry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
-                                  "resource://gre/modules/ctypes.jsm");
-
-const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
-const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
-const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
-
-Cu.importGlobalProperties(["File"]);
-
-////////////////////////////////////////////////////////////////////////////////
-//// Helpers.
-
-let CtypesHelpers = {
-  _structs: {},
-  _functions: {},
-  _libs: {},
-
-  /**
-   * Must be invoked once before first use of any of the provided helpers.
-   */
-  initialize() {
-    const WORD = ctypes.uint16_t;
-    const DWORD = ctypes.uint32_t;
-    const BOOL = ctypes.int;
-
-    this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
-      {wYear: WORD},
-      {wMonth: WORD},
-      {wDayOfWeek: WORD},
-      {wDay: WORD},
-      {wHour: WORD},
-      {wMinute: WORD},
-      {wSecond: WORD},
-      {wMilliseconds: WORD}
-    ]);
-
-    this._structs.FILETIME = new ctypes.StructType('FILETIME', [
-      {dwLowDateTime: DWORD},
-      {dwHighDateTime: DWORD}
-    ]);
-
-    try {
-      this._libs.kernel32 = ctypes.open("Kernel32");
-      this._functions.FileTimeToSystemTime =
-        this._libs.kernel32.declare("FileTimeToSystemTime",
-                                    ctypes.default_abi,
-                                    BOOL,
-                                    this._structs.FILETIME.ptr,
-                                    this._structs.SYSTEMTIME.ptr);
-    } catch (ex) {
-      this.finalize();
-    }
-  },
-
-  /**
-   * Must be invoked once after last use of any of the provided helpers.
-   */
-  finalize() {
-    this._structs = {};
-    this._functions = {};
-    for each (let lib in this._libs) {
-      try {
-        lib.close();
-      } catch (ex) {}
-    }
-    this._libs = {};
-  },
-
-  /**
-   * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
-   * and then deduces the number of seconds since the epoch (which
-   * is the data we want for the cookie expiry date).
-   *
-   * @param aTimeHi
-   *        Least significant DWORD.
-   * @param aTimeLo
-   *        Most significant DWORD.
-   * @return the number of seconds since the epoch
-   */
-  fileTimeToSecondsSinceEpoch(aTimeHi, aTimeLo) {
-    let fileTime = this._structs.FILETIME();
-    fileTime.dwLowDateTime = aTimeLo;
-    fileTime.dwHighDateTime = aTimeHi;
-    let systemTime = this._structs.SYSTEMTIME();
-    let result = this._functions.FileTimeToSystemTime(fileTime.address(),
-                                                      systemTime.address());
-    if (result == 0)
-      throw new Error(ctypes.winLastError);
-
-    // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
-    // then divide by 1000 to get seconds, and round down:
-    return Math.floor(Date.UTC(systemTime.wYear,
-                               systemTime.wMonth - 1,
-                               systemTime.wDay,
-                               systemTime.wHour,
-                               systemTime.wMinute,
-                               systemTime.wSecond,
-                               systemTime.wMilliseconds) / 1000);
-  }
-};
-
-/**
- * Checks whether an host is an IP (v4 or v6) address.
- *
- * @param aHost
- *        The host to check.
- * @return whether aHost is an IP address.
- */
-function hostIsIPAddress(aHost) {
-  try {
-    Services.eTLD.getBaseDomainFromHost(aHost);
-  } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
-    return true;
-  } catch (e) {}
-  return false;
-}
-
-let gEdgeDir;
-function getEdgeLocalDataFolder() {
-  if (gEdgeDir) {
-    return gEdgeDir.clone();
-  }
-  let packages = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
-  packages.append("Packages");
-  let edgeDir = packages.clone();
-  edgeDir.append("Microsoft.MicrosoftEdge_8wekyb3d8bbwe");
-  try {
-    if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
-      gEdgeDir = edgeDir;
-      return edgeDir.clone();
-    }
-
-    // Let's try the long way:
-    let dirEntries = packages.directoryEntries;
-    while (dirEntries.hasMoreElements()) {
-      let subDir = dirEntries.getNext();
-      subDir.QueryInterface(Ci.nsIFile);
-      if (subDir.leafName.startsWith("Microsoft.MicrosoftEdge") && subDir.isReadable() &&
-          subDir.isDirectory()) {
-        gEdgeDir = subDir;
-        return subDir.clone();
-      }
-    }
-  } catch (ex) {
-    Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
-  }
-  return null;
-}
-
-
-function Bookmarks(migrationType) {
-  this._migrationType = migrationType;
-}
-
-Bookmarks.prototype = {
-  type: MigrationUtils.resourceTypes.BOOKMARKS,
-
-  get exists() !!this._favoritesFolder,
-
-  get importedAppLabel() this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE ? "IE" : "Edge",
-
-  __favoritesFolder: null,
-  get _favoritesFolder() {
-    if (!this.__favoritesFolder) {
-      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
-        let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
-        if (favoritesFolder.exists() && favoritesFolder.isReadable())
-          return this.__favoritesFolder = favoritesFolder;
-      }
-      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
-        let edgeDir = getEdgeLocalDataFolder();
-        if (edgeDir) {
-          edgeDir.appendRelativePath(EDGE_FAVORITES);
-          if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
-            return this.__favoritesFolder = edgeDir;
-          }
-        }
-      }
-    }
-    return this.__favoritesFolder;
-  },
-
-  __toolbarFolderName: null,
-  get _toolbarFolderName() {
-    if (!this.__toolbarFolderName) {
-      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
-        // Retrieve the name of IE's favorites subfolder that holds the bookmarks
-        // in the toolbar. This was previously stored in the registry and changed
-        // in IE7 to always be called "Links".
-        let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                                                    "Software\\Microsoft\\Internet Explorer\\Toolbar",
-                                                    "LinksFolderName");
-        this.__toolbarFolderName = folderName || "Links";
-      } else {
-        this.__toolbarFolderName = "Links";
-      }
-    }
-    return this.__toolbarFolderName;
-  },
-
-  migrate: function B_migrate(aCallback) {
-    return Task.spawn(function* () {
-      // Import to the bookmarks menu.
-      let folderGuid = PlacesUtils.bookmarks.menuGuid;
-      if (!MigrationUtils.isStartupMigration) {
-        folderGuid =
-          yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
-      }
-      yield this._migrateFolder(this._favoritesFolder, folderGuid);
-    }.bind(this)).then(() => aCallback(true),
-                        e => { Cu.reportError(e); aCallback(false) });
-  },
-
-  _migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) {
-    // TODO (bug 741993): the favorites order is stored in the Registry, at
-    // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
-    // for IE, and in a similar location for Edge.
-    // Until we support it, bookmarks are imported in alphabetical order.
-    let entries = aSourceFolder.directoryEntries;
-    while (entries.hasMoreElements()) {
-      let entry = entries.getNext().QueryInterface(Ci.nsIFile);
-      try {
-        // Make sure that entry.path == entry.target to not follow .lnk folder
-        // shortcuts which could lead to infinite cycles.
-        // Don't use isSymlink(), since it would throw for invalid
-        // lnk files pointing to URLs or to unresolvable paths.
-        if (entry.path == entry.target && entry.isDirectory()) {
-          let folderGuid;
-          if (entry.leafName == this._toolbarFolderName &&
-              entry.parent.equals(this._favoritesFolder)) {
-            // Import to the bookmarks toolbar.
-            folderGuid = PlacesUtils.bookmarks.toolbarGuid;
-            if (!MigrationUtils.isStartupMigration) {
-              folderGuid =
-                yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
-            }
-          }
-          else {
-            // Import to a new folder.
-            folderGuid = (yield PlacesUtils.bookmarks.insert({
-              type: PlacesUtils.bookmarks.TYPE_FOLDER,
-              parentGuid: aDestFolderGuid,
-              title: entry.leafName
-            })).guid;
-          }
-
-          if (entry.isReadable()) {
-            // Recursively import the folder.
-            yield this._migrateFolder(entry, folderGuid);
-          }
-        }
-        else {
-          // Strip the .url extension, to both check this is a valid link file,
-          // and get the associated title.
-          let matches = entry.leafName.match(/(.+)\.url$/i);
-          if (matches) {
-            let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
-                              getService(Ci.nsIFileProtocolHandler);
-            let uri = fileHandler.readURLFile(entry);
-            let title = matches[1];
-
-            yield PlacesUtils.bookmarks.insert({
-              parentGuid: aDestFolderGuid, url: uri, title
-            });
-          }
-        }
-      } catch (ex) {
-        Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
-      }
-    }
-  })
-};
-
-function Cookies(migrationType) {
-  this._migrationType = migrationType;
-}
-
-Cookies.prototype = {
-  type: MigrationUtils.resourceTypes.COOKIES,
-
-  get exists() {
-    if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
-      return !!this._cookiesFolder;
-    }
-    return !!this._cookiesFolders;
-  },
-
-  __cookiesFolder: null,
-  get _cookiesFolder() {
-    // Edge stores cookies in a number of places, and this shouldn't get called:
-    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_IE) {
-      throw new Error("Shouldn't be looking for a single cookie folder unless we're migrating IE");
-    }
-
-    // Cookies are stored in txt files, in a Cookies folder whose path varies
-    // across the different OS versions.  CookD takes care of most of these
-    // cases, though, in Windows Vista/7, UAC makes a difference.
-    // If UAC is enabled, the most common destination is CookD/Low.  Though,
-    // if the user runs the application in administrator mode or disables UAC,
-    // cookies are stored in the original CookD destination.  Cause running the
-    // browser in administrator mode is unsafe and discouraged, we just care
-    // about the UAC state.
-    if (!this.__cookiesFolder) {
-      let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
-      if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
-        // Check if UAC is enabled.
-        if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
-          cookiesFolder.append("Low");
-        }
-        this.__cookiesFolder = cookiesFolder;
-      }
-    }
-    return this.__cookiesFolder;
-  },
-
-  __cookiesFolders: null,
-  get _cookiesFolders() {
-    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_EDGE) {
-      throw new Error("Shouldn't be looking for multiple cookie folders unless we're migrating Edge");
-    }
-
-    let folders = [];
-    let edgeDir = getEdgeLocalDataFolder();
-    if (edgeDir) {
-      edgeDir.append("AC");
-      for (let path of EDGE_COOKIE_PATH_OPTIONS) {
-        let folder = edgeDir.clone();
-        let fullPath = path + EDGE_COOKIES_SUFFIX;
-        folder.appendRelativePath(fullPath);
-        if (folder.exists() && folder.isReadable() && folder.isDirectory()) {
-          folders.push(folder);
-        }
-      }
-    }
-    return this.__cookiesFolders = folders.length ? folders : null;
-  },
-
-  migrate(aCallback) {
-    CtypesHelpers.initialize();
-
-    let cookiesGenerator = (function genCookie() {
-      let success = false;
-      let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
-                      this.__cookiesFolders : [this.__cookiesFolder];
-      for (let folder of folders) {
-        let entries = folder.directoryEntries;
-        while (entries.hasMoreElements()) {
-          let entry = entries.getNext().QueryInterface(Ci.nsIFile);
-          // Skip eventual bogus entries.
-          if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
-            continue;
-
-          this._readCookieFile(entry, function(aSuccess) {
-            // Importing even a single cookie file is considered a success.
-            if (aSuccess)
-              success = true;
-            try {
-              cookiesGenerator.next();
-            } catch (ex) {}
-          });
-
-          yield undefined;
-        }
-      }
-
-      CtypesHelpers.finalize();
-
-      aCallback(success);
-    }).apply(this);
-    cookiesGenerator.next();
-  },
-
-  _readCookieFile(aFile, aCallback) {
-    let fileReader = Cc["@mozilla.org/files/filereader;1"].
-                     createInstance(Ci.nsIDOMFileReader);
-    let onLoadEnd = () => {
-      fileReader.removeEventListener("loadend", onLoadEnd, false);
-
-      if (fileReader.readyState != fileReader.DONE) {
-        Cu.reportError("Could not read cookie contents: " + fileReader.error);
-        aCallback(false);
-        return;
-      }
-
-      let success = true;
-      try {
-        this._parseCookieBuffer(fileReader.result);
-      } catch (ex) {
-        Components.utils.reportError("Unable to migrate cookie: " + ex);
-        success = false;
-      } finally {
-        aCallback(success);
-      }
-    };
-    fileReader.addEventListener("loadend", onLoadEnd, false);
-    fileReader.readAsText(new File(aFile));
-  },
-
-  /**
-   * Parses a cookie file buffer and returns an array of the contained cookies.
-   *
-   * The cookie file format is a newline-separated-values with a "*" used as
-   * delimeter between multiple records.
-   * Each cookie has the following fields:
-   *  - name
-   *  - value
-   *  - host/path
-   *  - flags
-   *  - Expiration time most significant integer
-   *  - Expiration time least significant integer
-   *  - Creation time most significant integer
-   *  - Creation time least significant integer
-   *  - Record delimiter "*"
-   *
-   * @note All the times are in FILETIME format.
-   */
-  _parseCookieBuffer(aTextBuffer) {
-    // Note the last record is an empty string.
-    let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
-    for (let record of records) {
-      let [name, value, hostpath, flags,
-           expireTimeLo, expireTimeHi] = record.split("\n");
-
-      // IE stores deleted cookies with a zero-length value, skip them.
-      if (value.length == 0)
-        continue;
-
-      let hostLen = hostpath.indexOf("/");
-      let host = hostpath.substr(0, hostLen);
-      let path = hostpath.substr(hostLen);
-
-      // For a non-null domain, assume it's what Mozilla considers
-      // a domain cookie.  See bug 222343.
-      if (host.length > 0) {
-        // Fist delete any possible extant matching host cookie.
-        Services.cookies.remove(host, name, path, false);
-        // Now make it a domain cookie.
-        if (host[0] != "." && !hostIsIPAddress(host))
-          host = "." + host;
-      }
-
-      let expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
-                                                                 Number(expireTimeLo));
-      Services.cookies.add(host,
-                           path,
-                           name,
-                           value,
-                           Number(flags) & 0x1, // secure
-                           false, // httpOnly
-                           false, // session
-                           expireTime);
-    }
-  }
-};
-
-
-let MSMigrationUtils = {
-  MIGRATION_TYPE_IE: 1,
-  MIGRATION_TYPE_EDGE: 2,
-  getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
-    return new Bookmarks(migrationType);
-  },
-  getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
-    return new Cookies(migrationType);
-  },
-};
+/* 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 = ["MSMigrationUtils"];
+
+const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+                                  "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+                                  "resource://gre/modules/WindowsRegistry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+                                  "resource://gre/modules/ctypes.jsm");
+
+const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
+const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
+const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
+const EDGE_READINGLIST = "AC\\MicrosoftEdge\\User\\Default\\DataStore\\Data\\";
+
+Cu.importGlobalProperties(["File"]);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers.
+
+let CtypesHelpers = {
+  _structs: {},
+  _functions: {},
+  _libs: {},
+
+  /**
+   * Must be invoked once before first use of any of the provided helpers.
+   */
+  initialize() {
+    const WORD = ctypes.uint16_t;
+    const DWORD = ctypes.uint32_t;
+    const BOOL = ctypes.int;
+
+    this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
+      {wYear: WORD},
+      {wMonth: WORD},
+      {wDayOfWeek: WORD},
+      {wDay: WORD},
+      {wHour: WORD},
+      {wMinute: WORD},
+      {wSecond: WORD},
+      {wMilliseconds: WORD}
+    ]);
+
+    this._structs.FILETIME = new ctypes.StructType('FILETIME', [
+      {dwLowDateTime: DWORD},
+      {dwHighDateTime: DWORD}
+    ]);
+
+    try {
+      this._libs.kernel32 = ctypes.open("Kernel32");
+      this._functions.FileTimeToSystemTime =
+        this._libs.kernel32.declare("FileTimeToSystemTime",
+                                    ctypes.default_abi,
+                                    BOOL,
+                                    this._structs.FILETIME.ptr,
+                                    this._structs.SYSTEMTIME.ptr);
+    } catch (ex) {
+      this.finalize();
+    }
+  },
+
+  /**
+   * Must be invoked once after last use of any of the provided helpers.
+   */
+  finalize() {
+    this._structs = {};
+    this._functions = {};
+    for each (let lib in this._libs) {
+      try {
+        lib.close();
+      } catch (ex) {}
+    }
+    this._libs = {};
+  },
+
+  /**
+   * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
+   * and then deduces the number of seconds since the epoch (which
+   * is the data we want for the cookie expiry date).
+   *
+   * @param aTimeHi
+   *        Least significant DWORD.
+   * @param aTimeLo
+   *        Most significant DWORD.
+   * @return the number of seconds since the epoch
+   */
+  fileTimeToSecondsSinceEpoch(aTimeHi, aTimeLo) {
+    let fileTime = this._structs.FILETIME();
+    fileTime.dwLowDateTime = aTimeLo;
+    fileTime.dwHighDateTime = aTimeHi;
+    let systemTime = this._structs.SYSTEMTIME();
+    let result = this._functions.FileTimeToSystemTime(fileTime.address(),
+                                                      systemTime.address());
+    if (result == 0)
+      throw new Error(ctypes.winLastError);
+
+    // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
+    // then divide by 1000 to get seconds, and round down:
+    return Math.floor(Date.UTC(systemTime.wYear,
+                               systemTime.wMonth - 1,
+                               systemTime.wDay,
+                               systemTime.wHour,
+                               systemTime.wMinute,
+                               systemTime.wSecond,
+                               systemTime.wMilliseconds) / 1000);
+  }
+};
+
+/**
+ * Checks whether an host is an IP (v4 or v6) address.
+ *
+ * @param aHost
+ *        The host to check.
+ * @return whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+  try {
+    Services.eTLD.getBaseDomainFromHost(aHost);
+  } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+    return true;
+  } catch (e) {}
+  return false;
+}
+
+let gEdgeDir;
+function getEdgeLocalDataFolder() {
+  if (gEdgeDir) {
+    return gEdgeDir.clone();
+  }
+  let packages = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
+  packages.append("Packages");
+  let edgeDir = packages.clone();
+  edgeDir.append("Microsoft.MicrosoftEdge_8wekyb3d8bbwe");
+  try {
+    if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+      gEdgeDir = edgeDir;
+      return edgeDir.clone();
+    }
+
+    // Let's try the long way:
+    let dirEntries = packages.directoryEntries;
+    while (dirEntries.hasMoreElements()) {
+      let subDir = dirEntries.getNext();
+      subDir.QueryInterface(Ci.nsIFile);
+      if (subDir.leafName.startsWith("Microsoft.MicrosoftEdge") && subDir.isReadable() &&
+          subDir.isDirectory()) {
+        gEdgeDir = subDir;
+        return subDir.clone();
+      }
+    }
+  } catch (ex) {
+    Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
+  }
+  return null;
+}
+
+
+function Bookmarks(migrationType) {
+  this._migrationType = migrationType;
+}
+
+Bookmarks.prototype = {
+  type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+  get exists() !!this._favoritesFolder,
+
+  get importedAppLabel() this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE ? "IE" : "Edge",
+
+  __favoritesFolder: null,
+  get _favoritesFolder() {
+    if (!this.__favoritesFolder) {
+      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+        let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
+        if (favoritesFolder.exists() && favoritesFolder.isReadable())
+          return this.__favoritesFolder = favoritesFolder;
+      }
+      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+        let edgeDir = getEdgeLocalDataFolder();
+        if (edgeDir) {
+          edgeDir.appendRelativePath(EDGE_FAVORITES);
+          if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+            return this.__favoritesFolder = edgeDir;
+          }
+        }
+      }
+    }
+    return this.__favoritesFolder;
+  },
+
+  __toolbarFolderName: null,
+  get _toolbarFolderName() {
+    if (!this.__toolbarFolderName) {
+      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+        // Retrieve the name of IE's favorites subfolder that holds the bookmarks
+        // in the toolbar. This was previously stored in the registry and changed
+        // in IE7 to always be called "Links".
+        let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                                                    "Software\\Microsoft\\Internet Explorer\\Toolbar",
+                                                    "LinksFolderName");
+        this.__toolbarFolderName = folderName || "Links";
+      } else {
+        this.__toolbarFolderName = "Links";
+      }
+    }
+    return this.__toolbarFolderName;
+  },
+
+  migrate: function B_migrate(aCallback) {
+    return Task.spawn(function* () {
+      // Import to the bookmarks menu.
+      let folderGuid = PlacesUtils.bookmarks.menuGuid;
+      if (!MigrationUtils.isStartupMigration) {
+        folderGuid =
+          yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
+      }
+      yield this._migrateFolder(this._favoritesFolder, folderGuid);
+
+      if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+        yield this._migrateEdgeReadingList(PlacesUtils.bookmarks.menuGuid);
+      }
+    }.bind(this)).then(() => aCallback(true),
+                        e => { Cu.reportError(e); aCallback(false) });
+  },
+
+  _migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) {
+    // TODO (bug 741993): the favorites order is stored in the Registry, at
+    // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
+    // for IE, and in a similar location for Edge.
+    // Until we support it, bookmarks are imported in alphabetical order.
+    let entries = aSourceFolder.directoryEntries;
+    while (entries.hasMoreElements()) {
+      let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+      try {
+        // Make sure that entry.path == entry.target to not follow .lnk folder
+        // shortcuts which could lead to infinite cycles.
+        // Don't use isSymlink(), since it would throw for invalid
+        // lnk files pointing to URLs or to unresolvable paths.
+        if (entry.path == entry.target && entry.isDirectory()) {
+          let folderGuid;
+          if (entry.leafName == this._toolbarFolderName &&
+              entry.parent.equals(this._favoritesFolder)) {
+            // Import to the bookmarks toolbar.
+            folderGuid = PlacesUtils.bookmarks.toolbarGuid;
+            if (!MigrationUtils.isStartupMigration) {
+              folderGuid =
+                yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid);
+            }
+          }
+          else {
+            // Import to a new folder.
+            folderGuid = (yield PlacesUtils.bookmarks.insert({
+              type: PlacesUtils.bookmarks.TYPE_FOLDER,
+              parentGuid: aDestFolderGuid,
+              title: entry.leafName
+            })).guid;
+          }
+
+          if (entry.isReadable()) {
+            // Recursively import the folder.
+            yield this._migrateFolder(entry, folderGuid);
+          }
+        }
+        else {
+          // Strip the .url extension, to both check this is a valid link file,
+          // and get the associated title.
+          let matches = entry.leafName.match(/(.+)\.url$/i);
+          if (matches) {
+            let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
+                              getService(Ci.nsIFileProtocolHandler);
+            let uri = fileHandler.readURLFile(entry);
+            let title = matches[1];
+
+            yield PlacesUtils.bookmarks.insert({
+              parentGuid: aDestFolderGuid, url: uri, title
+            });
+          }
+        }
+      } catch (ex) {
+        Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
+      }
+    }
+  }),
+
+  _migrateEdgeReadingList: Task.async(function*(parentGuid) {
+    let edgeDir = getEdgeLocalDataFolder();
+    if (!edgeDir) {
+      return;
+    }
+
+    this._readingListExtractor = Cc["@mozilla.org/profile/migrator/edgereadinglistextractor;1"].
+                                 createInstance(Ci.nsIEdgeReadingListExtractor);
+    edgeDir.appendRelativePath(EDGE_READINGLIST);
+    if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+      let expectedDir = edgeDir.clone();
+      expectedDir.appendRelativePath("nouser1\\120712-0049");
+      if (expectedDir.exists() && expectedDir.isReadable() && expectedDir.isDirectory()) {
+        yield this._migrateEdgeReadingListDB(expectedDir, parentGuid);
+      } else {
+        let getSubdirs = someDir => {
+          let subdirs = someDir.directoryEntries;
+          let rv = [];
+          while (subdirs.hasMoreElements()) {
+            let subdir = subdirs.getNext().QueryInterface(Ci.nsIFile);
+            if (subdir.isDirectory() && subdir.isReadable()) {
+              rv.push(subdir);
+            }
+          }
+          return rv;
+        };
+        let dirs = getSubdirs(edgeDir).map(getSubdirs);
+        for (let dir of dirs) {
+          yield this._migrateEdgeReadingListDB(dir, parentGuid);
+        }
+      }
+    }
+  }),
+  _migrateEdgeReadingListDB: Task.async(function*(dbFile, parentGuid) {
+    dbFile.appendRelativePath("DBStore\\spartan.edb");
+    if (!dbFile.exists() || !dbFile.isReadable() || !dbFile.isFile()) {
+      return;
+    }
+    let readingListItems;
+    try {
+      readingListItems = this._readingListExtractor.extract(dbFile.path);
+    } catch (ex) {
+      Cu.reportError("Failed to extract Edge reading list information from " +
+                     "the database at " + dbPath + " due to the following error: " + ex);
+      return;
+    }
+    if (!readingListItems.length) {
+      return;
+    }
+    let destFolderGuid = yield this._ensureEdgeReadingListFolder(parentGuid);
+    for (let i = 0; i < readingListItems.length; i++) {
+      let readingListItem = readingListItems.queryElementAt(i, Ci.nsIPropertyBag2);
+      let url = readingListItem.get("uri");
+      let title = readingListItem.get("title");
+      let time = readingListItem.get("time");
+      // time is a PRTime, which is microseconds (since unix epoch), or null.
+      // We need milliseconds for the date constructor, so divide by 1000:
+      let dateAdded = time ? new Date(time / 1000) : new Date();
+      yield PlacesUtils.bookmarks.insert({
+        parentGuid: destFolderGuid, url: url, title, dateAdded
+      });
+    }
+  }),
+
+  _ensureEdgeReadingListFolder: Task.async(function*(parentGuid) {
+    if (!this.__edgeReadingListFolderGuid) {
+      let folderTitle = MigrationUtils.getLocalizedString("importedEdgeReadingList");
+      let folderSpec = {type: PlacesUtils.bookmarks.TYPE_FOLDER, parentGuid, title: folderTitle};
+      this.__edgeReadingListFolderGuid = (yield PlacesUtils.bookmarks.insert(folderSpec)).guid;
+    }
+    return this.__edgeReadingListFolderGuid;
+  }),
+};
+
+function Cookies(migrationType) {
+  this._migrationType = migrationType;
+}
+
+Cookies.prototype = {
+  type: MigrationUtils.resourceTypes.COOKIES,
+
+  get exists() {
+    if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+      return !!this._cookiesFolder;
+    }
+    return !!this._cookiesFolders;
+  },
+
+  __cookiesFolder: null,
+  get _cookiesFolder() {
+    // Edge stores cookies in a number of places, and this shouldn't get called:
+    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_IE) {
+      throw new Error("Shouldn't be looking for a single cookie folder unless we're migrating IE");
+    }
+
+    // Cookies are stored in txt files, in a Cookies folder whose path varies
+    // across the different OS versions.  CookD takes care of most of these
+    // cases, though, in Windows Vista/7, UAC makes a difference.
+    // If UAC is enabled, the most common destination is CookD/Low.  Though,
+    // if the user runs the application in administrator mode or disables UAC,
+    // cookies are stored in the original CookD destination.  Cause running the
+    // browser in administrator mode is unsafe and discouraged, we just care
+    // about the UAC state.
+    if (!this.__cookiesFolder) {
+      let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
+      if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
+        // Check if UAC is enabled.
+        if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+          cookiesFolder.append("Low");
+        }
+        this.__cookiesFolder = cookiesFolder;
+      }
+    }
+    return this.__cookiesFolder;
+  },
+
+  __cookiesFolders: null,
+  get _cookiesFolders() {
+    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+      throw new Error("Shouldn't be looking for multiple cookie folders unless we're migrating Edge");
+    }
+
+    let folders = [];
+    let edgeDir = getEdgeLocalDataFolder();
+    if (edgeDir) {
+      edgeDir.append("AC");
+      for (let path of EDGE_COOKIE_PATH_OPTIONS) {
+        let folder = edgeDir.clone();
+        let fullPath = path + EDGE_COOKIES_SUFFIX;
+        folder.appendRelativePath(fullPath);
+        if (folder.exists() && folder.isReadable() && folder.isDirectory()) {
+          folders.push(folder);
+        }
+      }
+    }
+    return this.__cookiesFolders = folders.length ? folders : null;
+  },
+
+  migrate(aCallback) {
+    CtypesHelpers.initialize();
+
+    let cookiesGenerator = (function genCookie() {
+      let success = false;
+      let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
+                      this.__cookiesFolders : [this.__cookiesFolder];
+      for (let folder of folders) {
+        let entries = folder.directoryEntries;
+        while (entries.hasMoreElements()) {
+          let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+          // Skip eventual bogus entries.
+          if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+            continue;
+
+          this._readCookieFile(entry, function(aSuccess) {
+            // Importing even a single cookie file is considered a success.
+            if (aSuccess)
+              success = true;
+            try {
+              cookiesGenerator.next();
+            } catch (ex) {}
+          });
+
+          yield undefined;
+        }
+      }
+
+      CtypesHelpers.finalize();
+
+      aCallback(success);
+    }).apply(this);
+    cookiesGenerator.next();
+  },
+
+  _readCookieFile(aFile, aCallback) {
+    let fileReader = Cc["@mozilla.org/files/filereader;1"].
+                     createInstance(Ci.nsIDOMFileReader);
+    let onLoadEnd = () => {
+      fileReader.removeEventListener("loadend", onLoadEnd, false);
+
+      if (fileReader.readyState != fileReader.DONE) {
+        Cu.reportError("Could not read cookie contents: " + fileReader.error);
+        aCallback(false);
+        return;
+      }
+
+      let success = true;
+      try {
+        this._parseCookieBuffer(fileReader.result);
+      } catch (ex) {
+        Components.utils.reportError("Unable to migrate cookie: " + ex);
+        success = false;
+      } finally {
+        aCallback(success);
+      }
+    };
+    fileReader.addEventListener("loadend", onLoadEnd, false);
+    fileReader.readAsText(new File(aFile));
+  },
+
+  /**
+   * Parses a cookie file buffer and returns an array of the contained cookies.
+   *
+   * The cookie file format is a newline-separated-values with a "*" used as
+   * delimeter between multiple records.
+   * Each cookie has the following fields:
+   *  - name
+   *  - value
+   *  - host/path
+   *  - flags
+   *  - Expiration time most significant integer
+   *  - Expiration time least significant integer
+   *  - Creation time most significant integer
+   *  - Creation time least significant integer
+   *  - Record delimiter "*"
+   *
+   * @note All the times are in FILETIME format.
+   */
+  _parseCookieBuffer(aTextBuffer) {
+    // Note the last record is an empty string.
+    let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
+    for (let record of records) {
+      let [name, value, hostpath, flags,
+           expireTimeLo, expireTimeHi] = record.split("\n");
+
+      // IE stores deleted cookies with a zero-length value, skip them.
+      if (value.length == 0)
+        continue;
+
+      let hostLen = hostpath.indexOf("/");
+      let host = hostpath.substr(0, hostLen);
+      let path = hostpath.substr(hostLen);
+
+      // For a non-null domain, assume it's what Mozilla considers
+      // a domain cookie.  See bug 222343.
+      if (host.length > 0) {
+        // Fist delete any possible extant matching host cookie.
+        Services.cookies.remove(host, name, path, false);
+        // Now make it a domain cookie.
+        if (host[0] != "." && !hostIsIPAddress(host))
+          host = "." + host;
+      }
+
+      let expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+                                                                 Number(expireTimeLo));
+      Services.cookies.add(host,
+                           path,
+                           name,
+                           value,
+                           Number(flags) & 0x1, // secure
+                           false, // httpOnly
+                           false, // session
+                           expireTime);
+    }
+  }
+};
+
+
+let MSMigrationUtils = {
+  MIGRATION_TYPE_IE: 1,
+  MIGRATION_TYPE_EDGE: 2,
+  getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+    return new Bookmarks(migrationType);
+  },
+  getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+    return new Cookies(migrationType);
+  },
+};
--- a/browser/components/migration/moz.build
+++ b/browser/components/migration/moz.build
@@ -5,22 +5,24 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 JAR_MANIFESTS += ['jar.mn']
 
 XPIDL_SOURCES += [
     'nsIBrowserProfileMigrator.idl',
+    'nsIEdgeReadingListExtractor.idl',
 ]
 
 XPIDL_MODULE = 'migration'
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     SOURCES += [
+        'nsEdgeReadingListExtractor.cpp',
         'nsIEHistoryEnumerator.cpp',
     ]
 
 EXTRA_COMPONENTS += [
     'FirefoxProfileMigrator.js',
     'ProfileMigrator.js',
 ]
 
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/nsEdgeReadingListExtractor.cpp
@@ -0,0 +1,203 @@
+/* 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/. */
+
+#include "nsEdgeReadingListExtractor.h"
+
+#include <windows.h>
+
+#include "nsCOMPtr.h"
+#include "nsIConsoleService.h"
+#include "nsIMutableArray.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindowsMigrationUtils.h"
+
+#define NS_HANDLE_JET_ERROR(err) { \
+  if (err < JET_errSuccess) {	\
+    rv = ConvertJETError(err); \
+    goto CloseDB; \
+  } \
+}
+
+#define MAX_URL_LENGTH 4168
+#define MAX_TITLE_LENGTH 1024
+
+NS_IMPL_ISUPPORTS(nsEdgeReadingListExtractor, nsIEdgeReadingListExtractor)
+
+NS_IMETHODIMP
+nsEdgeReadingListExtractor::Extract(const nsAString& aDBPath, nsIArray** aItems)
+{
+  nsresult rv = NS_OK;
+  *aItems = nullptr;
+
+  JET_ERR err;
+  JET_INSTANCE instance;
+  JET_SESID sesid;
+  JET_DBID dbid;
+  JET_TABLEID tableid;
+  JET_COLUMNDEF urlColumnInfo = { 0 };
+  JET_COLUMNDEF titleColumnInfo = { 0 };
+  JET_COLUMNDEF addedDateColumnInfo = { 0 };
+
+  // Need to ensure this happens before we skip ahead to CloseDB,
+  // otherwise the compiler complains.
+  nsCOMPtr<nsIMutableArray> items = do_CreateInstance(NS_ARRAY_CONTRACTID);
+
+  // JET does not throw exceptions, and so error handling and ensuring we close
+  // the DB is a bit finnicky. Keep track of how far we got so we guarantee closing
+  // the right things
+  bool instanceCreated, sessionCreated, dbOpened, tableOpened;
+
+  char16_t* dbPath = ToNewUnicode(aDBPath);
+
+  // Check for the right page size and initialize with that
+  unsigned long pageSize;
+  err = JetGetDatabaseFileInfoW(dbPath, &pageSize, sizeof(pageSize), JET_DbInfoPageSize);
+  NS_HANDLE_JET_ERROR(err)
+  err = JetSetSystemParameter(&instance, NULL, JET_paramDatabasePageSize, pageSize, NULL);
+  NS_HANDLE_JET_ERROR(err)
+
+  // Turn off recovery, because otherwise we will create log files in either the cwd or
+  // overwrite Edge's own logfiles, which is useless at best and at worst might mess with
+  // Edge actually using the DB
+  err = JetSetSystemParameter(&instance, NULL, JET_paramRecovery, NULL, "Off");
+  NS_HANDLE_JET_ERROR(err)
+
+  // Start our session:
+  err = JetCreateInstance(&instance, "edge_readinglist_migration");
+  NS_HANDLE_JET_ERROR(err)
+  instanceCreated = true;
+
+  err = JetInit(&instance);
+  NS_HANDLE_JET_ERROR(err)
+  err = JetBeginSession(instance, &sesid, 0, 0);
+  NS_HANDLE_JET_ERROR(err)
+  sessionCreated = true;
+
+  // Actually open the DB, and make sure to do so readonly:
+  err = JetAttachDatabaseW(sesid, dbPath, JET_bitDbReadOnly);
+  NS_HANDLE_JET_ERROR(err)
+  dbOpened = true;
+  err = JetOpenDatabaseW(sesid, dbPath, NULL, &dbid, JET_bitDbReadOnly);
+  NS_HANDLE_JET_ERROR(err)
+
+  // Open the readinglist table and get information on the columns we are interested in:
+  err = JetOpenTable(sesid, dbid, "ReadingList", NULL, 0, JET_bitTableReadOnly, &tableid);
+  NS_HANDLE_JET_ERROR(err)
+  tableOpened = true;
+  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "URL", &urlColumnInfo,
+                         sizeof(urlColumnInfo), JET_ColInfo);
+  NS_HANDLE_JET_ERROR(err)
+  if (urlColumnInfo.cbMax > MAX_URL_LENGTH) {
+    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (consoleService) {
+      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: URL column size increased").get());
+    }
+  }
+  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "Title", &titleColumnInfo,
+                         sizeof(titleColumnInfo), JET_ColInfo);
+  NS_HANDLE_JET_ERROR(err)
+  if (titleColumnInfo.cbMax > MAX_TITLE_LENGTH) {
+    nsCOMPtr<nsIConsoleService> consoleService = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+    if (consoleService) {
+      consoleService->LogStringMessage(NS_LITERAL_STRING("Edge migration: Title column size increased").get());
+    }
+  }
+  err = JetGetColumnInfo(sesid, dbid, "ReadingList", "AddedDate", &addedDateColumnInfo,
+                         sizeof(addedDateColumnInfo), JET_ColInfo);
+  NS_HANDLE_JET_ERROR(err)
+
+  // verify the column types are what we expect:
+  if (urlColumnInfo.coltyp != JET_coltypLongText ||
+      titleColumnInfo.coltyp != JET_coltypLongText ||
+      addedDateColumnInfo.coltyp != JET_coltypLongLong) {
+    rv = NS_ERROR_NOT_IMPLEMENTED;
+    goto CloseDB;
+  }
+
+  JET_COLUMNID urlColumnId = urlColumnInfo.columnid;
+  JET_COLUMNID titleColumnId = titleColumnInfo.columnid;
+  JET_COLUMNID addedDateColumnId = addedDateColumnInfo.columnid;
+
+  // If we got here, we've found our table and column information
+
+  err = JetMove(sesid, tableid, JET_MoveFirst, 0);
+  // It's possible there are 0 items in this table, in which case we want to
+  // not fail:
+  if (err == JET_errNoCurrentRecord) {
+    items.forget(aItems);
+    goto CloseDB;
+  }
+  // Check for any other errors
+  NS_HANDLE_JET_ERROR(err)
+
+  FILETIME addedDate;
+  wchar_t urlBuffer[MAX_URL_LENGTH] = { 0 };
+  wchar_t titleBuffer[MAX_TITLE_LENGTH] = { 0 };
+  do {
+    err = JetRetrieveColumn(sesid, tableid, urlColumnId, &urlBuffer,
+                            sizeof(urlBuffer), NULL, 0, NULL);
+    NS_HANDLE_JET_ERROR(err)
+    err = JetRetrieveColumn(sesid, tableid, titleColumnId, &titleBuffer,
+                            sizeof(titleBuffer), NULL, 0, NULL);
+    NS_HANDLE_JET_ERROR(err)
+    err = JetRetrieveColumn(sesid, tableid, addedDateColumnId, &addedDate,
+                            sizeof(addedDate), NULL, 0, NULL);
+    NS_HANDLE_JET_ERROR(err)
+    nsCOMPtr<nsIWritablePropertyBag2> pbag = do_CreateInstance("@mozilla.org/hash-property-bag;1");
+    bool dateIsValid;
+    PRTime prAddedDate = WinMigrationFileTimeToPRTime(&addedDate, &dateIsValid);
+    nsDependentString url(urlBuffer);
+    nsDependentString title(titleBuffer);
+    pbag->SetPropertyAsAString(NS_LITERAL_STRING("uri"), url);
+    pbag->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
+    if (dateIsValid) {
+      pbag->SetPropertyAsInt64(NS_LITERAL_STRING("time"), prAddedDate);
+    }
+    items->AppendElement(pbag, false);
+    memset(urlBuffer, 0, sizeof(urlBuffer));
+    memset(titleBuffer, 0, sizeof(titleBuffer));
+  } while (JET_errSuccess == JetMove(sesid, tableid, JET_MoveNext, 0));
+
+  items.forget(aItems);
+
+CloseDB:
+  // Terminate ESENT. This performs a clean shutdown.
+  // Ignore errors while closing:
+  if (tableOpened)
+    JetCloseTable(sesid, tableid);
+  if (dbOpened)
+    JetCloseDatabase(sesid, dbid, 0);
+  if (sessionCreated)
+    JetEndSession(sesid, 0);
+  if (instanceCreated)
+    JetTerm(instance);
+
+  return rv;
+}
+
+nsresult
+nsEdgeReadingListExtractor::ConvertJETError(const JET_ERR &aError)
+{
+  switch (aError) {
+    case JET_errPageSizeMismatch:
+    case JET_errInvalidName:
+    case JET_errColumnNotFound:
+      // The DB format has changed and we haven't updated this migration code:
+      return NS_ERROR_NOT_IMPLEMENTED;
+    case JET_errDatabaseLocked:
+      return NS_ERROR_FILE_IS_LOCKED;
+    case JET_errPermissionDenied:
+    case JET_errAccessDenied:
+      return NS_ERROR_FILE_ACCESS_DENIED;
+    case JET_errInvalidFilename:
+      return NS_ERROR_FILE_INVALID_PATH;
+    case JET_errFileNotFound:
+      return NS_ERROR_FILE_NOT_FOUND;
+    default:
+      return NS_ERROR_FAILURE;
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/nsEdgeReadingListExtractor.h
@@ -0,0 +1,31 @@
+/* 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/. */
+
+#ifndef edgereadinglistextractor__h__
+#define edgereadinglistextractor__h__
+
+#include "nsIArray.h"
+#include "nsIEdgeReadingListExtractor.h"
+
+// To get access to the long data types, we need to use at least the Vista version of the JET APIs
+#undef JET_VERSION
+#define JET_VERSION 0x0600
+#include <esent.h>
+
+class nsEdgeReadingListExtractor final : public nsIEdgeReadingListExtractor
+{
+public:
+  nsEdgeReadingListExtractor() {}
+
+  NS_DECL_ISUPPORTS
+
+  NS_DECL_NSIEDGEREADINGLISTEXTRACTOR
+
+private:
+  ~nsEdgeReadingListExtractor() {}
+
+  nsresult ConvertJETError(const JET_ERR &err);
+};
+
+#endif
--- a/browser/components/migration/nsIEHistoryEnumerator.cpp
+++ b/browser/components/migration/nsIEHistoryEnumerator.cpp
@@ -2,47 +2,23 @@
  * 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/. */
 
 #include "nsIEHistoryEnumerator.h"
 
 #include <urlhist.h>
 #include <shlguid.h>
 
-#include "nsStringAPI.h"
-#include "nsNetUtil.h"
-#include "prtime.h"
-#include "nsIVariant.h"
+#include "nsArrayEnumerator.h"
 #include "nsCOMArray.h"
-#include "nsArrayEnumerator.h"
-
-namespace {
-
-  PRTime FileTimeToPRTime(FILETIME* filetime)
-  {
-    SYSTEMTIME st;
-    ::FileTimeToSystemTime(filetime, &st);
-    PRExplodedTime prt;
-    prt.tm_year = st.wYear;
-    // SYSTEMTIME's day-of-month parameter is 1-based,
-    // PRExplodedTime's is 0-based.
-    prt.tm_month = st.wMonth - 1;
-    prt.tm_mday = st.wDay;
-    prt.tm_hour = st.wHour;
-    prt.tm_min = st.wMinute;
-    prt.tm_sec = st.wSecond;
-    prt.tm_usec = st.wMilliseconds * 1000;
-    prt.tm_wday = 0;
-    prt.tm_yday = 0;
-    prt.tm_params.tp_gmt_offset = 0;
-    prt.tm_params.tp_dst_offset = 0;
-    return PR_ImplodeTime(&prt);
-  }
-
-} // namespace
+#include "nsIVariant.h"
+#include "nsNetUtil.h"
+#include "nsStringAPI.h"
+#include "nsWindowsMigrationUtils.h"
+#include "prtime.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 //// nsIEHistoryEnumerator
 
 NS_IMPL_ISUPPORTS(nsIEHistoryEnumerator, nsISimpleEnumerator)
 
 nsIEHistoryEnumerator::nsIEHistoryEnumerator()
 {
@@ -101,24 +77,27 @@ nsIEHistoryEnumerator::HasMoreElements(b
     if (NS_FAILED(rv)) {
       // Got a corrupt or invalid URI, continue to the next entry.
       return HasMoreElements(_retval);
     }
   }
 
   nsDependentString title(statURL.pwcsTitle);
 
-  PRTime lastVisited = FileTimeToPRTime(&(statURL.ftLastVisited));
+  bool lastVisitTimeIsValid;
+  PRTime lastVisited = WinMigrationFileTimeToPRTime(&(statURL.ftLastVisited), &lastVisitTimeIsValid);
 
   mCachedNextEntry = do_CreateInstance("@mozilla.org/hash-property-bag;1");
   MOZ_ASSERT(mCachedNextEntry, "Should have instanced a new property bag");
   if (mCachedNextEntry) {
     mCachedNextEntry->SetPropertyAsInterface(NS_LITERAL_STRING("uri"), uri);
     mCachedNextEntry->SetPropertyAsAString(NS_LITERAL_STRING("title"), title);
-    mCachedNextEntry->SetPropertyAsInt64(NS_LITERAL_STRING("time"), lastVisited);
+    if (lastVisitTimeIsValid) {
+      mCachedNextEntry->SetPropertyAsInt64(NS_LITERAL_STRING("time"), lastVisited);
+    }
 
     *_retval = true;
   }
 
   if (statURL.pwcsTitle)
     ::CoTaskMemFree(statURL.pwcsTitle);
 
   return NS_OK;
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/nsIEdgeReadingListExtractor.idl
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIArray;
+
+[scriptable, uuid(bfdef4aa-dcd1-4d31-b5d9-188fe8d98623)]
+interface nsIEdgeReadingListExtractor : nsISupports
+{
+  /**
+   * Import data from the database indicated by the databasePath
+   * May fail if the path is invalid, unreadable, the database is corrupt,
+   * or the data in the database is not in the format we expect.
+   *
+   * @param  databasePath the absolute path to the database we'd like to import
+   * @return an enumerator of nsIPropertyBag2 items that each have a URL, title, and
+   *         creation dates.
+   */
+  nsIArray extract(in DOMString databasePath);
+};
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/nsWindowsMigrationUtils.h
@@ -0,0 +1,36 @@
+/* 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/. */
+
+#ifndef windowsmigrationutils__h__
+#define windowsmigrationutils__h__
+
+#include "prtime.h"
+
+static
+PRTime WinMigrationFileTimeToPRTime(FILETIME* filetime, bool* isValid)
+{
+  SYSTEMTIME st;
+  *isValid = ::FileTimeToSystemTime(filetime, &st);
+  if (!*isValid) {
+    return 0;
+  }
+  PRExplodedTime prt;
+  prt.tm_year = st.wYear;
+  // SYSTEMTIME's day-of-month parameter is 1-based,
+  // PRExplodedTime's is 0-based.
+  prt.tm_month = st.wMonth - 1;
+  prt.tm_mday = st.wDay;
+  prt.tm_hour = st.wHour;
+  prt.tm_min = st.wMinute;
+  prt.tm_sec = st.wSecond;
+  prt.tm_usec = st.wMilliseconds * 1000;
+  prt.tm_wday = 0;
+  prt.tm_yday = 0;
+  prt.tm_params.tp_gmt_offset = 0;
+  prt.tm_params.tp_dst_offset = 0;
+  return PR_ImplodeTime(&prt);
+}
+
+#endif
+
--- a/browser/components/shell/nsGNOMEShellService.cpp
+++ b/browser/components/shell/nsGNOMEShellService.cpp
@@ -312,32 +312,88 @@ nsGNOMEShellService::SetDefaultBrowser(b
         appInfo->SetAsDefaultForFileExtensions(nsDependentCString(appTypes[i].extensions));
       }
     }
   }
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs) {
     (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+    // Reset the number of times the dialog should be shown
+    // before it is silenced.
+    (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsGNOMEShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  nsresult rv;
+  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = prefs->GetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, aResult);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (*aResult) {
+    // Only skip the default browser check once. The next attempt in
+    // a new session should proceed.
+    return prefs->SetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, false);
+  }
+
+  int32_t defaultBrowserCheckCount;
+  rv = prefs->GetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                         &defaultBrowserCheckCount);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (defaultBrowserCheckCount < 3) {
+    *aResult = false;
+    return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                             defaultBrowserCheckCount + 1);
+  }
+
+  // Disable the default browser check after three attempts.
+  // Don't modify PREF_CHECKDEFAULTBROWSER since that is a
+  // user-initiated action and it shouldn't get re-enabled
+  // if it has been user disabled.
+  *aResult = true;
+  return rv;
+}
+
+NS_IMETHODIMP
 nsGNOMEShellService::GetShouldCheckDefaultBrowser(bool* aResult)
 {
   // If we've already checked, the browser has been started and this is a 
   // new window open, and we don't want to check again.
   if (mCheckedThisSession) {
     *aResult = false;
     return NS_OK;
   }
 
   nsresult rv;
+#ifndef RELEASE_BUILD
+  bool skipDefaultBrowserCheck;
+  rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (skipDefaultBrowserCheck) {
+    *aResult = false;
+    return rv;
+  }
+#endif
+
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
 }
 
--- a/browser/components/shell/nsIShellService.idl
+++ b/browser/components/shell/nsIShellService.idl
@@ -3,17 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIDOMElement;
 interface nsIFile;
 
-[scriptable, uuid(99d2e9f1-3c86-40f7-81fd-3060c18489f0)]
+[scriptable, uuid(55cb78a8-2fc4-48f4-9345-ff0e541c5cc4)]
 interface nsIShellService : nsISupports
 {
   /**
    * Determines whether or not Firefox is the "Default Browser."
    * This is simply whether or not Firefox is registered to handle
    * http links.
    *
    * @param aStartupCheck true if this is the check being performed
@@ -33,25 +33,32 @@ interface nsIShellService : nsISupports
    *                       additional protocols (ftp, chrome etc)
    *                       and web documents (.html, .xhtml etc).
    * @param aForAllUsers   Whether or not Firefox should attempt
    *                       to become the default browser for all
    *                       users on a multi-user system. 
    */
   void setDefaultBrowser(in boolean aClaimAllTypes, in boolean aForAllUsers);
 
-  /** 
+  /**
    * Used to determine whether or not to show a "Set Default Browser"
    * query dialog. This attribute is true if the application is starting
    * up and "browser.shell.checkDefaultBrowser" is true, otherwise it
    * is false.
    */
   attribute boolean shouldCheckDefaultBrowser;
 
   /**
+   * Used to determine whether or not the "Set Default Browser" check
+   * should be skipped during first-run or after the browser has been
+   * run a few times.
+   */
+  readonly attribute boolean shouldSkipCheckDefaultBrowser;
+
+  /**
    * Used to determine whether or not to offer "Set as desktop background"
    * functionality. Even if shell service is available it is not
    * guaranteed that it is able to set the background for every desktop
    * which is especially true for Linux with its many different desktop
    * environments.
    */
   readonly attribute boolean canSetDesktopBackground;
 
--- a/browser/components/shell/nsMacShellService.cpp
+++ b/browser/components/shell/nsMacShellService.cpp
@@ -90,32 +90,88 @@ nsMacShellService::SetDefaultBrowser(boo
     if (::LSSetDefaultRoleHandlerForContentType(kUTTypeHTML, kLSRolesAll, firefoxID) != noErr) {
       return NS_ERROR_FAILURE;
     }
   }
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs) {
     (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+    // Reset the number of times the dialog should be shown
+    // before it is silenced.
+    (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsMacShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  nsresult rv;
+  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  rv = prefs->GetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, aResult);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (*aResult) {
+    // Only skip the default browser check once. The next attempt in
+    // a new session should proceed.
+    return prefs->SetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, false);
+  }
+
+  int32_t defaultBrowserCheckCount;
+  rv = prefs->GetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                         &defaultBrowserCheckCount);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (defaultBrowserCheckCount < 3) {
+    *aResult = false;
+    return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                             defaultBrowserCheckCount + 1);
+  }
+
+  // Disable the default browser check after three attempts.
+  // Don't modify PREF_CHECKDEFAULTBROWSER since that is a
+  // user-initiated action and it shouldn't get re-enabled
+  // if it has been user disabled.
+  *aResult = true;
+  return rv;
+}
+
+NS_IMETHODIMP
 nsMacShellService::GetShouldCheckDefaultBrowser(bool* aResult)
 {
   // If we've already checked, the browser has been started and this is a 
   // new window open, and we don't want to check again.
   if (mCheckedThisSession) {
     *aResult = false;
     return NS_OK;
   }
 
   nsresult rv;
+#ifndef RELEASE_BUILD
+  bool skipDefaultBrowserCheck;
+  rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (skipDefaultBrowserCheck) {
+    *aResult = false;
+    return rv;
+  }
+#endif
+
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
 }
 
--- a/browser/components/shell/nsShellService.h
+++ b/browser/components/shell/nsShellService.h
@@ -1,10 +1,12 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
 
 #define PREF_CHECKDEFAULTBROWSER "browser.shell.checkDefaultBrowser"
+#define PREF_SKIPDEFAULTBROWSERCHECK "browser.shell.skipDefaultBrowserCheck"
+#define PREF_DEFAULTBROWSERCHECKCOUNT "browser.shell.defaultBrowserCheckCount"
 
 #define SHELLSERVICE_PROPERTIES "chrome://browser/locale/shellservice.properties"
 #define BRAND_PROPERTIES "chrome://branding/locale/brand.properties"
 
--- a/browser/components/shell/nsWindowsShellService.cpp
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -971,34 +971,90 @@ nsWindowsShellService::SetDefaultBrowser
       bool isDefault;
       SaveWin8RegistryHashes(aClaimAllTypes, &isDefault);
     }
   }
 
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID));
   if (prefs) {
     (void) prefs->SetBoolPref(PREF_CHECKDEFAULTBROWSER, true);
+    // Reset the number of times the dialog should be shown
+    // before it is silenced.
+    (void) prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT, 0);
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsWindowsShellService::GetShouldSkipCheckDefaultBrowser(bool* aResult)
+{
+  NS_ENSURE_ARG_POINTER(aResult);
+
+  nsresult rv;
+  nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+  if (NS_FAILED(rv)) {
+    return rv;
   }
 
+  rv = prefs->GetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, aResult);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (*aResult) {
+    // Only skip the default browser check once. The next attempt in
+    // a new session should proceed.
+    return prefs->SetBoolPref(PREF_SKIPDEFAULTBROWSERCHECK, false);
+  }
+
+  int32_t defaultBrowserCheckCount;
+  rv = prefs->GetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                         &defaultBrowserCheckCount);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (defaultBrowserCheckCount < 3) {
+    *aResult = false;
+    return prefs->SetIntPref(PREF_DEFAULTBROWSERCHECKCOUNT,
+                             defaultBrowserCheckCount + 1);
+  }
+
+  // Disable the default browser check after three attempts.
+  // Don't modify PREF_CHECKDEFAULTBROWSER since that is a
+  // user-initiated action and it shouldn't get re-enabled
+  // if it has been user disabled.
+  *aResult = true;
   return rv;
 }
 
 NS_IMETHODIMP
 nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult)
 {
   NS_ENSURE_ARG_POINTER(aResult);
 
-  // If we've already checked, the browser has been started and this is a 
+  // If we've already checked, the browser has been started and this is a
   // new window open, and we don't want to check again.
   if (mCheckedThisSession) {
     *aResult = false;
     return NS_OK;
   }
 
   nsresult rv;
+#ifndef RELEASE_BUILD
+  bool skipDefaultBrowserCheck;
+  rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (skipDefaultBrowserCheck) {
+    *aResult = false;
+    return rv;
+  }
+#endif
+
   nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return prefs->GetBoolPref(PREF_CHECKDEFAULTBROWSER, aResult);
 }
 
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -103,19 +103,17 @@ browser.jar:
     content/browser/devtools/performance/views/toolbar.js              (performance/views/toolbar.js)
     content/browser/devtools/performance/views/details.js              (performance/views/details.js)
     content/browser/devtools/performance/views/details-subview.js      (performance/views/details-abstract-subview.js)
     content/browser/devtools/performance/views/details-waterfall.js    (performance/views/details-waterfall.js)
     content/browser/devtools/performance/views/details-js-call-tree.js      (performance/views/details-js-call-tree.js)
     content/browser/devtools/performance/views/details-js-flamegraph.js     (performance/views/details-js-flamegraph.js)
     content/browser/devtools/performance/views/details-memory-call-tree.js  (performance/views/details-memory-call-tree.js)
     content/browser/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
-    content/browser/devtools/performance/views/details-optimizations.js     (performance/views/details-optimizations.js)
     content/browser/devtools/performance/views/optimizations-list.js        (performance/views/optimizations-list.js)
-    content/browser/devtools/performance/views/frames-list.js               (performance/views/frames-list.js)
     content/browser/devtools/performance/views/recordings.js           (performance/views/recordings.js)
     content/browser/devtools/promisedebugger/promise-debugger.js       (promisedebugger/promise-debugger.js)
     content/browser/devtools/promisedebugger/promise-debugger.xhtml    (promisedebugger/promise-debugger.xhtml)
     content/browser/devtools/commandline.css                           (commandline/commandline.css)
     content/browser/devtools/commandlineoutput.xhtml                   (commandline/commandlineoutput.xhtml)
     content/browser/devtools/commandlinetooltip.xhtml                  (commandline/commandlinetooltip.xhtml)
 *   content/browser/devtools/framework/toolbox-window.xul              (framework/toolbox-window.xul)
     content/browser/devtools/framework/toolbox-options.xul             (framework/toolbox-options.xul)
--- a/browser/devtools/performance/modules/logic/marker-utils.js
+++ b/browser/devtools/performance/modules/logic/marker-utils.js
@@ -349,23 +349,26 @@ const Formatters = {
   /**
    * Uses the marker name as the label for markers that do not have
    * a blueprint entry. Uses "Other" in the marker filter menu.
    */
   UnknownLabel: function (marker={}) {
     return marker.name || L10N.getStr("marker.label.unknown");
   },
 
-  GCLabel: function (marker={}) {
+  GCLabel: function (marker) {
+    if (!marker) {
+      return L10N.getStr("marker.label.garbageCollection2");
+    }
     // Only if a `nonincrementalReason` exists, do we want to label
     // this as a non incremental GC event.
     if ("nonincrementalReason" in marker) {
       return L10N.getStr("marker.label.garbageCollection.nonIncremental");
     }
-    return L10N.getStr("marker.label.garbageCollection");
+    return L10N.getStr("marker.label.garbageCollection.incremental");
   },
 
   JSLabel: function (marker={}) {
     let generic = L10N.getStr("marker.label.javascript");
     if ("causeName" in marker) {
       return JS_MARKER_MAP[marker.causeName] || generic;
     }
     return generic;
--- a/browser/devtools/performance/modules/widgets/tree-view.js
+++ b/browser/devtools/performance/modules/widgets/tree-view.js
@@ -8,46 +8,85 @@
  * received from the proviler in a tree-like structure.
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { L10N } = require("devtools/performance/global");
 const { Heritage } = require("resource:///modules/devtools/ViewHelpers.jsm");
 const { AbstractTreeItem } = require("resource:///modules/devtools/AbstractTreeItem.jsm");
 
-const MILLISECOND_UNITS = L10N.getStr("table.ms");
-const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
 const URL_LABEL_TOOLTIP = L10N.getStr("table.url.tooltiptext");
-const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr("table.view-optimizations.tooltiptext");
+const VIEW_OPTIMIZATIONS_TOOLTIP = L10N.getStr("table.view-optimizations.tooltiptext2");
 
 const CALL_TREE_INDENTATION = 16; // px
 
+// Used for rendering values in cells
+const FORMATTERS = {
+  TIME: (value) => L10N.getFormatStr("table.ms2", L10N.numberWithDecimals(value, 2)),
+  PERCENT: (value) => L10N.getFormatStr("table.percentage2", L10N.numberWithDecimals(value, 2)),
+  NUMBER: (value) => value || 0,
+  BYTESIZE: (value) => L10N.getFormatStr("table.bytes", (value || 0))
+};
+
+/**
+ * Definitions for rendering cells. Triads of class name, property name from
+ * `frame.getInfo()`, and a formatter function.
+ */
+const CELLS = {
+  duration:       ["duration", "totalDuration", FORMATTERS.TIME],
+  percentage:     ["percentage", "totalPercentage", FORMATTERS.PERCENT],
+  selfDuration:   ["self-duration", "selfDuration", FORMATTERS.TIME],
+  selfPercentage: ["self-percentage", "selfPercentage", FORMATTERS.PERCENT],
+  samples:        ["samples", "samples", FORMATTERS.NUMBER],
+
+  selfSize:            ["self-size", "selfSize", FORMATTERS.BYTESIZE],
+  selfSizePercentage:  ["self-size-percentage", "selfSizePercentage", FORMATTERS.PERCENT],
+  selfCount:           ["self-count", "selfCount", FORMATTERS.NUMBER],
+  selfCountPercentage: ["self-count-percentage", "selfCountPercentage", FORMATTERS.PERCENT],
+  size:                ["size", "totalSize", FORMATTERS.BYTESIZE],
+  sizePercentage:      ["size-percentage", "totalSizePercentage", FORMATTERS.PERCENT],
+  count:               ["count", "totalCount", FORMATTERS.NUMBER],
+  countPercentage:     ["count-percentage", "totalCountPercentage", FORMATTERS.PERCENT],
+};
+const CELL_TYPES = Object.keys(CELLS);
+
 const DEFAULT_SORTING_PREDICATE = (frameA, frameB) => {
   let dataA = frameA.getDisplayedData();
   let dataB = frameB.getDisplayedData();
-  if (this.inverted) {
-    // Invert trees, sort by selfPercentage, and then totalPercentage
-    if (dataA.selfPercentage === dataB.selfPercentage) {
-      return dataA.totalPercentage < dataB.totalPercentage ? 1 : -1;
-    }
-    return dataA.selfPercentage < dataB.selfPercentage ? 1 : - 1;
+  let isAllocations = "totalSize" in dataA;
+
+  if (isAllocations) {
+    return this.inverted && dataA.selfSize !== dataB.selfSize ?
+           (dataA.selfSize < dataB.selfSize ? 1 : - 1) :
+           (dataA.totalSize < dataB.totalSize ? 1 : -1);
   }
-  return dataA.totalPercentage < dataB.totalPercentage ? 1 : -1;
+
+  return this.inverted && dataA.selfPercentage !== dataB.selfPercentage ?
+         (dataA.selfPercentage < dataB.selfPercentage ? 1 : - 1) :
+         (dataA.totalPercentage < dataB.totalPercentage ? 1 : -1);
 };
 
 const DEFAULT_AUTO_EXPAND_DEPTH = 3; // depth
 const DEFAULT_VISIBLE_CELLS = {
   duration: true,
   percentage: true,
-  count: false,
   selfDuration: true,
   selfPercentage: true,
+  samples: true,
+  function: true,
+
+  // allocation columns
+  count: false,
   selfCount: false,
-  samples: true,
-  function: true
+  size: false,
+  selfSize: false,
+  countPercentage: false,
+  selfCountPercentage: false,
+  sizePercentage: false,
+  selfSizePercentage: false,
 };
 
 const clamp = (val, min, max) => Math.max(min, Math.min(max, val));
 const sum = vals => vals.reduce((a, b) => a + b, 0);
 
 /**
  * An item in a call tree view, which looks like this:
  *
@@ -131,37 +170,24 @@ CallView.prototype = Heritage.extend(Abs
    * @param nsIDOMNode document
    * @param nsIDOMNode arrowNode
    * @return nsIDOMNode
    */
   _displaySelf: function(document, arrowNode) {
     let frameInfo = this.getDisplayedData();
     let cells = [];
 
-    if (this.visibleCells.duration) {
-      cells.push(this._createTimeCell(document, frameInfo.totalDuration));
-    }
-    if (this.visibleCells.percentage) {
-      cells.push(this._createExecutionCell(document, frameInfo.totalPercentage));
-    }
-    if (this.visibleCells.count) {
-      cells.push(this._createCountCell(document, frameInfo.totalCount));
+    for (let type of CELL_TYPES) {
+      if (this.visibleCells[type]) {
+        // Inline for speed, but pass in the formatted value via
+        // cell definition, as well as the element type.
+        cells.push(this._createCell(document, CELLS[type][2](frameInfo[CELLS[type][1]]), CELLS[type][0]));
+      }
     }
-    if (this.visibleCells.selfDuration) {
-      cells.push(this._createTimeCell(document, frameInfo.selfDuration, true));
-    }
-    if (this.visibleCells.selfPercentage) {
-      cells.push(this._createExecutionCell(document, frameInfo.selfPercentage, true));
-    }
-    if (this.visibleCells.selfCount) {
-      cells.push(this._createCountCell(document, frameInfo.selfCount, true));
-    }
-    if (this.visibleCells.samples) {
-      cells.push(this._createSamplesCell(document, frameInfo.samples));
-    }
+
     if (this.visibleCells.function) {
       cells.push(this._createFunctionCell(document, arrowNode, frameInfo.name, frameInfo, this.level));
     }
 
     let targetNode = document.createElement("hbox");
     targetNode.className = "call-tree-item";
     targetNode.setAttribute("origin", frameInfo.isContent ? "content" : "chrome");
     targetNode.setAttribute("category", frameInfo.categoryData.abbrev || "");
@@ -199,61 +225,36 @@ CallView.prototype = Heritage.extend(Abs
     // if no other sorting predicate was specified on this on the root item.
     children.sort(this.sortingPredicate.bind(this));
   },
 
   /**
    * Functions creating each cell in this call view.
    * Invoked by `_displaySelf`.
    */
-  _createTimeCell: function(doc, duration, isSelf = false) {
+  _createCell: function (doc, value, type) {
     let cell = doc.createElement("description");
     cell.className = "plain call-tree-cell";
-    cell.setAttribute("type", isSelf ? "self-duration" : "duration");
+    cell.setAttribute("type", type);
     cell.setAttribute("crop", "end");
-    cell.setAttribute("value", L10N.numberWithDecimals(duration, 2) + " " + MILLISECOND_UNITS);
-    return cell;
-  },
-  _createExecutionCell: function(doc, percentage, isSelf = false) {
-    let cell = doc.createElement("description");
-    cell.className = "plain call-tree-cell";
-    cell.setAttribute("type", isSelf ? "self-percentage" : "percentage");
-    cell.setAttribute("crop", "end");
-    cell.setAttribute("value", L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS);
+    cell.setAttribute("value", value);
     return cell;
   },
-  _createCountCell: function(doc, count, isSelf = false) {
-    let cell = doc.createElement("description");
-    cell.className = "plain call-tree-cell";
-    cell.setAttribute("type", isSelf ? "self-count" : "count");
-    cell.setAttribute("crop", "end");
-    cell.setAttribute("value", count || 0);
-    return cell;
-  },
-  _createSamplesCell: function(doc, count) {
-    let cell = doc.createElement("description");
-    cell.className = "plain call-tree-cell";
-    cell.setAttribute("type", "samples");
-    cell.setAttribute("crop", "end");
-    cell.setAttribute("value", count || 0);
-    return cell;
-  },
+
   _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) {
     let cell = doc.createElement("hbox");
     cell.className = "call-tree-cell";
     cell.style.MozMarginStart = (frameLevel * CALL_TREE_INDENTATION) + "px";
     cell.setAttribute("type", "function");
     cell.appendChild(arrowNode);
 
-    // Render optimization link to JIT view if the frame
-    // has optimizations
+    // Render optimization hint if this frame has opt data.
     if (this.root.showOptimizationHint && frameInfo.hasOptimizations && !frameInfo.isMetaCategory) {
       let icon = doc.createElement("description");
       icon.setAttribute("tooltiptext", VIEW_OPTIMIZATIONS_TOOLTIP);
-      icon.setAttribute("type", "linkable");
       icon.className = "opt-icon";
       cell.appendChild(icon);
     }
 
     // Don't render a name label node if there's no function name. A different
     // location label node will be rendered instead.
     if (frameName) {
       let nameNode = doc.createElement("description");
--- a/browser/devtools/performance/performance-controller.js
+++ b/browser/devtools/performance/performance-controller.js
@@ -111,17 +111,17 @@ const EVENTS = {
   RECORDING_EXPORTED: "Performance:RecordingExported",
 
   // When the front has updated information on the profiler's circular buffer
   PROFILER_STATUS_UPDATED: "Performance:BufferUpdated",
 
   // When the PerformanceView updates the display of the buffer status
   UI_BUFFER_STATUS_UPDATED: "Performance:UI:BufferUpdated",
 
-  // Emitted by the JITOptimizationsView when it renders new optimization
+  // Emitted by the OptimizationsListView when it renders new optimization
   // data and clears the optimization data
   OPTIMIZATIONS_RESET: "Performance:UI:OptimizationsReset",
   OPTIMIZATIONS_RENDERED: "Performance:UI:OptimizationsRendered",
 
   // Emitted by the OverviewView when more data has been rendered
   OVERVIEW_RENDERED: "Performance:UI:OverviewRendered",
   FRAMERATE_GRAPH_RENDERED: "Performance:UI:OverviewFramerateRendered",
   MARKERS_GRAPH_RENDERED: "Performance:UI:OverviewMarkersRendered",
--- a/browser/devtools/performance/performance.xul
+++ b/browser/devtools/performance/performance.xul
@@ -19,21 +19,19 @@
   <script type="application/javascript" src="performance/views/overview.js"/>
   <script type="application/javascript" src="performance/views/toolbar.js"/>
   <script type="application/javascript" src="performance/views/details-subview.js"/>
   <script type="application/javascript" src="performance/views/details-waterfall.js"/>
   <script type="application/javascript" src="performance/views/details-js-call-tree.js"/>
   <script type="application/javascript" src="performance/views/details-js-flamegraph.js"/>
   <script type="application/javascript" src="performance/views/details-memory-call-tree.js"/>
   <script type="application/javascript" src="performance/views/details-memory-flamegraph.js"/>
-  <script type="application/javascript" src="performance/views/details-optimizations.js"/>
   <script type="application/javascript" src="performance/views/details.js"/>
   <script type="application/javascript" src="performance/views/recordings.js"/>
   <script type="application/javascript" src="performance/views/optimizations-list.js"/>
-  <script type="application/javascript" src="performance/views/frames-list.js"/>
 
   <popupset id="performance-options-popupset">
     <menupopup id="performance-filter-menupopup"/>
     <menupopup id="performance-options-menupopup" position="before_end">
       <menuitem id="option-show-platform-data"
                 type="checkbox"
                 data-pref="show-platform-data"
                 label="&performanceUI.showPlatformData;"
@@ -139,21 +137,16 @@
                          hidden="true"
                          data-view="memory-calltree"
                          tooltiptext="&performanceUI.toolbar.allocations.tooltiptext;" />
           <toolbarbutton id="select-memory-flamegraph-view"
                          class="devtools-toolbarbutton devtools-button"
                          label="&performanceUI.toolbar.memory-flamegraph;"
                          hidden="true"
                          data-view="memory-flamegraph" />
-          <toolbarbutton id="select-optimizations-view"
-                         class="devtools-toolbarbutton devtools-button"
-                         label="Optimizations"
-                         hidden="true"
-                         data-view="optimizations" />
         </hbox>
         <spacer flex="1"></spacer>
         <hbox id="performance-toolbar-controls-options"
               class="devtools-toolbarbutton-group">
           <toolbarbutton id="performance-options-button"
                          class="devtools-toolbarbutton devtools-option-toolbarbutton"
                          popup="performance-options-menupopup"
                          tooltiptext="&performanceUI.options.gear.tooltiptext;"/>
@@ -293,86 +286,89 @@
                     <label class="plain call-tree-header"
                            type="function"
                            crop="end"
                            value="&performanceUI.table.function;"
                            tooltiptext="&performanceUI.table.function.tooltip;"/>
                   </hbox>
                   <vbox class="call-tree-cells-container" flex="1"/>
                 </vbox>
+                <splitter class="devtools-side-splitter"/>
+                <!-- Optimizations Panel -->
+                <vbox id="jit-optimizations-view"
+                      class="hidden">
+                  <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
+                    <hbox id="jit-optimizations-header">
+                      <span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
+                      <span class="header-function-name" />
+                      <span class="header-file opt-url debugger-link" />
+                      <span class="header-line opt-line" />
+                    </hbox>
+                  </toolbar>
+                  <vbox id="jit-optimizations-raw-view"></vbox>
+                </vbox>
               </hbox>
 
               <!-- JS FlameChart -->
               <hbox id="js-flamegraph-view" flex="1">
               </hbox>
 
               <!-- Memory Tree -->
               <vbox id="memory-calltree-view" flex="1">
                 <hbox class="call-tree-headers-container">
                   <label class="plain call-tree-header"
-                         type="count"
+                         type="self-size"
                          crop="end"
-                         value="&performanceUI.table.totalAlloc;"
+                         value="Self Bytes"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="self-size-percentage"
+                         crop="end"
+                         value="Self Bytes %"
                          tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
                   <label class="plain call-tree-header"
                          type="self-count"
                          crop="end"
-                         value="&performanceUI.table.selfAlloc;"
-                         tooltiptext="&performanceUI.table.selfAlloc.tooltip;"/>
+                         value="Self Count"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="self-count-percentage"
+                         crop="end"
+                         value="Self Count %"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="size"
+                         crop="end"
+                         value="Total Size"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="size-percentage"
+                         crop="end"
+                         value="Total Size %"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="count"
+                         crop="end"
+                         value="Total Count"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
+                  <label class="plain call-tree-header"
+                         type="count-percentage"
+                         crop="end"
+                         value="Total Count %"
+                         tooltiptext="&performanceUI.table.totalAlloc.tooltip;"/>
                   <label class="plain call-tree-header"
                          type="function"
                          crop="end"
                          value="&performanceUI.table.function;"/>
                 </hbox>
                 <vbox class="call-tree-cells-container" flex="1"/>
               </vbox>
 
               <!-- Memory FlameChart -->
               <hbox id="memory-flamegraph-view" flex="1">
               </hbox>
-
-              <!-- JIT View -->
-              <hbox id="optimizations-view" flex="1">
-                <hbox id="graph-placeholder" flex="1">
-                </hbox>
-                <splitter id="optimizations-splitter" class="devtools-side-splitter"/>
-                <tabbox id="optimizations-tabs"
-                        class="devtools-sidebar-tabs"
-                        handleCtrlTab="false">
-                  <tabs>
-                    <tab id="optimizations-optimizations-tab"
-                         label="Optimizations" />
-                    <tab id="optimizations-frames-tab"
-                         label="Frames" />
-                  </tabs>
-                  <tabpanels flex="1">
-
-                    <!-- Optimizations Panel -->
-                    <tabpanel id="optimizations-tabpanel"
-                              class="tabpanel-content">
-                      <vbox id="jit-optimizations-view">
-                        <toolbar id="jit-optimizations-toolbar" class="devtools-toolbar">
-                          <hbox id="jit-optimizations-header">
-                            <span class="jit-optimizations-title">&performanceUI.JITOptimizationsTitle;</span>
-                            <span class="header-function-name" />
-                            <span class="header-file opt-url debugger-link" />
-                            <span class="header-line opt-line" />
-                          </hbox>
-                        </toolbar>
-                        <vbox id="jit-optimizations-raw-view"></vbox>
-                      </vbox>
-                    </tabpanel>
-
-                    <!-- Frames Panel -->
-                    <tabpanel id="frames-tabpanel"
-                              class="tabpanel-content">
-                    </tabpanel>
-                  </tabpanels>
-                </tabbox>
-              </hbox>
-              <!-- /JIT View -->
             </deck>
           </deck>
         </vbox>
       </deck>
     </vbox>
   </hbox>
 </window>
--- a/browser/devtools/performance/test/browser.ini
+++ b/browser/devtools/performance/test/browser.ini
@@ -37,18 +37,18 @@ skip-if = true # Bug 1161817
 [browser_perf-details-02.js]
 [browser_perf-details-03.js]
 [browser_perf-details-04.js]
 [browser_perf-details-05.js]
 [browser_perf-details-06.js]
 [browser_perf-details-07.js]
 [browser_perf-events-calltree.js]
 [browser_perf-highlighted.js]
-#[browser_perf-jit-view-01.js] bug 1176056
-#[browser_perf-jit-view-02.js] bug 1176056
+[browser_perf-jit-view-01.js]
+[browser_perf-jit-view-02.js]
 [browser_perf-legacy-front-01.js]
 [browser_perf-legacy-front-02.js]
 [browser_perf-legacy-front-03.js]
 [browser_perf-legacy-front-04.js]
 [browser_perf-legacy-front-05.js]
 [browser_perf-legacy-front-06.js]
 [browser_perf-loading-01.js]
 [browser_perf-loading-02.js]
@@ -64,17 +64,16 @@ skip-if = os == 'linux' # Bug 1172120
 [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]
 [browser_perf-options-show-idle-blocks-01.js]
 [browser_perf-options-show-idle-blocks-02.js]
 [browser_perf-options-enable-memory-01.js]
 [browser_perf-options-enable-memory-02.js]
-[browser_perf-options-enable-optimizations.js]
 [browser_perf-options-enable-framerate.js]
 [browser_perf-options-allocations.js]
 [browser_perf-options-profiler.js]
 [browser_perf-overview-render-01.js]
 [browser_perf-overview-render-02.js]
 [browser_perf-overview-render-03.js]
 [browser_perf-overview-render-04.js]
 skip-if = os == 'linux' # bug 1186322
--- a/browser/devtools/performance/test/browser_perf-columns-memory-calltree.js
+++ b/browser/devtools/performance/test/browser_perf-columns-memory-calltree.js
@@ -19,19 +19,25 @@ function* spawnTest() {
   yield DetailsView.selectView("memory-calltree");
   ok(DetailsView.isViewSelected(MemoryCallTreeView), "The call tree is now selected.");
   yield rendered;
 
   testCells($, $$, {
     "duration": false,
     "percentage": false,
     "count": true,
+    "count-percentage": true,
+    "size": true,
+    "size-percentage": true,
     "self-duration": false,
     "self-percentage": false,
     "self-count": true,
+    "self-count-percentage": true,
+    "self-size": true,
+    "self-size-percentage": true,
     "samples": false,
     "function": true
   });
 
   yield teardown(panel);
   finish();
 }
 
--- a/browser/devtools/performance/test/browser_perf-jit-view-01.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-01.js
@@ -6,17 +6,17 @@
  * if on, and displays selected frames on focus.
  */
 
 Services.prefs.setBoolPref(INVERT_PREF, false);
 
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
-  let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
+  let { OverviewView, DetailsView, OptimizationsListView, JsCallTreeView, RecordingsView } = panel.panelWin;
 
   let profilerData = { threads: [gThread] }
 
   is(Services.prefs.getBoolPref(JIT_PREF), false, "record JIT Optimizations pref off by default");
   Services.prefs.setBoolPref(JIT_PREF, true);
   is(Services.prefs.getBoolPref(JIT_PREF), true, "toggle on record JIT Optimizations");
 
   // Make two recordings, so we have one to switch to later, as the
@@ -37,17 +37,17 @@ function* spawnTest() {
   // gRawSite2 and gRawSite3 are both optimizations on B, so they'll have
   // indices in descending order of # of samples.
   yield checkFrame(2, [{ i: 0, opt: gRawSite2 }, { i: 1, opt: gRawSite3 }]);
 
   // Leaf node (C) with no optimizations should not display any opts.
   yield checkFrame(3);
 
   let select = once(PerformanceController, EVENTS.RECORDING_SELECTED);
-  let reset = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RESET);
+  let reset = once(OptimizationsListView, EVENTS.OPTIMIZATIONS_RESET);
   RecordingsView.selectedIndex = 0;
   yield Promise.all([select, reset]);
   ok(true, "JITOptimizations view correctly reset when switching recordings.");
 
   yield teardown(panel);
   finish();
 
   function *injectAndRenderProfilerData() {
@@ -62,35 +62,36 @@ function* spawnTest() {
     yield rendered;
 
     is($("#jit-optimizations-view").hidden, false, "JIT Optimizations should be visible when pref is on");
     ok($("#jit-optimizations-view").classList.contains("empty"),
       "JIT Optimizations view has empty message when no frames selected.");
   }
 
   function *checkFrame (frameIndex, expectedOpts=[]) {
+    info(`Checking frame ${frameIndex}`);
     // Click the frame
-    let rendered = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
+    let rendered = once(OptimizationsListView, EVENTS.OPTIMIZATIONS_RENDERED);
     mousedown(window, $$(".call-tree-item")[frameIndex]);
     yield rendered;
-    ok(true, "JITOptimizationsView rendered when enabling with the current frame node selected");
+    ok(true, "OptimizationsListView rendered when enabling with the current frame node selected");
 
     let isEmpty = $("#jit-optimizations-view").classList.contains("empty");
     if (expectedOpts.length === 0) {
       ok(isEmpty, "JIT Optimizations view has an empty message when selecting a frame without opt data.");
       return;
     } else {
       ok(!isEmpty, "JIT Optimizations view has no empty message.");
     }
 
     // Get the frame info for the first opt site, since all opt sites
     // share the same frame info
     let frameInfo = expectedOpts[0].opt._testFrameInfo;
 
-    let { $headerName, $headerLine, $headerFile } = JITOptimizationsView;
+    let { $headerName, $headerLine, $headerFile } = OptimizationsListView;
     ok(!$headerName.hidden, "header function name should be shown");
     ok(!$headerLine.hidden, "header line should be shown");
     ok(!$headerFile.hidden, "header file should be shown");
     is($headerName.textContent, frameInfo.name, "correct header function name.");
     is($headerLine.textContent, frameInfo.line, "correct header line");
     is($headerFile.textContent, frameInfo.file, "correct header file");
 
     // Need the value of the optimizations in its array, as its
--- a/browser/devtools/performance/test/browser_perf-jit-view-02.js
+++ b/browser/devtools/performance/test/browser_perf-jit-view-02.js
@@ -1,23 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Tests that the JIT Optimizations view does not display information
+ * Tests that the OptimizationsListView does not display information
  * for meta nodes when viewing "content only".
  */
 
 Services.prefs.setBoolPref(INVERT_PREF, false);
 Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
+let { CATEGORY_MASK } = require("devtools/performance/global");
 
 function* spawnTest() {
   let { panel } = yield initPerformance(SIMPLE_URL);
   let { EVENTS, $, $$, window, PerformanceController } = panel.panelWin;
-  let { OverviewView, DetailsView, JITOptimizationsView, JsCallTreeView, RecordingsView } = panel.panelWin;
+  let { OverviewView, DetailsView, OptimizationsListView, JsCallTreeView, RecordingsView } = panel.panelWin;
 
   let profilerData = { threads: [gThread] };
 
   Services.prefs.setBoolPref(JIT_PREF, true);
 
   // Make two recordings, so we have one to switch to later, as the
   // second one will have fake sample data
   yield startRecording(panel);
@@ -26,24 +27,24 @@ function* spawnTest() {
   yield startRecording(panel);
   yield stopRecording(panel);
 
   yield DetailsView.selectView("js-calltree");
 
   yield injectAndRenderProfilerData();
 
   // Click the frame
-  let rendered = once(JITOptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
+  let rendered = once(OptimizationsListView, EVENTS.OPTIMIZATIONS_RENDERED);
   mousedown(window, $$(".call-tree-item")[2]);
   yield rendered;
 
   ok($("#jit-optimizations-view").classList.contains("empty"),
     "platform meta frame shows as empty");
 
-  let { $headerName, $headerLine, $headerFile } = JITOptimizationsView;
+  let { $headerName, $headerLine, $headerFile } = OptimizationsListView;
   ok(!$headerName.hidden, "header function name should be shown");
   ok($headerLine.hidden, "header line should be hidden");
   ok($headerFile.hidden, "header file should be hidden");
   is($headerName.textContent, "JIT", "correct header function name.");
   is($headerLine.textContent, "", "correct header line (empty string).");
   is($headerFile.textContent, "", "correct header file (empty string).");
 
   yield teardown(panel);
deleted file mode 100644
--- a/browser/devtools/performance/test/browser_perf-options-enable-optimizations.js
+++ /dev/null
@@ -1,45 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Tests that `enable-jit-optimizations` sets the recording to subsequently
- * enable the Optimizations View.
- */
-function* spawnTest() {
-  let { panel } = yield initPerformance(SIMPLE_URL);
-  let { EVENTS, PerformanceController, $, DetailsView, WaterfallView, OptimizationsView } = panel.panelWin;
-  Services.prefs.setBoolPref(JIT_PREF, true);
-
-
-  yield startRecording(panel);
-  let rendered = once(OptimizationsView, EVENTS.OPTIMIZATIONS_RENDERED);
-  yield stopRecording(panel);
-
-  yield DetailsView.selectView("optimizations");
-  ok(DetailsView.isViewSelected(OptimizationsView), "The Optimizations View is now selected.");
-  yield rendered;
-
-  let recording = PerformanceController.getCurrentRecording();
-  is(recording.getConfiguration().withJITOptimizations, true, "recording model has withJITOptimizations as true");
-
-  // Set back to false, should not affect display of first recording
-  info("Disabling enable-jit-optimizations");
-  Services.prefs.setBoolPref(JIT_PREF, false);
-  is($("#select-optimizations-view").hidden, false,
-    "JIT Optimizations selector still available since the recording has it enabled.");
-
-  yield startRecording(panel);
-  rendered = once(WaterfallView, EVENTS.WATERFALL_RENDERED);
-  yield stopRecording(panel);
-
-  ok(DetailsView.isViewSelected(WaterfallView), "The waterfall view is now selected.");
-  yield rendered;
-
-  recording = PerformanceController.getCurrentRecording();
-  is(recording.getConfiguration().withJITOptimizations, false, "recording model has withJITOptimizations as false");
-  is($("#select-optimizations-view").hidden, true,
-    "JIT Optimizations selector is hidden if recording did not enable optimizations.");
-
-  yield teardown(panel);
-  finish();
-}
--- a/browser/devtools/performance/test/unit/test_marker-utils.js
+++ b/browser/devtools/performance/test/unit/test_marker-utils.js
@@ -14,16 +14,18 @@ add_task(function () {
   let Utils = require("devtools/performance/marker-utils");
 
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, false);
 
   equal(Utils.getMarkerLabel({ name: "DOMEvent" }), "DOM Event",
     "getMarkerLabel() returns a simple label");
   equal(Utils.getMarkerLabel({ name: "Javascript", causeName: "setTimeout handler" }), "setTimeout",
     "getMarkerLabel() returns a label defined via function");
+  equal(Utils.getMarkerLabel({ name: "GarbageCollection", causeName: "ALLOC_TRIGGER" }), "Incremental GC",
+    "getMarkerLabel() returns a label for a function that is generalizable");
 
   ok(Utils.getMarkerFields({ name: "Paint" }).length === 0,
     "getMarkerFields() returns an empty array when no fields defined");
 
   let fields = Utils.getMarkerFields({ name: "ConsoleTime", causeName: "snowstorm" });
   equal(fields[0].label, "Timer Name:", "getMarkerFields() returns an array with proper label");
   equal(fields[0].value, "snowstorm", "getMarkerFields() returns an array with proper value");
 
@@ -48,17 +50,17 @@ add_task(function () {
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "(Gecko)",
     "Correctly obfuscates JS markers when platform data is off.");
   Services.prefs.setBoolPref(PLATFORM_DATA_PREF, true);
   equal(Utils.getMarkerFields({ name: "Javascript", causeName: "Some Platform Field" })[0].value, "Some Platform Field",
     "Correctly deobfuscates JS markers when platform data is on.");
 
   equal(Utils.getMarkerClassName("Javascript"), "Function Call",
     "getMarkerClassName() returns correct string when defined via function");
-  equal(Utils.getMarkerClassName("GarbageCollection"), "Incremental GC",
+  equal(Utils.getMarkerClassName("GarbageCollection"), "Garbage Collection",
     "getMarkerClassName() returns correct string when defined via function");
   equal(Utils.getMarkerClassName("Reflow"), "Layout",
     "getMarkerClassName() returns correct string when defined via string");
 
   TIMELINE_BLUEPRINT["fakemarker"] = { group: 0 };
   try {
     Utils.getMarkerClassName("fakemarker");
     ok(false, "getMarkerClassName() should throw when no label on blueprint.");
--- a/browser/devtools/performance/views/details-js-call-tree.js
+++ b/browser/devtools/performance/views/details-js-call-tree.js
@@ -18,48 +18,79 @@ let JsCallTreeView = Heritage.extend(Det
 
   /**
    * Sets up the view with event binding.
    */
   initialize: function () {
     DetailsSubview.initialize.call(this);
 
     this._onLink = this._onLink.bind(this);
+    this._onFocus = this._onFocus.bind(this);
 
     this.container = $("#js-calltree-view .call-tree-cells-container");
+
+    OptimizationsListView.initialize();
   },
 
   /**
    * Unbinds events.
    */
   destroy: function () {
+    OptimizationsListView.destroy();
     this.container = null;
     DetailsSubview.destroy.call(this);
   },
 
   /**
    * Method for handling all the set up for rendering a new call tree.
    *
    * @param object interval [optional]
    *        The { startTime, endTime }, in milliseconds.
    */
   render: function (interval={}) {
     let recording = PerformanceController.getCurrentRecording();
     let profile = recording.getProfile();
+    let optimizations = recording.getConfiguration().withJITOptimizations;
+
     let options = {
       contentOnly: !PerformanceController.getOption("show-platform-data"),
       invertTree: PerformanceController.getOption("invert-call-tree"),
       flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
-      showOptimizationHint: recording.getConfiguration().withJITOptimizations,
+      showOptimizationHint: optimizations
     };
     let threadNode = this._prepareCallTree(profile, interval, options);
     this._populateCallTree(threadNode, options);
+
+    if (optimizations) {
+      this.showOptimizations();
+    } else {
+      this.hideOptimizations();
+    }
+    OptimizationsListView.reset();
+
     this.emit(EVENTS.JS_CALL_TREE_RENDERED);
   },
 
+  showOptimizations: function () {
+    $("#jit-optimizations-view").classList.remove("hidden");
+  },
+
+  hideOptimizations: function () {
+    $("#jit-optimizations-view").classList.add("hidden");
+  },
+
+  _onFocus: function (_, treeItem) {
+    if (PerformanceController.getCurrentRecording().getConfiguration().withJITOptimizations) {
+      OptimizationsListView.setCurrentFrame(treeItem.frame);
+      OptimizationsListView.render();
+    }
+
+    this.emit("focus", treeItem);
+  },
+
   /**
    * Fired on the "link" event for the call tree in this container.
    */
   _onLink: function (_, treeItem) {
     let { url, line } = treeItem.frame.getInfo();
     gToolbox.viewSourceInDebugger(url, line).then(success => {
       if (success) {
         this.emit(EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
@@ -106,22 +137,17 @@ let JsCallTreeView = Heritage.extend(Det
       // Call trees should only auto-expand when not inverted. Passing undefined
       // will default to the CALL_TREE_AUTO_EXPAND depth.
       autoExpandDepth: inverted ? 0 : undefined,
       showOptimizationHint: options.showOptimizationHint
     });
 
     // Bind events.
     root.on("link", this._onLink);
-
-    // Pipe "focus" events to the view, mostly for tests
-    root.on("focus", () => this.emit("focus"));
-    // TODO tests for optimization event and rendering
-    // optimization bubbles in call tree
-    root.on("optimization", (_, node) => this.emit("optimization", node));
+    root.on("focus", this._onFocus);
 
     // Clear out other call trees.
     this.container.innerHTML = "";
     root.attachTo(this.container);
 
     // When platform data isn't shown, hide the cateogry labels, since they're
     // only available for C++ frames. Pass *false* to make them invisible.
     root.toggleCategories(!options.contentOnly);
--- a/browser/devtools/performance/views/details-memory-call-tree.js
+++ b/browser/devtools/performance/views/details-memory-call-tree.js
@@ -83,26 +83,30 @@ let MemoryCallTreeView = Heritage.extend
     // mis-interpreted as an error.
     let inverted = options.invertTree && frameNode.samples > 0;
 
     let root = new CallView({
       frame: frameNode,
       inverted: inverted,
       // Root nodes are hidden in inverted call trees.
       hidden: inverted,
-      // Memory call trees should be sorted by allocations.
-      sortingPredicate: (a, b) => a.frame.allocations < b.frame.allocations ? 1 : -1,
       // Call trees should only auto-expand when not inverted. Passing undefined
       // will default to the CALL_TREE_AUTO_EXPAND depth.
       autoExpandDepth: inverted ? 0 : undefined,
       // Some cells like the time duration and cost percentage don't make sense
       // for a memory allocations call tree.
       visibleCells: {
         selfCount: true,
         count: true,
+        selfSize: true,
+        size: true,
+        selfCountPercentage: true,
+        countPercentage: true,
+        selfSizePercentage: true,
+        sizePercentage: true,
         function: true
       }
     });
 
     // Bind events.
     root.on("link", this._onLink);
 
     // Pipe "focus" events to the view, mostly for tests
deleted file mode 100644
--- a/browser/devtools/performance/views/details-optimizations.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/* 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";
-
-let OptimizationsView = Heritage.extend(DetailsSubview, {
-
-  rerenderPrefs: [
-    "show-platform-data",
-    "flatten-tree-recursion",
-  ],
-
-  rangeChangeDebounceTime: 75, // ms
-
-  /**
-   * Sets up the view with event binding.
-   */
-  initialize: function () {
-    DetailsSubview.initialize.call(this);
-    this.reset = this.reset.bind(this);
-    this.tabs = $("#optimizations-tabs");
-    this._onFramesListSelect = this._onFramesListSelect.bind(this);
-
-    OptimizationsListView.initialize();
-    FramesListView.initialize({ container: $("#frames-tabpanel") });
-    FramesListView.on("select", this._onFramesListSelect);
-  },
-
-  /**
-   * Unbinds events.
-   */
-  destroy: function () {
-    DetailsSubview.destroy.call(this);
-    this.tabs = this._threadNode = this._frameNode = null;
-
-    FramesListView.off("select", this._onFramesListSelect);
-    FramesListView.destroy();
-    OptimizationsListView.destroy();
-  },
-
-  /**
-   * Selects a tab by name.
-   *
-   * @param {string} name
-   *                 Can be "frames" or "optimizations"
-   */
-  selectTabByName: function (name="frames") {
-    switch(name) {
-    case "optimizations":
-      this.tabs.selectedIndex = 0;
-      break;
-    case "frames":
-      this.tabs.selectedIndex = 1;
-      break;
-    }
-  },
-
-  /**
-   * Method for handling all the set up for rendering a new call tree.
-   *
-   * @param object interval [optional]
-   *        The { startTime, endTime }, in milliseconds.
-   */
-  render: function (interval={}) {
-    let options = {
-      contentOnly: !PerformanceController.getOption("show-platform-data"),
-      flattenRecursion: PerformanceController.getOption("flatten-tree-recursion"),
-      // Always invert the tree for the optimizations view so we can quickly
-      // get leaves
-      invertTree: true,
-    };
-    let recording = PerformanceController.getCurrentRecording();
-    let profile = recording.getProfile();
-
-    this.reset();
-    // TODO bug 1175662
-    // Share thread nodes between details view
-    this.threadNode = this._prepareThreadNode(profile, interval, options);
-    this.emit(EVENTS.OPTIMIZATIONS_RENDERED);
-  },
-
-  /**
-   * The main thread node used in this recording that contains
-   * all potential frame nodes to select.
-   */
-  set threadNode(threadNode) {
-    if (threadNode === this._threadNode) {
-      return;
-    }
-    this._threadNode = threadNode;
-    // Also clear out the current frame node as its no
-    // longer relevent
-    this.frameNode = null;
-    this._setAndRenderFramesList();
-  },
-  get threadNode() {
-    return this._threadNode;
-  },
-
-  /**
-   * frameNode is the frame node selected currently to inspect
-   * the optimization tiers over time and strategies.
-   */
-  set frameNode(frameNode) {
-    if (frameNode === this._frameNode) {
-      return;
-    }
-    this._frameNode = frameNode;
-
-    // If no frame selected, jump to the frame list view. If just selected
-    // a frame, jump to optimizations view.
-    // TODO test for this bug 1176056
-    this.selectTabByName(frameNode ? "optimizations" : "frames");
-    this._setAndRenderTierGraph();
-    this._setAndRenderOptimizationsList();
-  },
-
-  get frameNode() {
-    return this._frameNode;
-  },
-
-  /**
-   * Clears the frameNode so that tier and opts list
-   * views are cleared.
-   */
-  reset: function () {
-    this.threadNode = this.frameNode = null;
-  },
-
-  /**
-   * Called when the recording is stopped and prepares data to
-   * populate the graph.
-   */
-  _prepareThreadNode: function (profile, { startTime, endTime }, options) {
-    let thread = profile.threads[0];
-    let { contentOnly, invertTree, flattenRecursion } = options;
-    let threadNode = new ThreadNode(thread, { startTime, endTime, contentOnly, invertTree, flattenRecursion });
-    return threadNode;
-  },
-
-  /**
-   * Renders the tier graph.
-   */
-  _setAndRenderTierGraph: function () {
-    // TODO bug 1150299
-  },
-
-  /**
-   * Renders the frames list.
-   */
-  _setAndRenderFramesList: function () {
-    FramesListView.setCurrentThread(this.threadNode);
-    FramesListView.render();
-  },
-
-  /**
-   * Renders the optimizations list.
-   */
-  _setAndRenderOptimizationsList: function () {
-    OptimizationsListView.setCurrentFrame(this.frameNode);
-    OptimizationsListView.render();
-  },
-
-  /**
-   * Called when a frame is selected via the FramesListView
-   */
-  _onFramesListSelect: function (_, frameNode) {
-    this.frameNode = frameNode;
-  },
-
-  toString: () => "[object OptimizationsView]"
-});
-
-EventEmitter.decorate(OptimizationsView);
--- a/browser/devtools/performance/views/details.js
+++ b/browser/devtools/performance/views/details.js
@@ -32,21 +32,16 @@ let DetailsView = {
       features: ["withAllocations"]
     },
     "memory-flamegraph": {
       id: "memory-flamegraph-view",
       view: MemoryFlameGraphView,
       features: ["withAllocations"],
       prefs: ["enable-memory-flame"],
     },
-    "optimizations": {
-      id: "optimizations-view",
-      view: OptimizationsView,
-      features: ["withJITOptimizations"],
-    }
   },
 
   /**
    * Sets up the view with event binding, initializes subviews.
    */
   initialize: Task.async(function *() {
     this.el = $("#details-pane");
     this.toolbar = $("#performance-toolbar-controls-detail-views");
deleted file mode 100644
--- a/browser/devtools/performance/views/frames-list.js
+++ /dev/null
@@ -1,113 +0,0 @@
-/* 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 HTML_NS = "http://www.w3.org/1999/xhtml";
-const PERCENTAGE_UNITS = L10N.getStr("table.percentage");
-
-/**
- * View for rendering a list of all youngest-frames in a profiler recording.
- */
-
-let FramesListView = {
-
-  // Current `<li>` element selected.
-  _selectedItem: null,
-
-  /**
-   * Initialization function called when the tool starts up.
-   */
-  initialize: function ({ container }) {
-    this._onFrameListClick = this._onFrameListClick.bind(this);
-
-    this.container = container;
-    this.list = document.createElementNS(HTML_NS, "ul");
-    this.list.setAttribute("class", "frames-list");
-    this.list.addEventListener("click", this._onFrameListClick, false);
-
-    this.container.appendChild(this.list);
-  },
-
-  /**
-   * Destruction function called when the tool cleans up.
-   */
-  destroy: function () {
-    this.list.removeEventListener("click", this._onFrameListClick, false);
-    this.container.innerHTML = "";
-    this.container = this.list = null;
-  },
-
-  /**
-   * Sets the thread node used for subsequent rendering.
-   *
-   * @param {ThreadNode} threadNode
-   */
-  setCurrentThread: function (threadNode) {
-    this.threadNode = threadNode;
-  },
-
-  /**
-   * Renders a list of leaf frames with optimizations in
-   * order of hotness from the current ThreadNode.
-   */
-  render: function () {
-    this.list.innerHTML = "";
-
-    if (!this.threadNode) {
-      return;
-    }
-
-    let totalSamples = this.threadNode.samples;
-    let sortedFrames = this.threadNode.calls.sort((a, b) => a.youngestFrameSamples < b.youngestFrameSamples ? 1 : -1);
-    for (let frame of sortedFrames) {
-      if (!frame.hasOptimizations()) {
-        continue;
-      }
-      let info = frame.getInfo();
-      let el = document.createElementNS(HTML_NS, "li");
-      let percentage = frame.youngestFrameSamples / totalSamples * 100;
-      let percentageText = L10N.numberWithDecimals(percentage, 2) + PERCENTAGE_UNITS;
-      let label = `(${percentageText}) ${info.functionName}`;
-      el.textContent = label;
-      el.setAttribute("tooltip", label);
-      el.setAttribute("data-location", frame.location);
-      this.list.appendChild(el);
-    }
-  },
-
-  /**
-   * Fired when a frame in the list is clicked.
-   */
-  _onFrameListClick: function (e) {
-    // If no threadNode (no renders), abort;
-    // also only allow left click to trigger this event
-    if (!this.threadNode || e.button !== 0) {
-      return;
-    }
-
-    let target = e.target;
-    let location = target.getAttribute("data-location");
-    if (!location) {
-      return;
-    }
-
-    for (let frame of this.threadNode.calls) {
-      if (frame.location === location) {
-        // If found, set the selected class on element, remove it
-        // from previous element, and emit event "select"
-        if (this._selectedItem) {
-          this._selectedItem.classList.remove("selected");
-        }
-        this._selectedItem = target;
-        target.classList.add("selected");
-        this.emit("select", frame);
-        break;
-      }
-    }
-  },
-
-  toString: () => "[object FramesListView]"
-};
-
-EventEmitter.decorate(FramesListView);
--- a/browser/devtools/performance/views/optimizations-list.js
+++ b/browser/devtools/performance/views/optimizations-list.js
@@ -355,12 +355,11 @@ let OptimizationsListView = {
   _isLinkableURL: function (url) {
     return url && url.indexOf &&
        (url.indexOf("http") === 0 ||
         url.indexOf("resource://") === 0 ||
         url.indexOf("file://") === 0);
   },
 
   toString: () => "[object OptimizationsListView]"
-
 };
 
 EventEmitter.decorate(OptimizationsListView);
--- a/browser/devtools/shared/inplace-editor.js
+++ b/browser/devtools/shared/inplace-editor.js
@@ -1,16 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=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/. */
+
+/* globals focusManager, CSSPropertyList, domUtils */
 
 /**
- * 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/.
- *
  * Basic use:
  * let spanToEdit = document.getElementById("somespan");
  *
  * editableField({
  *   element: spanToEdit,
  *   done: function(value, commit, direction) {
  *     if (commit) {
  *       spanToEdit.textContent = value;
@@ -43,108 +44,106 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
 
 /**
  * Mark a span editable.  |editableField| will listen for the span to
  * be focused and create an InlineEditor to handle text input.
  * Changes will be committed when the InlineEditor's input is blurred
  * or dropped when the user presses escape.
  *
- * @param {object} aOptions
+ * @param {Object} options
  *    Options for the editable field, including:
  *    {Element} element:
  *      (required) The span to be edited on focus.
- *    {function} canEdit:
+ *    {Function} canEdit:
  *       Will be called before creating the inplace editor.  Editor
  *       won't be created if canEdit returns false.
- *    {function} start:
+ *    {Function} start:
  *       Will be called when the inplace editor is initialized.
- *    {function} change:
+ *    {Function} change:
  *       Will be called when the text input changes.  Will be called
  *       with the current value of the text input.
- *    {function} done:
+ *    {Function} done:
  *       Called when input is committed or blurred.  Called with
  *       current value, a boolean telling the caller whether to
  *       commit the change, and the direction of the next element to be
  *       selected. Direction may be one of nsIFocusManager.MOVEFOCUS_FORWARD,
  *       nsIFocusManager.MOVEFOCUS_BACKWARD, or null (no movement).
  *       This function is called before the editor has been torn down.
- *    {function} destroy:
+ *    {Function} destroy:
  *       Called when the editor is destroyed and has been torn down.
- *    {object} advanceChars:
+ *    {Object} advanceChars:
  *       This can be either a string or a function.
  *       If it is a string, then if any characters in it are typed,
  *       focus will advance to the next element.
  *       Otherwise, if it is a function, then the function will
  *       be called with three arguments: a key code, the current text,
  *       and the insertion point.  If the function returns true,
  *       then the focus advance takes place.  If it returns false,
  *       then the character is inserted instead.
- *    {boolean} stopOnReturn:
+ *    {Boolean} stopOnReturn:
  *       If true, the return key will not advance the editor to the next
  *       focusable element.
- *    {boolean} stopOnTab:
+ *    {Boolean} stopOnTab:
  *       If true, the tab key will not advance the editor to the next
  *       focusable element.
- *    {boolean} stopOnShiftTab:
+ *    {Boolean} stopOnShiftTab:
  *       If true, shift tab will not advance the editor to the previous
  *       focusable element.
- *    {string} trigger: The DOM event that should trigger editing,
+ *    {String} trigger: The DOM event that should trigger editing,
  *      defaults to "click"
- *    {boolean} multiline: Should the editor be a multiline textarea?
+ *    {Boolean} multiline: Should the editor be a multiline textarea?
  *      defaults to false
- *    {boolean} trimOutput: Should the returned string be trimmed?
+ *    {Boolean} trimOutput: Should the returned string be trimmed?
  *      defaults to true
  */
-function editableField(aOptions)
-{
-  return editableItem(aOptions, function(aElement, aEvent) {
-    if (!aOptions.element.inplaceEditor) {
-      new InplaceEditor(aOptions, aEvent);
+function editableField(options) {
+  return editableItem(options, function(element, event) {
+    if (!options.element.inplaceEditor) {
+      new InplaceEditor(options, event);
     }
   });
 }
 
 exports.editableField = editableField;
 
 /**
  * Handle events for an element that should respond to
  * clicks and sit in the editing tab order, and call
  * a callback when it is activated.
  *
- * @param {object} aOptions
+ * @param {Object} options
  *    The options for this editor, including:
  *    {Element} element: The DOM element.
- *    {string} trigger: The DOM event that should trigger editing,
+ *    {String} trigger: The DOM event that should trigger editing,
  *      defaults to "click"
- * @param {function} aCallback
+ * @param {Function} callback
  *        Called when the editor is activated.
- * @return {function} function which calls aCallback
+ * @return {Function} function which calls callback
  */
-function editableItem(aOptions, aCallback)
-{
-  let trigger = aOptions.trigger || "click"
-  let element = aOptions.element;
+function editableItem(options, callback) {
+  let trigger = options.trigger || "click";
+  let element = options.element;
   element.addEventListener(trigger, function(evt) {
     if (evt.target.nodeName !== "a") {
       let win = this.ownerDocument.defaultView;
       let selection = win.getSelection();
       if (trigger != "click" || selection.isCollapsed) {
-        aCallback(element, evt);
+        callback(element, evt);
       }
       evt.stopPropagation();
     }
   }, false);
 
   // If focused by means other than a click, start editing by
   // pressing enter or space.
   element.addEventListener("keypress", function(evt) {
     if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
         evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
-      aCallback(element);
+      callback(element);
     }
   }, true);
 
   // Ugly workaround - the element is focused on mousedown but
   // the editor is activated on click/mouseup.  This leads
   // to an ugly flash of the focus ring before showing the editor.
   // So hide the focus ring while the mouse is down.
   element.addEventListener("mousedown", function(evt) {
@@ -163,143 +162,142 @@ function editableItem(aOptions, aCallbac
   // Mark the element editable field for tab
   // navigation while editing.
   element._editable = true;
 
   // Save the trigger type so we can dispatch this later
   element._trigger = trigger;
 
   return function turnOnEditMode() {
-    aCallback(element);
-  }
+    callback(element);
+  };
 }
 
 exports.editableItem = this.editableItem;
 
 /*
  * Various API consumers (especially tests) sometimes want to grab the
  * inplaceEditor expando off span elements. However, when each global has its
  * own compartment, those expandos live on Xray wrappers that are only visible
  * within this JSM. So we provide a little workaround here.
  */
 
-function getInplaceEditorForSpan(aSpan)
-{
-  return aSpan.inplaceEditor;
-};
+function getInplaceEditorForSpan(span) {
+  return span.inplaceEditor;
+}
+
 exports.getInplaceEditorForSpan = getInplaceEditorForSpan;
 
-function InplaceEditor(aOptions, aEvent)
-{
-  this.elt = aOptions.element;
+function InplaceEditor(options, event) {
+  this.elt = options.element;
   let doc = this.elt.ownerDocument;
   this.doc = doc;
   this.elt.inplaceEditor = this;
 
-  this.change = aOptions.change;
-  this.done = aOptions.done;
-  this.destroy = aOptions.destroy;
-  this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
-  this.multiline = aOptions.multiline || false;
-  this.trimOutput = aOptions.trimOutput === undefined ? true : !!aOptions.trimOutput;
-  this.stopOnShiftTab = !!aOptions.stopOnShiftTab;
-  this.stopOnTab = !!aOptions.stopOnTab;
-  this.stopOnReturn = !!aOptions.stopOnReturn;
-  this.contentType = aOptions.contentType || CONTENT_TYPES.PLAIN_TEXT;
-  this.property = aOptions.property;
-  this.popup = aOptions.popup;
+  this.change = options.change;
+  this.done = options.done;
+  this.destroy = options.destroy;
+  this.initial = options.initial ? options.initial : this.elt.textContent;
+  this.multiline = options.multiline || false;
+  this.trimOutput = options.trimOutput === undefined
+                    ? true
+                    : !!options.trimOutput;
+  this.stopOnShiftTab = !!options.stopOnShiftTab;
+  this.stopOnTab = !!options.stopOnTab;
+  this.stopOnReturn = !!options.stopOnReturn;
+  this.contentType = options.contentType || CONTENT_TYPES.PLAIN_TEXT;
+  this.property = options.property;
+  this.popup = options.popup;
 
   this._onBlur = this._onBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
   this._onInput = this._onInput.bind(this);
   this._onKeyup = this._onKeyup.bind(this);
 
   this._createInput();
   this._autosize();
   this.inputCharWidth = this._getInputCharWidth();
 
   // Pull out character codes for advanceChars, listing the
   // characters that should trigger a blur.
-  if (typeof(aOptions.advanceChars) === "function") {
-    this._advanceChars = aOptions.advanceChars;
+  if (typeof options.advanceChars === "function") {
+    this._advanceChars = options.advanceChars;
   } else {
     let advanceCharcodes = {};
-    let advanceChars = aOptions.advanceChars || '';
+    let advanceChars = options.advanceChars || "";
     for (let i = 0; i < advanceChars.length; i++) {
       advanceCharcodes[advanceChars.charCodeAt(i)] = true;
     }
     this._advanceChars = aCharCode => aCharCode in advanceCharcodes;
   }
 
   // Hide the provided element and add our editor.
   this.originalDisplay = this.elt.style.display;
   this.elt.style.display = "none";
   this.elt.parentNode.insertBefore(this.input, this.elt);
 
   this.input.focus();
 
-  if (typeof(aOptions.selectAll) == "undefined" || aOptions.selectAll) {
+  if (typeof options.selectAll == "undefined" || options.selectAll) {
     this.input.select();
   }
 
   if (this.contentType == CONTENT_TYPES.CSS_VALUE && this.input.value == "") {
     this._maybeSuggestCompletion(true);
   }
 
   this.input.addEventListener("blur", this._onBlur, false);
   this.input.addEventListener("keypress", this._onKeyPress, false);
   this.input.addEventListener("input", this._onInput, false);
 
   this.input.addEventListener("dblclick",
     (e) => { e.stopPropagation(); }, false);
   this.input.addEventListener("mousedown",
     (e) => { e.stopPropagation(); }, false);
 
-  this.validate = aOptions.validate;
+  this.validate = options.validate;
 
   if (this.validate) {
     this.input.addEventListener("keyup", this._onKeyup, false);
   }
 
   this._updateSize();
 
-  if (aOptions.start) {
-    aOptions.start(this, aEvent);
+  if (options.start) {
+    options.start(this, event);
   }
 
   EventEmitter.decorate(this);
 }
 
 exports.InplaceEditor = InplaceEditor;
 
 InplaceEditor.CONTENT_TYPES = CONTENT_TYPES;
 
 InplaceEditor.prototype = {
 
   get currentInputValue() {
     let val = this.trimOutput ? this.input.value.trim() : this.input.value;
     return val;
   },
 
-  _createInput: function InplaceEditor_createEditor()
-  {
+  _createInput: function() {
     this.input =
       this.doc.createElementNS(HTML_NS, this.multiline ? "textarea" : "input");
     this.input.inplaceEditor = this;
     this.input.classList.add("styleinspector-propertyeditor");
     this.input.value = this.initial;
 
     copyTextStyles(this.elt, this.input);
   },
 
   /**
    * Get rid of the editor.
    */
-  _clear: function InplaceEditor_clear()
-  {
+  _clear: function() {
     if (!this.input) {
       // Already cleared.
       return;
     }
 
     this.input.removeEventListener("blur", this._onBlur, false);
     this.input.removeEventListener("keypress", this._onKeyPress, false);
     this.input.removeEventListener("keyup", this._onKeyup, false);
@@ -322,18 +320,17 @@ InplaceEditor.prototype = {
       this.destroy();
     }
   },
 
   /**
    * Keeps the editor close to the size of its input string.  This is pretty
    * crappy, suggestions for improvement welcome.
    */
-  _autosize: function InplaceEditor_autosize()
-  {
+  _autosize: function() {
     // Create a hidden, absolutely-positioned span to measure the text
     // in the input.  Boo.
 
     // We can't just measure the original element because a) we don't
     // change the underlying element's text ourselves (we leave that
     // up to the client), and b) without tweaking the style of the
     // original element, it might wrap differently or something.
     this._measurement =
@@ -347,34 +344,32 @@ InplaceEditor.prototype = {
     style.left = "0";
     copyTextStyles(this.input, this._measurement);
     this._updateSize();
   },
 
   /**
    * Clean up the mess created by _autosize().
    */
-  _stopAutosize: function InplaceEditor_stopAutosize()
-  {
+  _stopAutosize: function() {
     if (!this._measurement) {
       return;
     }
     this._measurement.remove();
     delete this._measurement;
   },
 
   /**
    * Size the editor to fit its current contents.
    */
-  _updateSize: function InplaceEditor_updateSize()
-  {
+  _updateSize: function() {
     // Replace spaces with non-breaking spaces.  Otherwise setting
     // the span's textContent will collapse spaces and the measurement
     // will be wrong.
-    this._measurement.textContent = this.input.value.replace(/ /g, '\u00a0');
+    this._measurement.textContent = this.input.value.replace(/ /g, "\u00a0");
 
     // We add a bit of padding to the end.  Should be enough to fit
     // any letter that could be typed, otherwise we'll scroll before
     // we get a chance to resize.  Yuck.
     let width = this._measurement.offsetWidth + 10;
 
     if (this.multiline) {
       // Make sure there's some content in the current line.  This is a hack to
@@ -386,33 +381,31 @@ InplaceEditor.prototype = {
 
     this.input.style.width = width + "px";
   },
 
   /**
    * Get the width of a single character in the input to properly position the
    * autocompletion popup.
    */
-  _getInputCharWidth: function InplaceEditor_getInputCharWidth()
-  {
+  _getInputCharWidth: function() {
     // Just make the text content to be 'x' to get the width of any character in
     // a monospace font.
     this._measurement.textContent = "x";
     return this._measurement.offsetWidth;
   },
 
    /**
    * Increment property values in rule view.
    *
-   * @param {number} increment
+   * @param {Number} increment
    *        The amount to increase/decrease the property value.
-   * @return {bool} true if value has been incremented.
+   * @return {Boolean} true if value has been incremented.
    */
-  _incrementValue: function InplaceEditor_incrementValue(increment)
-  {
+  _incrementValue: function(increment) {
     let value = this.input.value;
     let selectionStart = this.input.selectionStart;
     let selectionEnd = this.input.selectionEnd;
 
     let newValue = this._incrementCSSValue(value, increment, selectionStart,
                                            selectionEnd);
 
     if (!newValue) {
@@ -429,29 +422,27 @@ InplaceEditor.prototype = {
     }
 
     return true;
   },
 
   /**
    * Increment the property value based on the property type.
    *
-   * @param {string} value
+   * @param {String} value
    *        Property value.
-   * @param {number} increment
+   * @param {Number} increment
    *        Amount to increase/decrease the property value.
-   * @param {number} selStart
+   * @param {Number} selStart
    *        Starting index of the value.
-   * @param {number} selEnd
+   * @param {Number} selEnd
    *        Ending index of the value.
-   * @return {object} object with properties 'value', 'start', and 'end'.
+   * @return {Object} object with properties 'value', 'start', and 'end'.
    */
-  _incrementCSSValue: function InplaceEditor_incrementCSSValue(value, increment,
-                                                               selStart, selEnd)
-  {
+  _incrementCSSValue: function(value, increment, selStart, selEnd) {
     let range = this._parseCSSValue(value, selStart);
     let type = (range && range.type) || "";
     let rawValue = (range ? value.substring(range.start, range.end) : "");
     let incrementedValue = null, selection;
 
     if (type === "num") {
       let newValue = this._incrementRawValue(rawValue, increment);
       if (newValue !== null) {
@@ -484,58 +475,59 @@ InplaceEditor.prototype = {
 
           // select the previous number if the selection is at the end of a
           // percentage sign.
           if (value.charAt(selStart - 1) === "%") {
             --selStart;
           }
         }
       }
-      return this._incrementGenericValue(value, increment, selStart, selEnd, info);
+      return this._incrementGenericValue(value, increment, selStart, selEnd,
+                                         info);
     }
 
     if (incrementedValue === null) {
-      return;
+      return null;
     }
 
     let preRawValue = value.substr(0, range.start);
     let postRawValue = value.substr(range.end);
 
     return {
       value: preRawValue + incrementedValue + postRawValue,
       start: range.start + selection[0],
       end: range.start + selection[1]
     };
   },
 
   /**
    * Parses the property value and type.
    *
-   * @param {string} value
+   * @param {String} value
    *        Property value.
-   * @param {number} offset
+   * @param {Number} offset
    *        Starting index of value.
-   * @return {object} object with properties 'value', 'start', 'end', and 'type'.
+   * @return {Object} object with properties 'value', 'start', 'end', and
+   *         'type'.
    */
-   _parseCSSValue: function InplaceEditor_parseCSSValue(value, offset)
-  {
+   _parseCSSValue: function(value, offset) {
     const reSplitCSS = /(url\("?[^"\)]+"?\)?)|(rgba?\([^)]*\)?)|(hsla?\([^)]*\)?)|(#[\dA-Fa-f]+)|(-?\d*\.?\d+(%|[a-z]{1,4})?)|"([^"]*)"?|'([^']*)'?|([^,\s\/!\(\)]+)|(!(.*)?)/;
     let start = 0;
     let m;
 
     // retreive values from left to right until we find the one at our offset
     while ((m = reSplitCSS.exec(value)) &&
           (m.index + m[0].length < offset)) {
       value = value.substr(m.index + m[0].length);
       start += m.index + m[0].length;
       offset -= m.index + m[0].length;
     }
 
     if (!m) {
-      return;
+      return null;
     }
 
     let type;
     if (m[1]) {
       type = "url";
     } else if (m[2]) {
       type = "rgb";
     } else if (m[3]) {
@@ -553,62 +545,58 @@ InplaceEditor.prototype = {
       type: type
     };
   },
 
   /**
    * Increment the property value for types other than
    * number or hex, such as rgb, hsl, and file names.
    *
-   * @param {string} value
+   * @param {String} value
    *        Property value.
-   * @param {number} increment
+   * @param {Number} increment
    *        Amount to increment/decrement.
-   * @param {number} offset
+   * @param {Number} offset
    *        Starting index of the property value.
-   * @param {number} offsetEnd
+   * @param {Number} offsetEnd
    *        Ending index of the property value.
-   * @param {object} info
+   * @param {Object} info
    *        Object with details about the property value.
-   * @return {object} object with properties 'value', 'start', and 'end'.
+   * @return {Object} object with properties 'value', 'start', and 'end'.
    */
-  _incrementGenericValue:
-  function InplaceEditor_incrementGenericValue(value, increment, offset,
-                                               offsetEnd, info)
-  {
+  _incrementGenericValue: function(value, increment, offset, offsetEnd, info) {
     // Try to find a number around the cursor to increment.
     let start, end;
     // Check if we are incrementing in a non-number context (such as a URL)
     if (/^-?[0-9.]/.test(value.substring(offset, offsetEnd)) &&
       !(/\d/.test(value.charAt(offset - 1) + value.charAt(offsetEnd)))) {
       // We have a number selected, possibly with a suffix, and we are not in
       // the disallowed case of just part of a known number being selected.
       // Use that number.
       start = offset;
       end = offsetEnd;
     } else {
-      // Parse periods as belonging to the number only if we are in a known number
-      // context. (This makes incrementing the 1 in 'image1.gif' work.)
+      // Parse periods as belonging to the number only if we are in a known
+      // number context. (This makes incrementing the 1 in 'image1.gif' work.)
       let pattern = "[" + (info ? "0-9." : "0-9") + "]*";
       let before = new RegExp(pattern + "$").exec(value.substr(0, offset))[0].length;
       let after = new RegExp("^" + pattern).exec(value.substr(offset))[0].length;
 
       start = offset - before;
       end = offset + after;
 
       // Expand the number to contain an initial minus sign if it seems
       // free-standing.
       if (value.charAt(start - 1) === "-" &&
          (start - 1 === 0 || /[ (:,='"]/.test(value.charAt(start - 2)))) {
         --start;
       }
     }
 
-    if (start !== end)
-    {
+    if (start !== end) {
       // Include percentages as part of the incremented number (they are
       // common enough).
       if (value.charAt(end) === "%") {
         ++end;
       }
 
       let first = value.substr(0, start);
       let mid = value.substring(start, end);
@@ -624,27 +612,25 @@ InplaceEditor.prototype = {
         };
       }
     }
   },
 
   /**
    * Increment the property value for numbers.
    *
-   * @param {string} rawValue
+   * @param {String} rawValue
    *        Raw value to increment.
-   * @param {number} increment
+   * @param {Number} increment
    *        Amount to increase/decrease the raw value.
-   * @param {object} info
+   * @param {Object} info
    *        Object with info about the property value.
-   * @return {string} the incremented value.
+   * @return {String} the incremented value.
    */
-  _incrementRawValue:
-  function InplaceEditor_incrementRawValue(rawValue, increment, info)
-  {
+  _incrementRawValue: function(rawValue, increment, info) {
     let num = parseFloat(rawValue);
 
     if (isNaN(num)) {
       return null;
     }
 
     let number = /\d+(\.\d+)?/.exec(rawValue);
     let units = rawValue.substr(number.index + number[0].length);
@@ -662,35 +648,33 @@ InplaceEditor.prototype = {
     newValue = newValue.toString();
 
     return newValue + units;
   },
 
   /**
    * Increment the property value for hex.
    *
-   * @param {string} value
+   * @param {String} value
    *        Property value.
-   * @param {number} increment
+   * @param {Number} increment
    *        Amount to increase/decrease the property value.
-   * @param {number} offset
+   * @param {Number} offset
    *        Starting index of the property value.
-   * @param {number} offsetEnd
+   * @param {Number} offsetEnd
    *        Ending index of the property value.
-   * @return {object} object with properties 'value' and 'selection'.
+   * @return {Object} object with properties 'value' and 'selection'.
    */
-  _incHexColor:
-  function InplaceEditor_incHexColor(rawValue, increment, offset, offsetEnd)
-  {
+  _incHexColor: function(rawValue, increment, offset, offsetEnd) {
     // Return early if no part of the rawValue is selected.
     if (offsetEnd > rawValue.length && offset >= rawValue.length) {
-      return;
+      return null;
     }
     if (offset < 1 && offsetEnd <= 1) {
-      return;
+      return null;
     }
     // Ignore the leading #.
     rawValue = rawValue.substr(1);
     --offset;
     --offsetEnd;
 
     // Clamp the selection to within the actual value.
     offset = Math.max(offset, 0);
@@ -702,49 +686,49 @@ InplaceEditor.prototype = {
       rawValue = rawValue.charAt(0) + rawValue.charAt(0) +
                  rawValue.charAt(1) + rawValue.charAt(1) +
                  rawValue.charAt(2) + rawValue.charAt(2);
       offset *= 2;
       offsetEnd *= 2;
     }
 
     if (rawValue.length !== 6) {
-      return;
+      return null;
     }
 
     // If no selection, increment an adjacent color, preferably one to the left.
     if (offset === offsetEnd) {
       if (offset === 0) {
         offsetEnd = 1;
       } else {
         offset = offsetEnd - 1;
       }
     }
 
     // Make the selection cover entire parts.
     offset -= offset % 2;
     offsetEnd += offsetEnd % 2;
 
     // Remap the increments from [0.1, 1, 10] to [1, 1, 16].
-    if (-1 < increment && increment < 1) {
+    if (increment > -1 && increment < 1) {
       increment = (increment < 0 ? -1 : 1);
     }
     if (Math.abs(increment) === 10) {
       increment = (increment < 0 ? -16 : 16);
     }
 
     let isUpper = (rawValue.toUpperCase() === rawValue);
 
     for (let pos = offset; pos < offsetEnd; pos += 2) {
       // Increment the part in [pos, pos+2).
       let mid = rawValue.substr(pos, 2);
       let value = parseInt(mid, 16);
 
       if (isNaN(value)) {
-        return;
+        return null;
       }
 
       mid = Math.min(Math.max(value + increment, 0), 255).toString(16);
 
       while (mid.length < 2) {
         mid = "0" + mid;
       }
       if (isUpper) {
@@ -758,99 +742,101 @@ InplaceEditor.prototype = {
       value: "#" + rawValue,
       selection: [offset + 1, offsetEnd + 1]
     };
   },
 
   /**
    * Cycle through the autocompletion suggestions in the popup.
    *
-   * @param {boolean} aReverse
+   * @param {Boolean} reverse
    *        true to select previous item from the popup.
-   * @param {boolean} aNoSelect
+   * @param {Boolean} noSelect
    *        true to not select the text after selecting the newly selectedItem
    *        from the popup.
    */
-  _cycleCSSSuggestion:
-  function InplaceEditor_cycleCSSSuggestion(aReverse, aNoSelect)
-  {
+  _cycleCSSSuggestion: function(reverse, noSelect) {
     // selectedItem can be null when nothing is selected in an empty editor.
-    let {label, preLabel} = this.popup.selectedItem || {label: "", preLabel: ""};
-    if (aReverse) {
+    let {label, preLabel} = this.popup.selectedItem ||
+                            {label: "", preLabel: ""};
+    if (reverse) {
       this.popup.selectPreviousItem();
     } else {
       this.popup.selectNextItem();
     }
+
     this._selectedIndex = this.popup.selectedIndex;
     let input = this.input;
     let pre = "";
+
     if (input.selectionStart < input.selectionEnd) {
       pre = input.value.slice(0, input.selectionStart);
-    }
-    else {
+    } else {
       pre = input.value.slice(0, input.selectionStart - label.length +
-                                 preLabel.length);
+                              preLabel.length);
     }
+
     let post = input.value.slice(input.selectionEnd, input.value.length);
     let item = this.popup.selectedItem;
     let toComplete = item.label.slice(item.preLabel.length);
     input.value = pre + toComplete + post;
-    if (!aNoSelect) {
+
+    if (!noSelect) {
       input.setSelectionRange(pre.length, pre.length + toComplete.length);
-    }
-    else {
+    } else {
       input.setSelectionRange(pre.length + toComplete.length,
                               pre.length + toComplete.length);
     }
+
     this._updateSize();
     // This emit is mainly for the purpose of making the test flow simpler.
     this.emit("after-suggest");
   },
 
   /**
    * Call the client's done handler and clear out.
    */
-  _apply: function InplaceEditor_apply(aEvent, direction)
-  {
+  _apply: function(event, direction) {
     if (this._applied) {
-      return;
+      return null;
     }
 
     this._applied = true;
 
     if (this.done) {
       let val = this.cancelled ? this.initial : this.currentInputValue;
       return this.done(val, !this.cancelled, direction);
     }
 
     return null;
   },
 
   /**
    * Handle loss of focus by calling done if it hasn't been called yet.
    */
-  _onBlur: function InplaceEditor_onBlur(aEvent, aDoNotClear)
-  {
-    if (aEvent && this.popup && this.popup.isOpen &&
+  _onBlur: function(event, doNotClear) {
+    if (event && this.popup && this.popup.isOpen &&
         this.popup.selectedIndex >= 0) {
       let label, preLabel;
+
       if (this._selectedIndex === undefined) {
-        ({label, preLabel} = this.popup.getItemAtIndex(this.popup.selectedIndex));
-      }
-      else {
+        ({label, preLabel} =
+          this.popup.getItemAtIndex(this.popup.selectedIndex));
+      } else {
         ({label, preLabel} = this.popup.getItemAtIndex(this._selectedIndex));
       }
+
       let input = this.input;
       let pre = "";
+
       if (input.selectionStart < input.selectionEnd) {
         pre = input.value.slice(0, input.selectionStart);
-      }
-      else {
+      } else {
         pre = input.value.slice(0, input.selectionStart - label.length +
-                                   preLabel.length);
+                                preLabel.length);
       }
       let post = input.value.slice(input.selectionEnd, input.value.length);
       let item = this.popup.selectedItem;
       this._selectedIndex = this.popup.selectedIndex;
       let toComplete = item.label.slice(item.preLabel.length);
       input.value = pre + toComplete + post;
       input.setSelectionRange(pre.length + toComplete.length,
                               pre.length + toComplete.length);
@@ -868,196 +854,195 @@ InplaceEditor.prototype = {
       this.popup.hidePopup();
       // Content type other than CSS_MIXED is used in rule-view where the values
       // are live previewed. So we apply the value before returning.
       if (this.contentType != CONTENT_TYPES.CSS_MIXED) {
         this._apply();
       }
       return;
     }
+
     this._apply();
-    if (!aDoNotClear) {
+
+    if (!doNotClear) {
       this._clear();
     }
   },
 
   /**
    * Handle the input field's keypress event.
    */
-  _onKeyPress: function InplaceEditor_onKeyPress(aEvent)
-  {
+  _onKeyPress: function(event) {
     let prevent = false;
 
     const largeIncrement = 100;
     const mediumIncrement = 10;
     const smallIncrement = 0.1;
 
     let increment = 0;
 
-    if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP
-       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
+    if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_UP ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP) {
       increment = 1;
-    } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN
-       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
+    } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DOWN ||
+               event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
       increment = -1;
     }
 
-    if (aEvent.shiftKey && !aEvent.altKey) {
-      if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP
-           ||  aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
+    if (event.shiftKey && !event.altKey) {
+      if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
+          event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
         increment *= largeIncrement;
       } else {
         increment *= mediumIncrement;
       }
-    } else if (aEvent.altKey && !aEvent.shiftKey) {
+    } else if (event.altKey && !event.shiftKey) {
       increment *= smallIncrement;
     }
 
     // Use default cursor movement rather than providing auto-suggestions.
-    if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME
-        || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END
-        || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP
-        || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
+    if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_HOME ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_END ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN) {
       this._preventSuggestions = true;
     }
 
     let cycling = false;
-    if (increment && this._incrementValue(increment) ) {
+    if (increment && this._incrementValue(increment)) {
       this._updateSize();
       prevent = true;
       cycling = true;
     } else if (increment && this.popup && this.popup.isOpen) {
       cycling = true;
       prevent = true;
       this._cycleCSSSuggestion(increment > 0);
       this._doValidation();
     }
 
-    if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
-        aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
-        aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
-        aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
+    if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_DELETE ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_LEFT ||
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RIGHT) {
       if (this.popup && this.popup.isOpen) {
         this.popup.hidePopup();
       }
-    } else if (!cycling && !aEvent.metaKey && !aEvent.altKey && !aEvent.ctrlKey) {
+    } else if (!cycling && !event.metaKey && !event.altKey && !event.ctrlKey) {
       this._maybeSuggestCompletion();
     }
 
     if (this.multiline &&
-        aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
-        aEvent.shiftKey) {
+        event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
+        event.shiftKey) {
       prevent = false;
-    } else if (this._advanceChars(aEvent.charCode, this.input.value,
-                                 this.input.selectionStart)
-       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN
-       || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
+    } else if (this._advanceChars(event.charCode, this.input.value,
+                                  this.input.selectionStart) ||
+               event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
+               event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
       prevent = true;
 
       let direction = FOCUS_FORWARD;
-      if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
-          aEvent.shiftKey) {
+      if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+          event.shiftKey) {
         if (this.stopOnShiftTab) {
           direction = null;
         } else {
           direction = FOCUS_BACKWARD;
         }
       }
       if ((this.stopOnReturn &&
-           aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) ||
-          (this.stopOnTab && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
-           !aEvent.shiftKey)) {
+           event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) ||
+          (this.stopOnTab && event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+           !event.shiftKey)) {
         direction = null;
       }
 
       // Now we don't want to suggest anything as we are moving out.
       this._preventSuggestions = true;
       // But we still want to show suggestions for css values. i.e. moving out
       // of css property input box in forward direction
       if (this.contentType == CONTENT_TYPES.CSS_PROPERTY &&
           direction == FOCUS_FORWARD) {
         this._preventSuggestions = false;
       }
 
       let input = this.input;
 
-      if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
+      if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
           this.contentType == CONTENT_TYPES.CSS_MIXED) {
         if (this.popup && input.selectionStart < input.selectionEnd) {
-          aEvent.preventDefault();
+          event.preventDefault();
           input.setSelectionRange(input.selectionEnd, input.selectionEnd);
           this.emit("after-suggest");
           return;
-        }
-        else if (this.popup && this.popup.isOpen) {
-          aEvent.preventDefault();
-          this._cycleCSSSuggestion(aEvent.shiftKey, true);
+        } else if (this.popup && this.popup.isOpen) {
+          event.preventDefault();
+          this._cycleCSSSuggestion(event.shiftKey, true);
           return;
         }
       }
 
-      this._apply(aEvent, direction);
+      this._apply(event, direction);
 
       // Close the popup if open
       if (this.popup && this.popup.isOpen) {
         this.popup.hidePopup();
       }
 
       if (direction !== null && focusManager.focusedElement === input) {
         // If the focused element wasn't changed by the done callback,
         // move the focus as requested.
         let next = moveFocus(this.doc.defaultView, direction);
 
         // If the next node to be focused has been tagged as an editable
         // node, trigger editing using the configured event
         if (next && next.ownerDocument === this.doc && next._editable) {
-          let e = this.doc.createEvent('Event');
+          let e = this.doc.createEvent("Event");
           e.initEvent(next._trigger, true, true);
           next.dispatchEvent(e);
         }
       }
 
       this._clear();
-    } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
+    } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE) {
       // Cancel and blur ourselves.
       // Now we don't want to suggest anything as we are moving out.
       this._preventSuggestions = true;
       // Close the popup if open
       if (this.popup && this.popup.isOpen) {
         this.popup.hidePopup();
       }
       prevent = true;
       this.cancelled = true;
       this._apply();
       this._clear();
-      aEvent.stopPropagation();
-    } else if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
+      event.stopPropagation();
+    } else if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
       // No need for leading spaces here.  This is particularly
       // noticable when adding a property: it's very natural to type
       // <name>: (which advances to the next property) then spacebar.
       prevent = !this.input.value;
     }
 
     if (prevent) {
-      aEvent.preventDefault();
+      event.preventDefault();
     }
   },
 
   /**
    * Handle the input field's keyup event.
    */
-  _onKeyup: function(aEvent) {
+  _onKeyup: function() {
     this._applied = false;
   },
 
   /**
    * Handle changes to the input text.
    */
-  _onInput: function InplaceEditor_onInput(aEvent)
-  {
+  _onInput: function() {
     // Validate the entered value.
     this._doValidation();
 
     // Update size if we're autosizing.
     if (this._measurement) {
       this._updateSize();
     }
 
@@ -1065,30 +1050,29 @@ InplaceEditor.prototype = {
     if (this.change) {
       this.change(this.currentInputValue);
     }
   },
 
   /**
    * Fire validation callback with current input
    */
-  _doValidation: function()
-  {
+  _doValidation: function() {
     if (this.validate && this.input) {
       this.validate(this.input.value);
     }
   },
 
   /**
    * Handles displaying suggestions based on the current input.
    *
-   * @param {boolean} aNoAutoInsert
+   * @param {Boolean} noAutoInsert
    *        true if you don't want to automatically insert the first suggestion
    */
-  _maybeSuggestCompletion: function(aNoAutoInsert) {
+  _maybeSuggestCompletion: function(noAutoInsert) {
     // Input can be null in cases when you intantaneously switch out of it.
     if (!this.input) {
       return;
     }
     let preTimeoutQuery = this.input.value;
     // Since we are calling this method from a keypress event handler, the
     // |input.value| does not include currently typed character. Thus we perform
     // this method async.
@@ -1130,32 +1114,34 @@ InplaceEditor.prototype = {
         let match = /([^\s,.\/]+$)/.exec(query);
         if (match) {
           startCheckQuery = match[0];
         } else {
           startCheckQuery = "";
         }
 
         list =
-          ["!important", ...domUtils.getCSSValuesForProperty(this.property.name)];
+          ["!important",
+           ...domUtils.getCSSValuesForProperty(this.property.name)];
 
         if (query == "") {
           // Do not suggest '!important' without any manually typed character.
           list.splice(0, 1);
         }
       } else if (this.contentType == CONTENT_TYPES.CSS_MIXED &&
                  /^\s*style\s*=/.test(query)) {
         // Detecting if cursor is at property or value;
         let match = query.match(/([:;"'=]?)\s*([^"';:=]+)?$/);
         if (match && match.length >= 2) {
           if (match[1] == ":") { // We are in CSS value completion
             let propertyName =
               query.match(/[;"'=]\s*([^"';:= ]+)\s*:\s*[^"';:=]*$/)[1];
             list =
-              ["!important;", ...domUtils.getCSSValuesForProperty(propertyName)];
+              ["!important;",
+               ...domUtils.getCSSValuesForProperty(propertyName)];
             let matchLastQuery = /([^\s,.\/]+$)/.exec(match[2] || "");
             if (matchLastQuery) {
               startCheckQuery = matchLastQuery[0];
             } else {
               startCheckQuery = "";
             }
             if (!match[2]) {
               // Don't suggest '!important' without any manually typed character
@@ -1167,17 +1153,17 @@ InplaceEditor.prototype = {
           }
           if (startCheckQuery == null) {
             // This emit is mainly to make the test flow simpler.
             this.emit("after-suggest", "nothing to autocomplete");
             return;
           }
         }
       }
-      if (!aNoAutoInsert) {
+      if (!noAutoInsert) {
         list.some(item => {
           if (startCheckQuery != null && item.startsWith(startCheckQuery)) {
             input.value = query + item.slice(startCheckQuery.length) +
                           input.value.slice(query.length);
             input.setSelectionRange(query.length, query.length + item.length -
                                                   startCheckQuery.length);
             this._updateSize();
             return true;
@@ -1194,69 +1180,64 @@ InplaceEditor.prototype = {
       let length = list.length;
       for (let i = 0, count = 0; i < length && count < MAX_POPUP_ENTRIES; i++) {
         if (startCheckQuery != null && list[i].startsWith(startCheckQuery)) {
           count++;
           finalList.push({
             preLabel: startCheckQuery,
             label: list[i]
           });
-        }
-        else if (count > 0) {
+        } else if (count > 0) {
           // Since count was incremented, we had already crossed the entries
           // which would have started with query, assuming that list is sorted.
           break;
-        }
-        else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
+        } else if (startCheckQuery != null && list[i][0] > startCheckQuery[0]) {
           // We have crossed all possible matches alphabetically.
           break;
         }
       }
 
       if (finalList.length > 1) {
         // Calculate the offset for the popup to be opened.
         let x = (this.input.selectionStart - startCheckQuery.length) *
                 this.inputCharWidth;
         this.popup.setItems(finalList);
         this.popup.openPopup(this.input, x);
-        if (aNoAutoInsert) {
+        if (noAutoInsert) {
           this.popup.selectedIndex = -1;
         }
       } else {
         this.popup.hidePopup();
       }
       // This emit is mainly for the purpose of making the test flow simpler.
       this.emit("after-suggest");
       this._doValidation();
     }, 0);
   }
 };
 
 /**
  * Copy text-related styles from one element to another.
  */
-function copyTextStyles(aFrom, aTo)
-{
-  let win = aFrom.ownerDocument.defaultView;
-  let style = win.getComputedStyle(aFrom);
-  aTo.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
-  aTo.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
-  aTo.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
-  aTo.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
+function copyTextStyles(from, to) {
+  let win = from.ownerDocument.defaultView;
+  let style = win.getComputedStyle(from);
+  to.style.fontFamily = style.getPropertyCSSValue("font-family").cssText;
+  to.style.fontSize = style.getPropertyCSSValue("font-size").cssText;
+  to.style.fontWeight = style.getPropertyCSSValue("font-weight").cssText;
+  to.style.fontStyle = style.getPropertyCSSValue("font-style").cssText;
 }
 
 /**
  * Trigger a focus change similar to pressing tab/shift-tab.
  */
-function moveFocus(aWin, aDirection)
-{
-  return focusManager.moveFocus(aWin, null, aDirection, 0);
+function moveFocus(win, direction) {
+  return focusManager.moveFocus(win, null, direction, 0);
 }
 
-
 XPCOMUtils.defineLazyGetter(this, "focusManager", function() {
   return Services.focus;
 });
 
 XPCOMUtils.defineLazyGetter(this, "CSSPropertyList", function() {
   return domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES).sort();
 });
 
--- a/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
+++ b/browser/devtools/webconsole/test/browser_console_hide_jsterm_when_devtools_chrome_enabled_false.js
@@ -28,16 +28,20 @@ function testObjectInspectorPropertiesAr
 }
 
 function* getVariablesView(hud) {
   function openVariablesView(event, vview) {
     deferred.resolve(vview._variablesView);
   }
 
   let deferred = promise.defer();
+
+  // Filter out other messages to ensure ours stays visible.
+  hud.ui.filterBox.value = "browser_console_hide_jsterm_test";
+
   hud.jsterm.clearOutput();
   hud.jsterm.execute("new Object({ browser_console_hide_jsterm_test: true })");
 
   let [message] = yield waitForMessages({
     webconsole: hud,
     messages: [{
       text: "Object { browser_console_hide_jsterm_test: true }",
       category: CATEGORY_OUTPUT,
--- a/browser/locales/en-US/chrome/browser/devtools/markers.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/markers.properties
@@ -18,17 +18,18 @@ marker.label.styles=Recalculate Style
 marker.label.reflow=Layout
 marker.label.paint=Paint
 marker.label.composite=Composite Layers
 marker.label.javascript=Function Call
 marker.label.parseHTML=Parse HTML
 marker.label.parseXML=Parse XML
 marker.label.domevent=DOM Event
 marker.label.consoleTime=Console
-marker.label.garbageCollection=Incremental GC
+marker.label.garbageCollection2=Garbage Collection
+marker.label.garbageCollection.incremental=Incremental GC
 marker.label.garbageCollection.nonIncremental=Non-incremental GC
 marker.label.cycleCollection=Cycle Collection
 marker.label.cycleCollection.forgetSkippable=CC Graph Reduction
 marker.label.timestamp=Timestamp
 marker.label.unknown=Unknown
 
 # LOCALIZATION NOTE (marker.label.javascript.*):
 # These strings are displayed as JavaScript markers that have special
--- a/browser/locales/en-US/chrome/browser/devtools/performance.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/performance.properties
@@ -87,41 +87,48 @@ category.css=Styles
 category.js=JIT
 category.gc=GC
 category.network=Network
 category.graphics=Graphics
 category.storage=Storage
 category.events=Input & Events
 category.tools=Tools
 
-# LOCALIZATION NOTE (table.ms):
-# This string is displayed in the call tree after units of time in milliseconds.
-table.ms=ms
+# LOCALIZATION NOTE (table.bytes):
+# This string is displayed in the call tree after bytesize units.
+# %S represents the value in bytes.
+table.bytes=%S B
 
-# LOCALIZATION NOTE (table.percentage):
+# LOCALIZATION NOTE (table.ms2):
+# This string is displayed in the call tree after units of time in milliseconds.
+# %S represents the value in milliseconds.
+table.ms2=%S ms
+
+# LOCALIZATION NOTE (table.percentage2):
 # This string is displayed in the call tree after units representing percentages.
-table.percentage=%
+# %S represents the value in percentage with two decimal points, localized.
+table.percentage2=%S%
 
 # LOCALIZATION NOTE (table.root):
 # This string is displayed in the call tree for the root node.
 table.root=(root)
 
 # LOCALIZATION NOTE (table.idle):
 # This string is displayed in the call tree for the idle blocks.
 table.idle=(idle)
 
 # LOCALIZATION NOTE (table.url.tooltiptext):
 # This string is displayed in the call tree as the tooltip text for the url
 # labels which, when clicked, jump to the debugger.
 table.url.tooltiptext=View source in Debugger
 
-# LOCALIZATION NOTE (table.view-optimizations.tooltiptext):
+# LOCALIZATION NOTE (table.view-optimizations.tooltiptext2):
 # This string is displayed in the icon displayed next to frames that
 # have optimization data
-table.view-optimizations.tooltiptext=View optimizations in JIT View
+table.view-optimizations.tooltiptext2=Frame contains JIT optimization data
 
 # LOCALIZATION NOTE (recordingsList.importDialogTitle):
 # This string is displayed as a title for importing a recoring from disk.
 recordingsList.importDialogTitle=Import recording…
 
 # LOCALIZATION NOTE (recordingsList.saveDialogTitle):
 # This string is displayed as a title for saving a recording to disk.
 recordingsList.saveDialogTitle=Save recording…
@@ -160,9 +167,9 @@ timeline.tick=%S ms
 timeline.records=RECORDS
 
 # LOCALIZATION NOTE (profiler.bufferFull):
 # This string is displayed when recording, indicating how much of the
 # buffer is currently be used.
 # %S is the percentage of the buffer used -- there are two "%"s after to escape
 # the % that is actually displayed.
 # Example: "Buffer 54% full"
-profiler.bufferFull=Buffer %S%% full
\ No newline at end of file
+profiler.bufferFull=Buffer %S%% full
--- a/browser/locales/en-US/chrome/browser/migration/migration.properties
+++ b/browser/locales/en-US/chrome/browser/migration/migration.properties
@@ -12,16 +12,17 @@ sourceNameCanary=Google Chrome Canary
 sourceNameChrome=Google Chrome
 sourceNameChromium=Chromium
 sourceNameFirefox=Mozilla Firefox
 sourceName360se=360 Secure Browser
 
 importedBookmarksFolder=From %S
 
 importedSafariReadingList=Reading List (From Safari)
+importedEdgeReadingList=Reading List (From Edge)
 
 # Import Sources
 # Note: When adding an import source for profile reset, add the string name to
 # resetProfile.js if it should be listed in the reset dialog.
 1_ie=Internet Options
 1_edge=Settings
 1_safari=Preferences
 1_chrome=Preferences
--- a/browser/themes/shared/devtools/performance.css
+++ b/browser/themes/shared/devtools/performance.css
@@ -229,18 +229,30 @@
 .call-tree-header[type="samples"],
 .call-tree-cell[type="samples"] {
   width: 4.5vw;
 }
 
 .call-tree-header[type="count"],
 .call-tree-cell[type="count"],
 .call-tree-header[type="self-count"],
-.call-tree-cell[type="self-count"] {
-  width: 9vw;
+.call-tree-cell[type="self-count"],
+.call-tree-header[type="size"],
+.call-tree-cell[type="size"],
+.call-tree-header[type="self-size"],
+.call-tree-cell[type="self-size"],
+.call-tree-header[type="count-percentage"],
+.call-tree-cell[type="count-percentage"],
+.call-tree-header[type="self-count-percentage"],
+.call-tree-cell[type="self-count-percentage"],
+.call-tree-header[type="size-percentage"],
+.call-tree-cell[type="size-percentage"],
+.call-tree-header[type="self-size-percentage"],
+.call-tree-cell[type="self-size-percentage"] {
+  width: 6vw;
 }
 
 .call-tree-header[type="function"],
 .call-tree-cell[type="function"] {
   -moz-box-flex: 1;
 }
 
 .call-tree-header,
@@ -680,19 +692,16 @@ description.opt-icon::before {
   margin: 1px 4px 0px 0px;
 }
 .theme-light .opt-icon::before {
   background-image: url(chrome://browser/skin/devtools/webconsole.svg#light-icons);
 }
 .opt-icon[severity=warning]::before {
   background-position: -24px -24px;
 }
-.opt-icon[type=linkable]::before {
-  cursor: pointer;
-}
 
 ul.frames-list {
   list-style-type: none;
   padding: 0px;
   margin: 0px;
 }
 
 ul.frames-list li {
--- a/dom/apps/Langpacks.jsm
+++ b/dom/apps/Langpacks.jsm
@@ -13,21 +13,26 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIMessageBroadcaster");
 
 this.EXPORTED_SYMBOLS = ["Langpacks"];
 
-let debug = Services.prefs.getBoolPref("dom.mozApps.debug")
-  ? (aMsg) => {
-      dump("-*-*- Langpacks: " + aMsg + "\n");
-    }
-  : (aMsg) => {};
+let debug;
+function debugPrefObserver() {
+  debug = Services.prefs.getBoolPref("dom.mozApps.debug")
+            ? (aMsg) => {
+                dump("-*-*- Langpacks: " + aMsg + "\n");
+              }
+            : (aMsg) => {};
+}
+debugPrefObserver();
+Services.prefs.addObserver("dom.mozApps.debug", debugPrefObserver, false);
 
 /**
   * Langpack support
   *
   * Manifest format is:
   *
   * "languages-target" : { "app://*.gaiamobile.org/manifest.webapp": "2.2" },
   * "languages-provided": {
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -102,20 +102,24 @@ XPCOMUtils.defineLazyGetter(this, "libcu
 // On Android, define the "debug" function as a binding of the "d" function
 // from the AndroidLog module so it gets the "debug" priority and a log tag.
 // We always report debug messages on Android because it's unnecessary
 // to restrict reporting, per bug 1003469.
 let debug = Cu.import("resource://gre/modules/AndroidLog.jsm", {})
               .AndroidLog.d.bind(null, "Webapps");
 #else
 // Elsewhere, report debug messages only if dom.mozApps.debug is set to true.
-// The pref is only checked once, on startup, so restart after changing it.
-let debug = Services.prefs.getBoolPref("dom.mozApps.debug")
-              ? (aMsg) => dump("-*- Webapps.jsm : " + aMsg + "\n")
-              : (aMsg) => {};
+let debug;
+function debugPrefObserver() {
+  debug = Services.prefs.getBoolPref("dom.mozApps.debug")
+            ? (aMsg) => dump("-*- Webapps.jsm : " + aMsg + "\n")
+            : (aMsg) => {};
+}
+debugPrefObserver();
+Services.prefs.addObserver("dom.mozApps.debug", debugPrefObserver, false);
 #endif
 
 function getNSPRErrorCode(err) {
   return -1 * ((err) & 0xffff);
 }
 
 function supportUseCurrentProfile() {
   return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
--- a/dom/apps/tests/chrome.ini
+++ b/dom/apps/tests/chrome.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   asmjs/*
+  common.js
   file_bug_945152.html
   file_bug_945152.sjs
 
 [test_apps_service.xul]
 [test_bug_945152.html]
 skip-if = os != 'linux'
 [test_manifest_helper.xul]
 [test_operator_app_install.js]
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/common.js
@@ -0,0 +1,3 @@
+function prepareEnv(cb) {
+  SpecialPowers.pushPrefEnv({"set":[["dom.mozApps.debug", true]]}, cb);
+}
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -3,16 +3,17 @@ skip-if = e10s
 support-files =
   addons/application.zip
   addons/invalid.webapp
   addons/invalid.webapp^headers^
   addons/update.webapp
   addons/update.webapp^headers^
   addons/index.html
   chromeAddCert.js
+  common.js
   file_app.sjs
   file_app.template.html
   file_script.template.js
   file_cached_app.template.appcache
   file_cached_app.template.webapp
   file_hosted_app.template.webapp
   file_hosted_certified.webapp
   file_hosted_certified.webapp^headers^
--- a/dom/apps/tests/test_app_addons.html
+++ b/dom/apps/tests/test_app_addons.html
@@ -2,16 +2,17 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=1042881
 -->
 <html>
   <head>
   <meta charset="utf-8">
   <title>Test for Bug 923897 - Test apps as addons</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="application/javascript;version=1.7">
 /**
   * Test for Bug 923897
   * This file covers testing addons.
   *
   * The setup is as follows:
   * - app is installed and offers both script and css to inject in
@@ -198,17 +199,17 @@ function runTest() {
   openPage("http://mochi.test:8888/tests/dom/apps/tests/addons/index.html",
     ["Lorem ipsum", "rgb(0, 0, 0)",
      "Uncustomized content", "rgb(0, 0, 0)"]);
   yield undefined;
 }
 
   </script>
   </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 <div id="container"></div>
 </body>
 </html>
--- a/dom/apps/tests/test_app_enabled.html
+++ b/dom/apps/tests/test_app_enabled.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1XXXXXX}
 -->
 <head>
   <title>Test for Bug {1072090}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1072090}">Mozilla Bug {1072090}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -116,14 +117,14 @@ function runTest() {
 
   request = navigator.mozApps.mgmt.getAll();
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   is(request.result.length, initialAppsCount, "All apps are uninstalled.");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_app_update.html
+++ b/dom/apps/tests/test_app_update.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=826058
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 826058</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript;version=1.7">
 
   /** Test for Bug 826058 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var gBaseURL = 'http://test/tests/dom/apps/tests/';
@@ -273,17 +274,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function doReload() {
     window.location.reload(true);
   }
 
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=826058">Mozilla Bug 826058</a>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=863337">Mozilla Bug 863337</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
--- a/dom/apps/tests/test_bug_1168300.html
+++ b/dom/apps/tests/test_bug_1168300.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=1168300
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1168300</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript;version=1.7">
 
   /** Test for Bug 1168300 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url = "http://test/tests/dom/apps/tests/file_manifest.json";
@@ -106,17 +107,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     set onobserve (callback) {
       this.callback = callback;
     }
   };
 
   SimpleTest.waitForExplicitFinish();
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1168300">Mozilla Bug 1168300</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 </body>
--- a/dom/apps/tests/test_bug_795164.html
+++ b/dom/apps/tests/test_bug_795164.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=795164
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 795164</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript;version=1.7">
 
   /** Test for Bug 795164 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
@@ -91,17 +92,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     yield undefined;
 
     is(uninstallCount, 2, "mgmt.onuninstall got triggered only twice");
 
     navigator.mozApps.mgmt.onuninstall = null;
   }
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795164">Mozilla Bug 795164</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 </body>
--- a/dom/apps/tests/test_bug_945152.html
+++ b/dom/apps/tests/test_bug_945152.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=945152
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 945152</title>
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
   <script type="application/javascript;version=1.7">
 
   const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
   SimpleTest.waitForExplicitFinish();
 
   const gBaseURL = 'http://test/chrome/dom/apps/tests/';
@@ -157,17 +158,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function finish() {
     SimpleTest.finish();
   }
 
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=945152">Mozilla Bug 945152</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 <div id="container"></div>
 </body>
--- a/dom/apps/tests/test_checkInstalled.html
+++ b/dom/apps/tests/test_checkInstalled.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1191516}
 -->
 <head>
   <title>Test for Bug {1191516}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1191516}">Mozilla Bug {1191516}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -87,14 +88,14 @@ function runTest() {
   yield undefined;
 
   request = navigator.mozApps.mgmt.uninstall(app);
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_import_export.html
+++ b/dom/apps/tests/test_import_export.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={982874}
 -->
 <head>
   <title>Test for Bug {982874}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={982874}">Mozilla Bug {982874}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
@@ -317,14 +318,14 @@ function runTest() {
   // Check that we restored the app registry.
   request = navigator.mozApps.mgmt.getAll();
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   is(request.result.length, initialAppsCount, "All apps are uninstalled.");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_install_dev_mode.html
+++ b/dom/apps/tests/test_install_dev_mode.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1111961}
 -->
 <head>
   <title>Test for Bug {1111961}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1111961}">Mozilla Bug {1111961}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -109,14 +110,14 @@ function runTest() {
 
   request = navigator.mozApps.mgmt.getAll();
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   is(request.result.length, initialAppsCount, "All apps are uninstalled.");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_install_multiple_apps_origin.html
+++ b/dom/apps/tests/test_install_multiple_apps_origin.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={778277}
 -->
 <head>
   <title>Test for Bug {778277}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={778277}">Mozilla Bug {778277}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -111,14 +112,14 @@ function runTest() {
 
   request = navigator.mozApps.mgmt.getAll();
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   is(request.result.length, initialAppsCount, "All apps are uninstalled.");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_install_receipts.html
+++ b/dom/apps/tests/test_install_receipts.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={960837}
 -->
 <head>
   <title>Test for Bug {960837}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={960837}">Mozilla Bug {960837}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -201,14 +202,14 @@ function runTest() {
   request.onerror = function() {
     ok(this.error.name == "INVALID_SEGMENTS_NUMBER",
        "Less than 3 segments");
     continueTest();
   }
   yield undefined;
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_langpacks.html
+++ b/dom/apps/tests/test_langpacks.html
@@ -2,16 +2,17 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=1108096
 -->
 <html>
   <head>
   <meta charset="utf-8">
   <title>Test for Bug 1108096 - Langpack support</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="application/javascript;version=1.7">
 /**
   * Test for Bug 1108096
   * This file covers testing langpacks.
   *
   * The setup is as follows:
   * - app is the localizable application.
@@ -300,17 +301,17 @@ function runTest() {
     req.onsuccess = continueTest;
     req.onerror = mozAppsError;
     yield undefined;
   }
 }
 
   </script>
   </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 <div id="container"></div>
 </body>
 </html>
--- a/dom/apps/tests/test_marketplace_pkg_install.html
+++ b/dom/apps/tests/test_marketplace_pkg_install.html
@@ -4,16 +4,17 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=989806
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 989806</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=989806">Mozilla Bug 989806</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -336,15 +337,15 @@ PackagedTestHelper.setSteps([
     checkAppOnInstallSuccess(expected);
     installApp(gMarketplaceStageInstallOrigin, miniManifestURL);
   },
   function() {
     PackagedTestHelper.finish();
   }
 ]);
 
-addLoadEvent(PackagedTestHelper.start);
+addLoadEvent(() => prepareEnv(() => PackagedTestHelper.start()));
 
 </script>
 </pre>
 <div id="container"></div>
 </body>
 </html>
--- a/dom/apps/tests/test_operator_app_install.js
+++ b/dom/apps/tests/test_operator_app_install.js
@@ -208,16 +208,19 @@ function checkAppState(aApp,
   if (aCb && typeof aCb === 'function') {
     aCb();
   }
   return;
 }
 
 var steps = [
   function() {
+    prepareEnv(next);
+  },
+  function() {
     setupDataDirs(next);
     ok(true, "Data directory set up to " + singlevariantDir);
   },
   function() {
     ok(true, "autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(next);
   },
   function() {
--- a/dom/apps/tests/test_operator_app_install.xul
+++ b/dom/apps/tests/test_operator_app_install.xul
@@ -3,16 +3,17 @@
 <?xml-stylesheet type="text/css" href="/tests/SimpleTest/test.css"?>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=893800
 -->
 <window title="Mozilla Bug 893800"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="common.js"/>
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=893800"
      target="_blank">Mozilla Bug 893800</a>
   </body>
 
   <script type="application/javascript;version=1.7" src="test_operator_app_install.js" />
--- a/dom/apps/tests/test_packaged_app_asmjs.html
+++ b/dom/apps/tests/test_packaged_app_asmjs.html
@@ -5,16 +5,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 997886 - Test installing and updating apps with asm.js pre-compiling</title>
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="chrome://mochikit/content/chrome-harness.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
   <script type="application/javascript;version=1.7">
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 SimpleTest.waitForExplicitFinish();
 
 const gBaseURL = 'http://test/chrome/dom/apps/tests/asmjs/';
@@ -224,17 +225,17 @@ function runTest() {
   request = navigator.mozApps.mgmt.uninstall(app);
   request.onerror = mozAppsError;
   request.onsuccess = continueTest;
   yield undefined;
 }
 
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 </pre>
 <div id="container"></div>
 </body>
 </html>
--- a/dom/apps/tests/test_packaged_app_install.html
+++ b/dom/apps/tests/test_packaged_app_install.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={821589}
 -->
 <head>
   <title>Test for Bug {821589} Packaged apps installation and update</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={821589}">Mozilla Bug {821589}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
@@ -342,14 +343,14 @@ var steps = [
   function() {
     info("all done!\n");
     PackagedTestHelper.finish();
   }
 ];
 
 PackagedTestHelper.setSteps(steps);
 
-addLoadEvent(PackagedTestHelper.start);
+addLoadEvent(() => prepareEnv(() => PackagedTestHelper.start()));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={900533}
 -->
 <head>
   <title>Test for Bug {900533} Packaged app update tests</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={900533}">Mozilla Bug {900533}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
@@ -341,14 +342,14 @@ var steps = [
 ];
 
 PackagedTestHelper.setSteps(steps);
 // appToUpdate added to the URL so we get a unique URL for this app.
 // Unique in this case meaning different from the ones used on the
 // install tests
 miniManifestURL = PackagedTestHelper.gSJS + "?getManifest=true&appToUpdate&testNameChange";
 
-addLoadEvent(PackagedTestHelper.start);
+addLoadEvent(() => prepareEnv(() => PackagedTestHelper.start()));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_receipt_operations.html
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={757226}
 -->
 <head>
   <title>Test for Bug {757226} Implement mozApps app.replaceReceipt</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={757226}">Mozilla Bug {757226}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -226,14 +227,14 @@ function runTest() {
   // Uninstall the app.
   request = navigator.mozApps.mgmt.uninstall(app);
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   ok(true, "App uninstalled");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_signed_pkg_install.html
+++ b/dom/apps/tests/test_signed_pkg_install.html
@@ -3,16 +3,17 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=880043
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 880043 Packaged apps installation and update</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=880043">Mozilla Bug 880043</a>
 <p id="display"></p>
 <div id="content" style="display: none">
@@ -264,14 +265,14 @@ var steps = [
     info("all done!");
     PackagedTestHelper.finish();
   }
 ];
 
 PackagedTestHelper.setSteps(steps);
 PackagedTestHelper.gSJSPath = gSJSPath;
 
-addLoadEvent(PackagedTestHelper.start);
+addLoadEvent(() => prepareEnv(() => PackagedTestHelper.start()));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_theme_role.html
+++ b/dom/apps/tests/test_theme_role.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1011738}
 -->
 <head>
   <title>Test for Bug {1011738}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <script type="text/javascript" src="test_packaged_app_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1011738}">Mozilla Bug {1011738}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
@@ -97,14 +98,14 @@ function runTest() {
         ok(false, "We should not install this app!");
         continueTest();
       }
 
     };
   yield undefined;
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_third_party_homescreen.html
+++ b/dom/apps/tests/test_third_party_homescreen.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1097468}
 -->
 <head>
   <title>Test for Bug {1097468}</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1097468}">Mozilla Bug {1097468}</a>
 
 <script class="testbody" type="application/javascript;version=1.7">
 
@@ -187,14 +188,14 @@ function runTest() {
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   yield undefined;
   is(request.result, gManifestURL, "App uninstalled.");
   navigator.mozApps.mgmt.onuninstall = null;
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_uninstall_errors.html
+++ b/dom/apps/tests/test_uninstall_errors.html
@@ -3,16 +3,17 @@
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=830258
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 830258</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="common.js"></script>
   <script type="application/javascript;version=1.7">
 
   /** Test for Bug 830258 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
   var url2 = 'http://test2.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
@@ -91,17 +92,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     request.onerror = function() {
       ok(false, "Fail to uninstall the app2");
       continueTest();
     };
     yield undefined;
   }
   </script>
 </head>
-<body onload="go()">
+<body onload="prepareEnv(go)">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830258">Mozilla Bug 830258</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 </body>
--- a/dom/apps/tests/test_web_app_install.html
+++ b/dom/apps/tests/test_web_app_install.html
@@ -2,16 +2,17 @@
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id={1075716}
 -->
 <head>
   <title>Install web app from manifest with application/manifest+json MIME type</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={1075716}">Mozilla Bug {1075716}</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -160,14 +161,14 @@ function runTest() {
 
   request = navigator.mozApps.mgmt.getAll();
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
   is(request.result.length, initialAppsCount, "Correct number of apps.");
 }
 
-addLoadEvent(go);
+addLoadEvent(() => prepareEnv(go));
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/apps/tests/test_widget.html
+++ b/dom/apps/tests/test_widget.html
@@ -1,18 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for Widget</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="file_test_widget.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
   SimpleTest.waitForExplicitFinish();
   gHasBrowserPermission = false;
-  runTest();
+  prepareEnv(runTest);
   </script>
 </body>
 </html>
--- a/dom/apps/tests/test_widget_browser.html
+++ b/dom/apps/tests/test_widget_browser.html
@@ -1,18 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for DataStore - basic operation on a readonly db</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript" src="file_test_widget.js"></script>
+  <script type="application/javascript" src="common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
   SimpleTest.waitForExplicitFinish();
   gHasBrowserPermission = true;
-  runTest();
+  prepareEnv(runTest);
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/head.js
@@ -0,0 +1,4 @@
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+Cu.import("resource:///modules/Services.jsm");
+var dom_mozApps_debug = Services.prefs.getBoolPref("dom.mozApps.debug");
+Services.prefs.setBoolPref("dom.mozApps.debug", true);
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/tail.js
@@ -0,0 +1,1 @@
+Services.prefs.setBoolPref("dom.mozApps.debug", dom_mozApps_debug);
--- a/dom/apps/tests/unit/test_has_widget_criterion.js
+++ b/dom/apps/tests/unit/test_has_widget_criterion.js
@@ -1,15 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
 Cu.import("resource:///modules/AppsUtils.jsm");
-Cu.import("resource:///modules/Services.jsm");
 
 add_test(function test_has_widget_criterion() {
 
   // follow the logic |_saveWidgetsFullPath|
   let baseUri = Services.io.newURI('http://example.com', null, null);
   let resolve = (aPage)=>{
     let filepath = AppsUtils.getFilePath(aPage);
 
--- a/dom/apps/tests/unit/test_inter_app_comm_service.js
+++ b/dom/apps/tests/unit/test_inter_app_comm_service.js
@@ -1,14 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/InterAppCommService.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 let UUIDGenerator = Cc["@mozilla.org/uuid-generator;1"]
                       .getService(Ci.nsIUUIDGenerator);
 
 const MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
 const FAKE_MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
--- a/dom/apps/tests/unit/test_moziapplication.js
+++ b/dom/apps/tests/unit/test_moziapplication.js
@@ -1,13 +1,11 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const {interfaces: Ci, utils: Cu} = Components;
-
 Cu.import("resource:///modules/AppsUtils.jsm");
 
 add_test(() => {
   let app = {
     name: "TestApp",
     csp: "aCsp",
     installOrigin: "http://installorigin.com",
     origin: "http://www.example.com",
--- a/dom/apps/tests/unit/xpcshell.ini
+++ b/dom/apps/tests/unit/xpcshell.ini
@@ -1,9 +1,9 @@
 [DEFAULT]
-head =
-tail =
+head = head.js
+tail = tail.js
 
 [test_has_widget_criterion.js]
 [test_inter_app_comm_service.js]
 [test_manifestSanitizer.js]
 [test_manifestHelper.js]
 [test_moziapplication.js]
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -266,18 +266,16 @@ user_pref("security.ssl.errorReporting.u
 user_pref("browser.translation.bing.authURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
 user_pref("browser.translation.bing.translateArrayURL", "http://%(server)s/browser/browser/components/translation/test/bing.sjs");
 user_pref("browser.translation.yandex.translateURLOverride", "http://%(server)s/browser/browser/components/translation/test/yandex.sjs");
 user_pref("browser.translation.engine", "bing");
 
 // Make sure we don't try to load snippets from the network.
 user_pref("browser.aboutHomeSnippets.updateUrl", "nonexistent://test");
 
-// Enable debug logging in the mozApps implementation.
-user_pref("dom.mozApps.debug", true);
 // Enable apps customizations
 user_pref("dom.apps.customization.enabled", true);
 
 // Don't fetch or send directory tiles data from real servers
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
--- a/toolkit/devtools/gcli/commands/screenshot.js
+++ b/toolkit/devtools/gcli/commands/screenshot.js
@@ -62,16 +62,23 @@ const standardParams = {
     {
       name: "delay",
       type: { name: "number", min: 0 },
       defaultValue: 0,
       description: l10n.lookup("screenshotDelayDesc"),
       manual: l10n.lookup("screenshotDelayManual")
     },
     {
+      name: "dpi",
+      type: { name: "number", min: 0, allowFloat: true },
+      defaultValue: 0,
+      description: l10n.lookup("screenshotDPIDesc"),
+      manual: l10n.lookup("screenshotDPIManual")
+    },
+    {
       name: "fullpage",
       type: "boolean",
       description: l10n.lookup("screenshotFullPageDesc"),
       manual: l10n.lookup("screenshotFullPageManual")
     },
     {
       name: "selector",
       type: "node",
@@ -282,17 +289,17 @@ function createScreenshotData(document, 
   const scrollbarHeight = {};
   const scrollbarWidth = {};
   winUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
   width -= scrollbarWidth.value;
   height -= scrollbarHeight.value;
 
   const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
   const ctx = canvas.getContext("2d");
-  const ratio = window.devicePixelRatio;
+  const ratio = args.dpi ? args.dpi : window.devicePixelRatio;
   canvas.width = width * ratio;
   canvas.height = height * ratio;
   ctx.scale(ratio, ratio);
   ctx.drawWindow(window, left, top, width, height, "#fff");
   const data = canvas.toDataURL("image/png", "");
 
   // See comment above on bug 961832
   if (args.fullpage) {
--- a/toolkit/devtools/server/actors/performance.js
+++ b/toolkit/devtools/server/actors/performance.js
@@ -206,17 +206,22 @@ const PerformanceFront = exports.Perform
     if (!recording.isRecording()) {
       return void 0;
     }
     let { position: currentPosition, totalSize, generation: currentGeneration } = this._currentBufferStatus;
     let { position: origPosition, generation: origGeneration } = recording.getStartingBufferStatus();
 
     let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) + currentPosition;
     let percent = (normalizedCurrent - origPosition) / totalSize;
-    return percent > 1 ? 1 : percent;
+
+    // Clamp between 0 and 1; can get negative percentage values when a new
+    // recording starts and the currentBufferStatus has not yet been updated. Rather
+    // than fetching another status update, just clamp to 0, and this will be updated
+    // on the next profiler-status event.
+    return percent > 1 ? 1 : percent < 0 ? 0 : percent;
   },
 
   /**
    * Loads a recording from a file.
    *
    * @param {nsILocalFile} file
    *        The file to import the data from.
    * @return {Promise<PerformanceRecordingFront>}
--- a/toolkit/devtools/server/actors/styles.js
+++ b/toolkit/devtools/server/actors/styles.js
@@ -1550,8 +1550,109 @@ function getFontPreviewData(font, doc, o
 
   return {
     dataURL: dataURL,
     size: textWidth + FONT_PREVIEW_OFFSET * 2
   };
 }
 
 exports.getFontPreviewData = getFontPreviewData;
+
+/**
+ * Get the text content of a rule given some CSS text, a line and a column
+ * Consider the following example:
+ * body {
+ *  color: red;
+ * }
+ * p {
+ *  line-height: 2em;
+ *  color: blue;
+ * }
+ * Calling the function with the whole text above and line=4 and column=1 would
+ * return "line-height: 2em; color: blue;"
+ * @param {String} initialText
+ * @param {Number} line (1-indexed)
+ * @param {Number} column (1-indexed)
+ * @return {object} An object of the form {offset: number, text: string}
+ *                  The offset is the index into the input string where
+ *                  the rule text started.  The text is the content of
+ *                  the rule.
+ */
+function getRuleText(initialText, line, column) {
+  if (typeof line === "undefined" || typeof column === "undefined") {
+    throw new Error("Location information is missing");
+  }
+
+  let {offset: textOffset, text} =
+      getTextAtLineColumn(initialText, line, column);
+  let lexer = DOMUtils.getCSSLexer(text);
+
+  // Search forward for the opening brace.
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token) {
+      throw new Error("couldn't find start of the rule");
+    }
+    if (token.tokenType === "symbol" && token.text === "{") {
+      break;
+    }
+  }
+
+  // Now collect text until we see the matching close brace.
+  let braceDepth = 1;
+  let startOffset, endOffset;
+  while (true) {
+    let token = lexer.nextToken();
+    if (!token) {
+      break;
+    }
+    if (startOffset === undefined) {
+      startOffset = token.startOffset;
+    }
+    if (token.tokenType === "symbol") {
+      if (token.text === "{") {
+        ++braceDepth;
+      } else if (token.text === "}") {
+        --braceDepth;
+        if (braceDepth == 0) {
+          break;
+        }
+      }
+    }
+    endOffset = token.endOffset;
+  }
+
+  // If the rule was of the form "selector {" with no closing brace
+  // and no properties, just return an empty string.
+  if (startOffset === undefined) {
+    return {offset: 0, text: ""};
+  }
+  // Note that this approach will preserve comments, despite the fact
+  // that cssTokenizer skips them.
+  return {offset: textOffset + startOffset,
+          text: text.substring(startOffset, endOffset)};
+}
+
+exports.getRuleText = getRuleText;
+
+/**
+ * Return the offset and substring of |text| that starts at the given
+ * line and column.
+ * @param {String} text
+ * @param {Number} line (1-indexed)
+ * @param {Number} column (1-indexed)
+ * @return {object} An object of the form {offset: number, text: string},
+ *                  where the offset is the offset into the input string
+ *                  where the text starts, and where text is the text.
+ */
+function getTextAtLineColumn(text, line, column) {
+  let offset;
+  if (line > 1) {
+    let rx = new RegExp("(?:.*(?:\\r\\n|\\n|\\r|\\f)){" + (line - 1) + "}");
+    offset = rx.exec(text)[0].length;
+  } else {
+    offset = 0;
+  }
+  offset += column - 1;
+  return {offset: offset, text: text.substr(offset) };
+}
+
+exports.getTextAtLineColumn = getTextAtLineColumn;
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_getRuleText.js
@@ -0,0 +1,130 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {getRuleText} = devtools.require("devtools/server/actors/styles");
+
+const TEST_DATA = [
+  {
+    desc: "Empty input",
+    input: "",
+    line: 1,
+    column: 1,
+    throws: true
+  },
+  {
+    desc: "Simplest test case",
+    input: "#id{color:red;background:yellow;}",
+    line: 1,
+    column: 1,
+    expected: {offset: 4, text: "color:red;background:yellow;"}
+  },
+  {
+    desc: "Multiple rules test case",
+    input: "#id{color:red;background:yellow;}.class-one .class-two { position:absolute; line-height: 45px}",
+    line: 1,
+    column: 34,
+    expected: {offset: 56, text: " position:absolute; line-height: 45px"}
+  },
+  {
+    desc: "Unclosed rule",
+    input: "#id{color:red;background:yellow;",
+    line: 1,
+    column: 1,
+    expected: {offset: 4, text: "color:red;background:yellow;"}
+  },
+  {
+    desc: "Null input",
+    input: null,
+    line: 1,
+    column: 1,
+    throws: true
+  },
+  {
+    desc: "Missing loc",
+    input: "#id{color:red;background:yellow;}",
+    throws: true
+  },
+  {
+    desc: "Multi-lines CSS",
+    input: [
+      "/* this is a multi line css */",
+      "body {",
+      "  color: green;",
+      "  background-repeat: no-repeat",
+      "}",
+      " /*something else here */",
+      "* {",
+      "  color: purple;",
+      "}"
+    ].join("\n"),
+    line: 7,
+    column: 1,
+    expected: {offset: 116, text: "\n  color: purple;\n"}
+  },
+  {
+    desc: "Multi-lines CSS and multi-line rule",
+    input: [
+      "/* ",
+       "* some comments",
+       "*/",
+      "",
+      "body {",
+      "    margin: 0;",
+      "    padding: 15px 15px 2px 15px;",
+      "    color: red;",
+      "}",
+      "",
+      "#header .btn, #header .txt {",
+      "    font-size: 100%;",
+      "}",
+      "",
+      "#header #information {",
+      "    color: #dddddd;",
+      "    font-size: small;",
+      "}",
+    ].join("\n"),
+    line: 5,
+    column: 1,
+    expected: {
+      offset: 30,
+      text: "\n    margin: 0;\n    padding: 15px 15px 2px 15px;\n    color: red;\n"}
+  },
+  {
+    desc: "Content string containing a } character",
+    input: "   #id{border:1px solid red;content: '}';color:red;}",
+    line: 1,
+    column: 4,
+    expected: {offset: 7, text: "border:1px solid red;content: '}';color:red;"}
+  },
+];
+
+function run_test() {
+  for (let test of TEST_DATA) {
+    do_print("Starting test: " + test.desc);
+    do_print("Input string " + test.input);
+    let output;
+    try {
+      output = getRuleText(test.input, test.line, test.column);
+      if (test.throws) {
+        do_print("Test should have thrown");
+        do_check_true(false);
+      }
+    } catch (e) {
+      do_print("getRuleText threw an exception with the given input string");
+      if (test.throws) {
+        do_print("Exception expected");
+        do_check_true(true);
+      } else {
+        do_print("Exception unexpected\n" + e);
+        do_check_true(false);
+      }
+    }
+    if (output) {
+      deepEqual(output, test.expected);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_getTextAtLineColumn.js
@@ -0,0 +1,35 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {getTextAtLineColumn} = devtools.require("devtools/server/actors/styles");
+
+const TEST_DATA = [
+  {
+    desc: "simplest",
+    input: "#id{color:red;background:yellow;}",
+    line: 1,
+    column: 5,
+    expected: {offset: 4, text: "color:red;background:yellow;}"}
+  },
+  {
+    desc: "multiple lines",
+    input: "one\n two\n  three",
+    line: 3,
+    column: 3,
+    expected: {offset: 11, text: "three"}
+  },
+];
+
+function run_test() {
+  for (let test of TEST_DATA) {
+    do_print("Starting test: " + test.desc);
+    do_print("Input string " + test.input);
+
+    let output = getTextAtLineColumn(test.input, test.line, test.column);
+    deepEqual(output, test.expected);
+  }
+}
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -73,16 +73,18 @@ skip-if = os == 'linux' # Bug 1176173
 [test_blackboxing-06.js]
 [test_blackboxing-07.js]
 [test_frameactor-01.js]
 [test_frameactor-02.js]
 [test_frameactor-03.js]
 [test_frameactor-04.js]
 [test_frameactor-05.js]
 [test_framearguments-01.js]
+[test_getRuleText.js]
+[test_getTextAtLineColumn.js]
 [test_pauselifetime-01.js]
 [test_pauselifetime-02.js]
 [test_pauselifetime-03.js]
 [test_pauselifetime-04.js]
 [test_threadlifetime-01.js]
 [test_threadlifetime-02.js]
 [test_threadlifetime-03.js]
 [test_threadlifetime-04.js]
--- a/toolkit/locales/en-US/chrome/global/devtools/gclicommands.properties
+++ b/toolkit/locales/en-US/chrome/global/devtools/gclicommands.properties
@@ -91,16 +91,26 @@ screenshotAdvancedOptions=Advanced Optio
 # a dialog when the user is using this command.
 screenshotDelayDesc=Delay (seconds)
 
 # LOCALIZATION NOTE (screenshotDelayManual) A fuller description of the
 # 'delay' parameter to the 'screenshot' command, displayed when the user
 # asks for help on what it does.
 screenshotDelayManual=The time to wait (in seconds) before the screenshot is taken
 
+# LOCALIZATION NOTE (screenshotDPIDesc) A very short string to describe
+# the 'dpi' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotDPIDesc=Dots per inch
+
+# LOCALIZATION NOTE (screenshotDPIManual) A fuller description of the
+# 'dpi' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotDPIManual=The number of dots per inch in the screenshot
+
 # LOCALIZATION NOTE (screenshotFullscreenDesc) A very short string to describe
 # the 'fullscreen' parameter to the 'screenshot' command, which is displayed in
 # a dialog when the user is using this command.
 screenshotFullPageDesc=Entire webpage? (true/false)
 
 # LOCALIZATION NOTE (screenshotFullscreenManual) A fuller description of the
 # 'fullscreen' parameter to the 'screenshot' command, displayed when the user
 # asks for help on what it does.
--- a/toolkit/webapps/moz.build
+++ b/toolkit/webapps/moz.build
@@ -1,14 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Web Apps')
+
 EXTRA_PP_JS_MODULES += [
     'NativeApp.jsm',
     'WebappOSUtils.jsm',
 ]
 
 TEST_DIRS += ['tests']
 
 if CONFIG['MOZ_BUILD_APP'] == 'mobile/android':
--- a/toolkit/webapps/tests/head.js
+++ b/toolkit/webapps/tests/head.js
@@ -581,8 +581,12 @@ let AlertsService = {
   },
 };
 
 AlertsService.init();
 
 SimpleTest.registerCleanupFunction(() => {
   AlertsService.restore();
 });
+
+function prepareEnv(cb) {
+  SpecialPowers.pushPrefEnv({"set":[["dom.mozApps.debug", true]]}, cb);
+}
--- a/toolkit/webapps/tests/test_custom_origin.xul
+++ b/toolkit/webapps/tests/test_custom_origin.xul
@@ -120,16 +120,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_custom_origin_uninstall_install.xul
+++ b/toolkit/webapps/tests/test_custom_origin_uninstall_install.xul
@@ -156,16 +156,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted.xul
+++ b/toolkit/webapps/tests/test_hosted.xul
@@ -142,16 +142,16 @@ let runTest = Task.async(function*() {
   ok(true, "App launchable");
   ok((yield checkFiles(testAppInfo.installedFiles)), "Installation not corrupted");
   ok(!(yield OS.File.exists(OS.Path.join(testAppInfo.installPath, "update"))), "Update directory removed");
   ok((yield checkDateHigherThan(testAppInfo.updatedFiles, installTime)), "Modification date higher");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_checkforupdates_from_webapp_runtime.xul
+++ b/toolkit/webapps/tests/test_hosted_checkforupdates_from_webapp_runtime.xul
@@ -101,16 +101,16 @@ let runTest = Task.async(function*() {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
   ok(Number(yield getState("ManifestQueries")) >= 2, "Two manifest requests");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_icons.xul
+++ b/toolkit/webapps/tests/test_hosted_icons.xul
@@ -142,16 +142,16 @@ let runTest = Task.async(function*() {
     let stat = yield OS.File.stat(testAppInfo.iconFile);
     isfuzzy(stat.size, iconSizes[curTest][0], iconSizes[curTest][1],
             "Icon size correct");
   }
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_launch.xul
+++ b/toolkit/webapps/tests/test_hosted_launch.xul
@@ -88,16 +88,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_launch_no_registry.xul
+++ b/toolkit/webapps/tests/test_hosted_launch_no_registry.xul
@@ -88,16 +88,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_uninstall.xul
+++ b/toolkit/webapps/tests/test_hosted_uninstall.xul
@@ -130,16 +130,16 @@ let runTest = Task.async(function*() {
   } catch (e) {
     exc = e;
   }
   ok(!!exc, "Re-uninstalling failed");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_hosted_update_from_webapp_runtime.xul
+++ b/toolkit/webapps/tests/test_hosted_update_from_webapp_runtime.xul
@@ -105,16 +105,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_install_appcache.xul
+++ b/toolkit/webapps/tests/test_install_appcache.xul
@@ -111,16 +111,16 @@ let runTest = Task.async(function*() {
       }
     } while (size == 0);
     ok(size > 100000, "There are some files in the OfflineCache directory");
   }
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_non_ascii_app_name.xul
+++ b/toolkit/webapps/tests/test_non_ascii_app_name.xul
@@ -143,16 +143,16 @@ let runTest = Task.async(function*() {
   ok(true, "App launchable");
   ok((yield checkFiles(testAppInfo.installedFiles)), "Installation not corrupted");
   ok(!(yield OS.File.exists(OS.Path.join(testAppInfo.installPath, "update"))), "Update directory removed");
   ok((yield checkDateHigherThan(testAppInfo.updatedFiles, installTime)), "Modification date higher");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged.xul
+++ b/toolkit/webapps/tests/test_packaged.xul
@@ -159,16 +159,16 @@ let runTest = Task.async(function*() {
   ok(true, "App launchable");
   ok((yield checkFiles(testAppInfo.installedFiles)), "Installation not corrupted");
   ok(!(yield OS.File.exists(OS.Path.join(testAppInfo.installPath, "update"))), "Update directory removed");
   ok((yield checkDateHigherThan(testAppInfo.updatedFiles, installTime)), "Modification date higher");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_checkforupdates_from_webapp_runtime.xul
+++ b/toolkit/webapps/tests/test_packaged_checkforupdates_from_webapp_runtime.xul
@@ -113,16 +113,16 @@ let runTest = Task.async(function*() {
   }
   ok(!appClosed, "App was launched and is still running");
   ok(Number(yield getState("ManifestQueries")) >= 2, "Two manifest requests");
   ok(Number(yield getState("PackageQueries")) >= 2, "Two package requests");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_icons.xul
+++ b/toolkit/webapps/tests/test_packaged_icons.xul
@@ -155,16 +155,16 @@ let runTest = Task.async(function*() {
 
     // Flush the ZipReaderCache (so that the application.zip file gets closed)
     Services.obs.notifyObservers(null, "chrome-flush-caches", null);
   }
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_launch.xul
+++ b/toolkit/webapps/tests/test_packaged_launch.xul
@@ -96,16 +96,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_launch_no_registry.xul
+++ b/toolkit/webapps/tests/test_packaged_launch_no_registry.xul
@@ -96,16 +96,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_uninstall.xul
+++ b/toolkit/webapps/tests/test_packaged_uninstall.xul
@@ -139,16 +139,16 @@ let runTest = Task.async(function*() {
   } catch (e) {
     exc = e;
   }
   ok(!!exc, "Re-uninstalling failed");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_packaged_update_from_webapp_runtime.xul
+++ b/toolkit/webapps/tests/test_packaged_update_from_webapp_runtime.xul
@@ -119,16 +119,16 @@ let runTest = Task.async(function*() {
   while (!(yield wasAppSJSAccessed()) && !appClosed) {
     yield wait(1000);
   }
   ok(!appClosed, "App was launched and is still running");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/toolkit/webapps/tests/test_webapp_runtime_executable_update.xul
+++ b/toolkit/webapps/tests/test_webapp_runtime_executable_update.xul
@@ -147,16 +147,16 @@ let runTest = Task.async(function*() {
   if (WIN) {
     is((yield runProcess()), 0, "Webapp runtime executable has been replaced");
   }
   is((yield runProcess()), 42, "Webapp runtime executable has been replaced");
 
   SimpleTest.finish();
 });
 
-runTest().catch((e) => {
+prepareEnv(() => runTest().catch((e) => {
   ok(false, "Error during test: " + e);
   SimpleTest.finish();
-});
+}));
 
 ]]>
 </script>
 </window>
--- a/webapprt/moz.build
+++ b/webapprt/moz.build
@@ -1,14 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+with Files('**'):
+    BUG_COMPONENT = ('Firefox', 'Webapp Runtime')
+
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['win']
 elif CONFIG['OS_ARCH'] == 'Darwin':
     DIRS += ['mac']
 elif CONFIG['MOZ_ENABLE_GTK']:
     DIRS += ['gtk']
 
 DIRS += [