Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Sun, 30 Aug 2015 22:09:02 -0400
changeset 292644 f2518b8a7b97b5bb477e94bc9131584007aac887
parent 292627 975b92a32949438ae8b4739ce3df15ae34a1dcfb (current diff)
parent 292643 852042d67f3892293b52a0b251173c9dd5cf818f (diff)
child 292645 34893b393686be3adac2791da1a53f5727041ca4
child 292680 3b5192222029cfa069505d2c664c61ece1dfb5fc
child 292693 00c13d486a117525eecd1702e416afad22fedd6f
push id5245
push userraliiev@mozilla.com
push dateThu, 29 Oct 2015 11:30:51 +0000
treeherdermozilla-beta@dac831dc1bd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
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 += [