diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js index f29cd33a5c98..353130006e86 100644 --- a/browser/app/profile/firefox.js +++ b/browser/app/profile/firefox.js @@ -228,6 +228,8 @@ pref("general.autoScroll", true); 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 diff --git a/browser/components/build/moz.build b/browser/components/build/moz.build index 132362298e9d..65ffe8a846a2 100644 --- a/browser/components/build/moz.build +++ b/browser/components/build/moz.build @@ -24,11 +24,15 @@ LOCAL_INCLUDES += [ 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 diff --git a/browser/components/build/nsBrowserCompsCID.h b/browser/components/build/nsBrowserCompsCID.h index e325e43d4f0c..b854b22dae1b 100644 --- a/browser/components/build/nsBrowserCompsCID.h +++ b/browser/components/build/nsBrowserCompsCID.h @@ -10,6 +10,12 @@ #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 \ diff --git a/browser/components/build/nsModule.cpp b/browser/components/build/nsModule.cpp index 42486b527a1a..6a86f24bff64 100644 --- a/browser/components/build/nsModule.cpp +++ b/browser/components/build/nsModule.cpp @@ -18,6 +18,7 @@ #if defined(XP_WIN) #include "nsIEHistoryEnumerator.h" +#include "nsEdgeReadingListExtractor.h" #endif #include "rdf.h" @@ -42,6 +43,7 @@ NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsGNOMEShellService, Init) #if defined(XP_WIN) NS_GENERIC_FACTORY_CONSTRUCTOR(nsIEHistoryEnumerator) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsEdgeReadingListExtractor) #endif NS_GENERIC_FACTORY_CONSTRUCTOR(nsFeedSniffer) @@ -56,6 +58,7 @@ 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 @@ -71,6 +74,7 @@ static const mozilla::Module::CIDEntry kBrowserCIDs[] = { { &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 @@ -119,6 +123,7 @@ static const mozilla::Module::ContractIDEntry kBrowserContracts[] = { { 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 diff --git a/browser/components/migration/EdgeProfileMigrator.js b/browser/components/migration/EdgeProfileMigrator.js index b2fc8ce3ec52..a40564d14b1e 100644 --- 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]); diff --git a/browser/components/migration/IEProfileMigrator.js b/browser/components/migration/IEProfileMigrator.js index 5addf80bffd8..573944833ae0 100644 --- a/browser/components/migration/IEProfileMigrator.js +++ b/browser/components/migration/IEProfileMigrator.js @@ -84,7 +84,8 @@ History.prototype = { 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, diff --git a/browser/components/migration/MSMigrationUtils.jsm b/browser/components/migration/MSMigrationUtils.jsm index a07761f28100..c9cd07b94a11 100644 --- 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); + }, +}; diff --git a/browser/components/migration/moz.build b/browser/components/migration/moz.build index 8316806348b8..5cefcaeed3da 100644 --- a/browser/components/migration/moz.build +++ b/browser/components/migration/moz.build @@ -10,12 +10,14 @@ JAR_MANIFESTS += ['jar.mn'] XPIDL_SOURCES += [ 'nsIBrowserProfileMigrator.idl', + 'nsIEdgeReadingListExtractor.idl', ] XPIDL_MODULE = 'migration' if CONFIG['OS_ARCH'] == 'WINNT': SOURCES += [ + 'nsEdgeReadingListExtractor.cpp', 'nsIEHistoryEnumerator.cpp', ] diff --git a/browser/components/migration/nsEdgeReadingListExtractor.cpp b/browser/components/migration/nsEdgeReadingListExtractor.cpp new file mode 100644 index 000000000000..3f084d65a1fc --- /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 + +#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 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 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 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 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; + } +} + diff --git a/browser/components/migration/nsEdgeReadingListExtractor.h b/browser/components/migration/nsEdgeReadingListExtractor.h new file mode 100644 index 000000000000..3f6293b79a5c --- /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 + +class nsEdgeReadingListExtractor final : public nsIEdgeReadingListExtractor +{ +public: + nsEdgeReadingListExtractor() {} + + NS_DECL_ISUPPORTS + + NS_DECL_NSIEDGEREADINGLISTEXTRACTOR + +private: + ~nsEdgeReadingListExtractor() {} + + nsresult ConvertJETError(const JET_ERR &err); +}; + +#endif diff --git a/browser/components/migration/nsIEHistoryEnumerator.cpp b/browser/components/migration/nsIEHistoryEnumerator.cpp index 87d48319cef7..cca7e1b6f2e9 100644 --- a/browser/components/migration/nsIEHistoryEnumerator.cpp +++ b/browser/components/migration/nsIEHistoryEnumerator.cpp @@ -7,37 +7,13 @@ #include #include -#include "nsStringAPI.h" -#include "nsNetUtil.h" -#include "prtime.h" -#include "nsIVariant.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 "nsCOMArray.h" +#include "nsIVariant.h" +#include "nsNetUtil.h" +#include "nsStringAPI.h" +#include "nsWindowsMigrationUtils.h" +#include "prtime.h" //////////////////////////////////////////////////////////////////////////////// //// nsIEHistoryEnumerator @@ -106,14 +82,17 @@ nsIEHistoryEnumerator::HasMoreElements(bool* _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; } diff --git a/browser/components/migration/nsIEdgeReadingListExtractor.idl b/browser/components/migration/nsIEdgeReadingListExtractor.idl new file mode 100644 index 000000000000..d391a8d076f6 --- /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); +}; diff --git a/browser/components/migration/nsWindowsMigrationUtils.h b/browser/components/migration/nsWindowsMigrationUtils.h new file mode 100644 index 000000000000..0288d93d3934 --- /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 + diff --git a/browser/components/shell/nsGNOMEShellService.cpp b/browser/components/shell/nsGNOMEShellService.cpp index bfb9e51122d6..f2f125f7669e 100644 --- a/browser/components/shell/nsGNOMEShellService.cpp +++ b/browser/components/shell/nsGNOMEShellService.cpp @@ -317,11 +317,55 @@ nsGNOMEShellService::SetDefaultBrowser(bool aClaimAllTypes, nsCOMPtr 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 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) { @@ -333,6 +377,18 @@ nsGNOMEShellService::GetShouldCheckDefaultBrowser(bool* aResult) } nsresult rv; +#ifndef RELEASE_BUILD + bool skipDefaultBrowserCheck; + rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck); + if (NS_FAILED(rv)) { + return rv; + } + if (skipDefaultBrowserCheck) { + *aResult = false; + return rv; + } +#endif + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return rv; diff --git a/browser/components/shell/nsIShellService.idl b/browser/components/shell/nsIShellService.idl index 4825741b3e4f..64140bc36aba 100644 --- a/browser/components/shell/nsIShellService.idl +++ b/browser/components/shell/nsIShellService.idl @@ -8,7 +8,7 @@ interface nsIDOMElement; interface nsIFile; -[scriptable, uuid(99d2e9f1-3c86-40f7-81fd-3060c18489f0)] +[scriptable, uuid(55cb78a8-2fc4-48f4-9345-ff0e541c5cc4)] interface nsIShellService : nsISupports { /** @@ -38,7 +38,7 @@ interface nsIShellService : nsISupports */ 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 @@ -46,6 +46,13 @@ interface nsIShellService : nsISupports */ 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 diff --git a/browser/components/shell/nsMacShellService.cpp b/browser/components/shell/nsMacShellService.cpp index 00bd09de52f9..6308440cf704 100644 --- a/browser/components/shell/nsMacShellService.cpp +++ b/browser/components/shell/nsMacShellService.cpp @@ -95,11 +95,55 @@ nsMacShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) nsCOMPtr 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 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) { @@ -111,6 +155,18 @@ nsMacShellService::GetShouldCheckDefaultBrowser(bool* aResult) } nsresult rv; +#ifndef RELEASE_BUILD + bool skipDefaultBrowserCheck; + rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck); + if (NS_FAILED(rv)) { + return rv; + } + if (skipDefaultBrowserCheck) { + *aResult = false; + return rv; + } +#endif + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return rv; diff --git a/browser/components/shell/nsShellService.h b/browser/components/shell/nsShellService.h index d5fade37bbb3..516a8423abf4 100644 --- a/browser/components/shell/nsShellService.h +++ b/browser/components/shell/nsShellService.h @@ -4,6 +4,8 @@ * 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" diff --git a/browser/components/shell/nsWindowsShellService.cpp b/browser/components/shell/nsWindowsShellService.cpp index caa5252000f0..6f81e79c3931 100644 --- a/browser/components/shell/nsWindowsShellService.cpp +++ b/browser/components/shell/nsWindowsShellService.cpp @@ -976,17 +976,61 @@ nsWindowsShellService::SetDefaultBrowser(bool aClaimAllTypes, bool aForAllUsers) nsCOMPtr 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 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; @@ -994,6 +1038,18 @@ nsWindowsShellService::GetShouldCheckDefaultBrowser(bool* aResult) } nsresult rv; +#ifndef RELEASE_BUILD + bool skipDefaultBrowserCheck; + rv = GetShouldSkipCheckDefaultBrowser(&skipDefaultBrowserCheck); + if (NS_FAILED(rv)) { + return rv; + } + if (skipDefaultBrowserCheck) { + *aResult = false; + return rv; + } +#endif + nsCOMPtr prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); if (NS_FAILED(rv)) { return rv; diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index 040324398eba..5298034c1d7c 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -108,9 +108,7 @@ browser.jar: 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) diff --git a/browser/devtools/performance/modules/logic/marker-utils.js b/browser/devtools/performance/modules/logic/marker-utils.js index bbd05bfbdc4b..b78ed01bb54c 100644 --- a/browser/devtools/performance/modules/logic/marker-utils.js +++ b/browser/devtools/performance/modules/logic/marker-utils.js @@ -354,13 +354,16 @@ const Formatters = { 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={}) { diff --git a/browser/devtools/performance/modules/widgets/tree-view.js b/browser/devtools/performance/modules/widgets/tree-view.js index fe9d8045e235..c0f596ed63af 100644 --- a/browser/devtools/performance/modules/widgets/tree-view.js +++ b/browser/devtools/performance/modules/widgets/tree-view.js @@ -13,36 +13,75 @@ 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, - selfCount: false, samples: true, - function: true + function: true, + + // allocation columns + count: false, + selfCount: false, + size: false, + selfSize: false, + countPercentage: false, + selfCountPercentage: false, + sizePercentage: false, + selfSizePercentage: false, }; const clamp = (val, min, max) => Math.max(min, Math.min(max, val)); @@ -136,27 +175,14 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, { 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)); - } - 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)); + 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.function) { cells.push(this._createFunctionCell(document, arrowNode, frameInfo.name, frameInfo, this.level)); } @@ -204,38 +230,15 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, { * 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); - 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); + cell.setAttribute("value", value); return cell; }, + _createFunctionCell: function(doc, arrowNode, frameName, frameInfo, frameLevel) { let cell = doc.createElement("hbox"); cell.className = "call-tree-cell"; @@ -243,12 +246,10 @@ CallView.prototype = Heritage.extend(AbstractTreeItem.prototype, { 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); } diff --git a/browser/devtools/performance/performance-controller.js b/browser/devtools/performance/performance-controller.js index da60fa26cf32..32b544c03a55 100644 --- a/browser/devtools/performance/performance-controller.js +++ b/browser/devtools/performance/performance-controller.js @@ -116,7 +116,7 @@ const EVENTS = { // 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", diff --git a/browser/devtools/performance/performance.xul b/browser/devtools/performance/performance.xul index 1852a7811fe7..1d891295cf69 100644 --- a/browser/devtools/performance/performance.xul +++ b/browser/devtools/performance/performance.xul @@ -24,11 +24,9 @@ + - +

diff --git a/dom/apps/tests/test_app_enabled.html b/dom/apps/tests/test_app_enabled.html index 4ed48fc09cde..3e82f2c6a95d 100644 --- a/dom/apps/tests/test_app_enabled.html +++ b/dom/apps/tests/test_app_enabled.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id={1XXXXXX} Test for Bug {1072090} + @@ -121,7 +122,7 @@ function runTest() { is(request.result.length, initialAppsCount, "All apps are uninstalled."); } -addLoadEvent(go); +addLoadEvent(() => prepareEnv(go)); diff --git a/dom/apps/tests/test_app_update.html b/dom/apps/tests/test_app_update.html index cdff6856df79..55421b2cd7d3 100644 --- a/dom/apps/tests/test_app_update.html +++ b/dom/apps/tests/test_app_update.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=826058 Test for Bug 826058 + - + Mozilla Bug 826058 Mozilla Bug 863337

diff --git a/dom/apps/tests/test_bug_1168300.html b/dom/apps/tests/test_bug_1168300.html index ea7c2443fdf2..eb79b1121542 100644 --- a/dom/apps/tests/test_bug_1168300.html +++ b/dom/apps/tests/test_bug_1168300.html @@ -7,6 +7,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=1168300 Test for Bug 1168300 + - + Mozilla Bug 1168300