summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/nsSuiteGlue.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/components/nsSuiteGlue.js')
-rw-r--r--comm/suite/components/nsSuiteGlue.js1676
1 files changed, 1676 insertions, 0 deletions
diff --git a/comm/suite/components/nsSuiteGlue.js b/comm/suite/components/nsSuiteGlue.js
new file mode 100644
index 0000000000..2d0b5600f4
--- /dev/null
+++ b/comm/suite/components/nsSuiteGlue.js
@@ -0,0 +1,1676 @@
+/* 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 XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { XPCOMUtils } = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var { migrateMailnews } =
+ ChromeUtils.import("resource:///modules/mailnewsMigrator.js");
+var { ExtensionSupport } =
+ ChromeUtils.import("resource:///modules/ExtensionSupport.jsm");
+var { LightweightThemeConsumer } =
+ ChromeUtils.import("resource://gre/modules/LightweightThemeConsumer.jsm");
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ AddonManager: "resource://gre/modules/AddonManager.jsm",
+ LoginManagerParent: "resource://gre/modules/LoginManagerParent.jsm",
+ NetUtil: "resource://gre/modules/NetUtil.jsm",
+ FileUtils: "resource://gre/modules/FileUtils.jsm",
+ OS: "resource://gre/modules/osfile.jsm",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.jsm",
+ PlacesBackups: "resource://gre/modules/PlacesBackups.jsm",
+ AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
+ AutoCompletePopup: "resource://gre/modules/AutoCompletePopup.jsm",
+ DateTimePickerHelper: "resource://gre/modules/DateTimePickerHelper.jsm",
+ BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
+ BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
+ RecentWindow: "resource:///modules/RecentWindow.jsm",
+ Sanitizer: "resource:///modules/Sanitizer.jsm",
+ ShellService: "resource:///modules/ShellService.jsm",
+ DownloadsCommon: "resource:///modules/DownloadsCommon.jsm",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
+ Integration: "resource://gre/modules/Integration.jsm",
+ PermissionUI: "resource:///modules/PermissionUI.jsm",
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(this, "DebuggerServer", () => {
+ var tmp = {};
+ ChromeUtils.import("resource://devtools/shared/Loader.jsm", tmp);
+ return tmp.require("devtools/server/main").DebuggerServer;
+});
+
+var global = this;
+
+var listeners = {
+ mm: {
+ // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN nsBrowserGlue.js
+ "RemoteLogins:findLogins": ["LoginManagerParent"],
+ "RemoteLogins:findRecipes": ["LoginManagerParent"],
+ "RemoteLogins:onFormSubmit": ["LoginManagerParent"],
+ "RemoteLogins:autoCompleteLogins": ["LoginManagerParent"],
+ "RemoteLogins:removeLogin": ["LoginManagerParent"],
+ "RemoteLogins:insecureLoginFormPresent": ["LoginManagerParent"],
+ // PLEASE KEEP THIS LIST IN SYNC WITH THE MOBILE LISTENERS IN nsBrowserGlue.js
+ },
+
+ receiveMessage(modules, data) {
+ let val;
+ for (let module of modules[data.name]) {
+ try {
+ val = global[module].receiveMessage(data) || val;
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ return val;
+ },
+
+ init() {
+ let receiveMessageMM = this.receiveMessage.bind(this, this.mm);
+ for (let message of Object.keys(this.mm)) {
+ Services.mm.addMessageListener(message, receiveMessageMM);
+ }
+ }
+};
+
+// We try to backup bookmarks at idle times, to avoid doing that at shutdown.
+// Number of idle seconds before trying to backup bookmarks 8 minutes.
+const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 15 * 60;
+// Minimum interval between backups. We try to not create more than one backup
+// per interval.
+const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1;
+
+// Devtools Preferences
+const DEBUGGER_REMOTE_ENABLED = "devtools.debugger.remote-enabled";
+const DEBUGGER_REMOTE_PORT = "devtools.debugger.remote-port";
+const DEBUGGER_FORCE_LOCAL = "devtools.debugger.force-local";
+const DEBUGGER_WIFI_VISIBLE = "devtools.remote.wifi.visible";
+const DOWNLOAD_MANAGER_URL = "chrome://communicator/content/downloads/downloadmanager.xul";
+const PREF_FOCUS_WHEN_STARTING = "browser.download.manager.focusWhenStarting";
+const PREF_FLASH_COUNT = "browser.download.manager.flashCount";
+
+var gDownloadManager;
+
+// Constructor
+function SuiteGlue() {
+ XPCOMUtils.defineLazyServiceGetter(this, "_idleService",
+ "@mozilla.org/widget/idleservice;1",
+ "nsIIdleService");
+
+ this._init();
+ extensionDefaults(); // extensionSupport.jsm
+}
+
+SuiteGlue.prototype = {
+ _saveSession: false,
+ _isIdleObserver: false,
+ _isPlacesDatabaseLocked: false,
+ _migrationImportsDefaultBookmarks: false,
+
+ _setPrefToSaveSession: function()
+ {
+ Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
+ },
+
+ _logConsoleAPI: function(aEvent)
+ {
+ const nsIScriptError = Ci.nsIScriptError;
+ var flg = nsIScriptError.errorFlag;
+ switch (aEvent.level) {
+ case "warn":
+ flg = nsIScriptError.warningFlag;
+ case "error":
+ var scriptError = Cc["@mozilla.org/scripterror;1"]
+ .createInstance(nsIScriptError);
+ scriptError.initWithWindowID(Array.from(aEvent.arguments),
+ aEvent.filename, "", aEvent.lineNumber, 0,
+ flg, "content javascript", aEvent.innerID);
+ Services.console.logMessage(scriptError);
+ break;
+ case "log":
+ case "info":
+ Services.console.logStringMessage(Array.from(aEvent.arguments));
+ break;
+ }
+ },
+
+ _setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() {
+ // Assume that a non-zero value for services.sync.autoconnectDelay should override
+ if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) {
+ let prefDelay = Services.prefs.getIntPref("services.sync.autoconnectDelay");
+
+ if (prefDelay > 0)
+ return;
+ }
+
+ // delays are in seconds
+ const MAX_DELAY = 300;
+ let delay = 3;
+ let browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ delay += browserEnum.getNext().gBrowser.tabs.length;
+ }
+ delay = delay <= MAX_DELAY ? delay : MAX_DELAY;
+
+ const {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+ Weave.Service.scheduler.delayedAutoConnect(delay);
+ },
+
+ // nsIObserver implementation
+ observe: function(subject, topic, data)
+ {
+ switch(topic) {
+ case "nsPref:changed":
+ switch (data) {
+ case DEBUGGER_REMOTE_ENABLED:
+ if (this.dbgIsEnabled)
+ this.dbgStart();
+ else
+ this.dbgStop();
+ break;
+ case DEBUGGER_REMOTE_PORT:
+ case DEBUGGER_FORCE_LOCAL:
+ /**
+ * If the server is not on, port changes have nothing to affect.
+ * The new value will be picked up if the server is started.
+ */
+ if (this.dbgIsEnabled)
+ this.dbgRestart();
+ break;
+ case DEBUGGER_WIFI_VISIBLE:
+ // Wifi visibility has changed, we need to restart the debugger
+ // server.
+ if (this.dbgIsEnabled && !Services.prefs.getBoolPref(DEBUGGER_FORCE_LOCAL))
+ this.dbgRestart();
+ break;
+ }
+ break;
+ case "profile-before-change":
+ // Any component depending on Places should be finalized in
+ // _onPlacesShutdown. Any component that doesn't need to act after
+ // the UI has gone should be finalized in _onQuitApplicationGranted.
+ this._dispose();
+ break;
+ case "profile-after-change":
+ this._onProfileAfterChange();
+ break;
+ case "chrome-document-global-created":
+ // Set up lwt, but only if the "lightweightthemes" attr is set on the root
+ // (i.e. in messenger.xul).
+ subject.addEventListener("DOMContentLoaded", () => {
+ if (subject.document.documentElement.hasAttribute("lightweightthemes")) {
+ new LightweightThemeConsumer(subject.document);
+ }
+ }, {once: true});
+ break;
+ case "final-ui-startup":
+ this._onProfileStartup();
+ this._promptForMasterPassword();
+ this._checkForNewAddons();
+ Services.search.init();
+ listeners.init();
+
+ Services.mm.loadFrameScript("chrome://navigator/content/content.js",
+ true);
+ ChromeUtils.import("resource://gre/modules/NotificationDB.jsm");
+ break;
+ case "browser-delayed-startup-finished":
+ // Intended fallthrough.
+ case "mail-startup-done":
+ Services.obs.removeObserver(this, "browser-delayed-startup-finished");
+ Services.obs.removeObserver(this, "mail-startup-done");
+ this._onFirstWindowLoaded(subject);
+ break;
+ case "sessionstore-windows-restored":
+ this._onBrowserStartup(subject);
+ break;
+ case "browser:purge-session-history":
+ // reset the console service's error buffer
+ Services.console.logStringMessage(null); // clear the console (in case it's open)
+ Services.console.reset();
+ break;
+ case "quit-application-requested":
+ this._onQuitRequest(subject, data);
+ break;
+ case "quit-application-granted":
+ this._onQuitApplicationGranted();
+ break;
+ case "browser-lastwindow-close-requested":
+ // The application is not actually quitting, but the last full browser
+ // window is about to be closed.
+ this._onQuitRequest(subject, "lastwindow");
+ break;
+ case "browser-lastwindow-close-granted":
+ if (this._saveSession)
+ this._setPrefToSaveSession();
+ break;
+ case "console-api-log-event":
+ if (Services.prefs.getBoolPref("browser.dom.window.console.enabled"))
+ this._logConsoleAPI(subject.wrappedJSObject);
+ break;
+// case "weave:service:ready":
+// this._setSyncAutoconnectDelay();
+// break;
+// case "weave:engine:clients:display-uri":
+// this._onDisplaySyncURI(subject);
+// break;
+ case "session-save":
+ this._setPrefToSaveSession();
+ subject.QueryInterface(Ci.nsISupportsPRBool);
+ subject.data = true;
+ break;
+ case "places-init-complete":
+ if (!this._migrationImportsDefaultBookmarks)
+ this._initPlaces(false);
+
+ Services.obs.removeObserver(this, "places-init-complete");
+ break;
+ case "idle":
+ this._backupBookmarks();
+ break;
+ case "initial-migration":
+ this._initialMigrationPerformed = true;
+ break;
+ case "browser-search-engine-modified":
+ break;
+ case "notifications-open-settings":
+ // Since this is a web notification, there's probably a browser window.
+ var mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (mostRecentBrowserWindow)
+ mostRecentBrowserWindow.toDataManager("|permissions");
+ break;
+ case "timer-callback":
+ // Load the Login Manager data from disk off the main thread, some time
+ // after startup. If the data is required before the timeout, for example
+ // because a restored page contains a password field, it will be loaded on
+ // the main thread, and this initialization request will be ignored.
+ Services.logins;
+ break;
+ case "handle-xul-text-link":
+ let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool);
+ if (!linkHandled.data) {
+ let mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (mostRecentBrowserWindow) {
+ let dataObj = JSON.parse(data);
+ let where = mostRecentBrowserWindow.whereToOpenLink(dataObj, false, true, true);
+ // Preserve legacy behavior of non-modifier left-clicks
+ // opening in a new selected tab.
+ if (where == "current") {
+ where = "tabfocused";
+ }
+ mostRecentBrowserWindow.openUILinkIn(dataObj.href, where);
+ linkHandled.data = true;
+ }
+ }
+ break;
+ }
+ },
+
+ // nsIWebProgressListener partial implementation
+ onLocationChange: function(aWebProgress, aRequest, aLocation, aFlags)
+ {
+ if (aWebProgress.isTopLevel &&
+ aWebProgress instanceof Ci.nsIDocShell &&
+ aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL &&
+ aWebProgress.useGlobalHistory &&
+ aWebProgress instanceof Ci.nsILoadContext &&
+ !aWebProgress.usePrivateBrowsing) {
+ switch (aLocation.scheme) {
+ case "about":
+ case "imap":
+ case "news":
+ case "mailbox":
+ case "moz-anno":
+ case "view-source":
+ case "chrome":
+ case "resource":
+ case "data":
+ case "wyciwyg":
+ case "javascript":
+ break;
+ default:
+ Services.prefs.setStringPref("browser.history.last_page_visited",
+ aLocation.spec);
+ break;
+ }
+ }
+ },
+
+ // initialization (called on application startup)
+ _init: function()
+ {
+ // observer registration
+ Services.obs.addObserver(this, "profile-before-change", true);
+ Services.obs.addObserver(this, "profile-after-change", true);
+ Services.obs.addObserver(this, "final-ui-startup", true);
+ Services.obs.addObserver(this, "browser-delayed-startup-finished", true);
+ Services.obs.addObserver(this, "mail-startup-done", true);
+ Services.obs.addObserver(this, "sessionstore-windows-restored", true);
+ Services.obs.addObserver(this, "browser:purge-session-history", true);
+ Services.obs.addObserver(this, "quit-application-requested", true);
+ Services.obs.addObserver(this, "quit-application-granted", true);
+ Services.obs.addObserver(this, "browser-lastwindow-close-requested", true);
+ Services.obs.addObserver(this, "browser-lastwindow-close-granted", true);
+ Services.obs.addObserver(this, "console-api-log-event", true);
+ Services.obs.addObserver(this, "weave:service:ready", true);
+ Services.obs.addObserver(this, "weave:engine:clients:display-uri", true);
+ Services.obs.addObserver(this, "session-save", true);
+ Services.obs.addObserver(this, "places-init-complete", true);
+ Services.obs.addObserver(this, "browser-search-engine-modified", true);
+ Services.obs.addObserver(this, "notifications-open-settings", true);
+ Services.obs.addObserver(this, "chrome-document-global-created", true);
+ Services.prefs.addObserver("devtools.debugger.", this, true);
+ Services.obs.addObserver(this, "handle-xul-text-link", true);
+ Cc['@mozilla.org/docloaderservice;1']
+ .getService(Ci.nsIWebProgress)
+ .addProgressListener(this, Ci.nsIWebProgress.NOTIFY_LOCATION);
+ },
+
+ // cleanup (called on application shutdown)
+ _dispose: function BG__dispose() {
+ try {
+ Services.obs.removeObserver(this, "chrome-document-global-created");
+ }
+ catch (ex) {}
+ if (this._isIdleObserver) {
+ this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME_SEC);
+ delete this._isIdleObserver;
+ }
+ },
+
+ // profile is available
+ _onProfileAfterChange: function()
+ {
+ // check if we're in safe mode
+ if (Services.appinfo.inSafeMode) {
+ Services.ww.openWindow(null, "chrome://communicator/content/safeMode.xul",
+ "_blank", "chrome,centerscreen,modal,resizable=no", null);
+ }
+ this._copyDefaultProfileFiles();
+ },
+
+ // profile startup handler (contains profile initialization routines)
+ _onProfileStartup: function()
+ {
+ this._migrateUI();
+ this._migrateUI2();
+ migrateMailnews(); // mailnewsMigrator.js
+
+ Sanitizer.onStartup();
+
+ var timer = Cc["@mozilla.org/timer;1"]
+ .createInstance(Ci.nsITimer);
+ timer.init(this, 3000, timer.TYPE_ONE_SHOT);
+ },
+
+ /**
+ * Determine if the UI has been upgraded for this release. If not
+ * reset or migrate some user configurations depending on the migration
+ * level.
+ */
+ _migrateUI() {
+ const UI_VERSION = 10;
+
+ // If the pref is not set this is a new or pre SeaMonkey 2.49 profile.
+ // We can't tell so we just run migration with version 0.
+ let currentUIVersion =
+ Services.prefs.getIntPref("suite.migration.version", 0);
+
+ if (currentUIVersion >= UI_VERSION)
+ return;
+
+ if (currentUIVersion < 1) {
+ // Run any migrations due prior to 2.49.
+ this._updatePrefs();
+ this._migrateDownloadPrefs();
+
+ // Migrate remote content exceptions for email addresses which are
+ // encoded as chrome URIs.
+ let permissionsDB =
+ Services.dirsvc.get("ProfD", Ci.nsIFile);
+ permissionsDB.append("permissions.sqlite");
+ let db = Services.storage.openDatabase(permissionsDB);
+
+ try {
+ let statement = db.createStatement(
+ "select origin, permission from moz_perms where " +
+ // Avoid 'like' here which needs to be escaped.
+ " substr(origin, 1, 28) = 'chrome://messenger/content/?';");
+
+ try {
+ while (statement.executeStep()) {
+ let origin = statement.getUTF8String(0);
+ let permission = statement.getInt32(1);
+ Services.console.logStringMessage("Mail-Image-Perm Mig: " + origin);
+ Services.perms.remove(
+ Services.io.newURI(origin), "image");
+ origin = origin.replace("chrome://messenger/content/?",
+ "chrome://messenger/content/");
+ Services.perms.add(
+ Services.io.newURI(origin), "image", permission);
+ }
+ } finally {
+ statement.finalize();
+ }
+
+ // Sadly we still need to clear the database manually. Experiments
+ // showed that the permissions manager deletes only one record.
+ db.defaultTransactionType = Ci.mozIStorageConnection.TRANSACTION_EXCLUSIVE;
+ db.beginTransaction();
+
+ try {
+ db.executeSimpleSQL("delete from moz_perms where " +
+ " substr(origin, 1, 28) = 'chrome://messenger/content/?';");
+ db.commitTransaction();
+ } catch (ex) {
+ db.rollbackTransaction();
+ throw ex;
+ }
+ } finally {
+ db.close();
+ }
+ }
+
+ // Migration of disabled safebrowsing-phishing setting after pref renaming.
+ if (currentUIVersion < 2) {
+ try {
+ if (!Services.prefs.getBoolPref("browser.safebrowsing.enabled")) {
+ Services.prefs.setBoolPref("browser.safebrowsing.phishing.enabled", false);
+ Services.prefs.clearUserPref("browser.safebrowsing.enabled");
+ }
+ } catch (ex) {}
+ }
+
+ // Pretend currentUIVersion 3 never happened (used in 2.57 for a time).
+
+ // Remove obsolete download preferences set by user.
+ if (currentUIVersion < 4) {
+ try {
+ if (Services.prefs.prefHasUserValue("browser.download.manager.showAlertOnComplete")) {
+ Services.prefs.clearUserPref("browser.download.manager.showAlertOnComplete");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.showAlertInterval")) {
+ Services.prefs.clearUserPref("browser.download.manager.showAlertInterval");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.retention")) {
+ Services.prefs.clearUserPref("browser.download.manager.retention");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.quitBehavior")) {
+ Services.prefs.clearUserPref("browser.download.manager.quitBehavior");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.scanWhenDone")) {
+ Services.prefs.clearUserPref("browser.download.manager.scanWhenDone");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.showWhenStarting")) {
+ Services.prefs.clearUserPref("browser.download.manager.showWhenStarting");
+ }
+ if (Services.prefs.prefHasUserValue("browser.download.manager.closeWhenDone")) {
+ Services.prefs.clearUserPref("browser.download.manager.closeWhenDone");
+ }
+ } catch (ex) {}
+ }
+
+ if (currentUIVersion < 5) {
+ // Delete obsolete ssl and strict transport security permissions.
+ let perms = Services.perms.enumerator;
+ while (perms.hasMoreElements()) {
+ let perm = perms.getNext();
+ if (perm.type == "falsestart-rc4" ||
+ perm.type == "falsestart-rsa" ||
+ perm.type == "sts/use" ||
+ perm.type == "sts/subd") {
+ Services.perms.removePermission(perm);
+ }
+ }
+ }
+
+ // Pretend currentUIVersion 6 and 7 never happened (used in 2.57 for a
+ // time).
+
+ // Migrate sanitizer options.
+ if (currentUIVersion < 8) {
+ const prefs = [ "history", "urlbar", "formdata", "passwords",
+ "downloads", "cookies", "cache", "sessions",
+ "offlineApps" ];
+
+ for (let pref of prefs) {
+ try {
+ let prefOld = "privacy.item." + pref;
+
+ // Migrate user value otherwise use default.
+ // Only the names have changed but not the default values.
+ if (Services.prefs.prefHasUserValue(prefOld)) {
+ let prefCpd = "privacy.cpd." + pref;
+ let prefShutdown = "privacy.clearOnShutdown." + pref;
+
+ // If it has a value this should never fail.
+ let oldValue = Services.prefs.getBoolPref(prefOld);
+ Services.prefs.setBoolPref(prefCpd, oldValue);
+ Services.prefs.setBoolPref(prefShutdown, oldValue);
+ Services.prefs.clearUserPref(prefOld);
+ }
+ } catch (ex) {
+ // Better safe than sorry.
+ Cu.reportError(ex);
+ }
+ }
+
+ // We might bring this back later but currently set to default.
+ Services.prefs.clearUserPref("privacy.sanitize.promptOnSanitize");
+
+ // As a precaution set to default if the user has enabled
+ // clearing data on shutdown because there will no longer be
+ // a possible prompt.
+ Services.prefs.clearUserPref("privacy.sanitize.sanitizeOnShutdown");
+ }
+
+ // Migrate mail tab options.
+ if (currentUIVersion < 9) {
+ const tabPrefs = [ "autoHide", "opentabfor.doubleclick",
+ "opentabfor.middleclick" ];
+ for (let pref of tabPrefs) {
+ try {
+ let prefBT = "browser.tabs." + pref;
+
+ // Copy user value otherwise use default.
+ if (Services.prefs.prefHasUserValue(prefBT)) {
+ let prefMT = "mail.tabs." + pref;
+
+ // If it has a value this should never fail.
+ let valueBT = Services.prefs.getBoolPref(prefBT);
+ Services.prefs.setBoolPref(prefMT, valueBT);
+ }
+ } catch (ex) {
+ // Better safe than sorry.
+ Cu.reportError(ex);
+ }
+ }
+
+ // We might bring this back later but currently set to default.
+ Services.prefs.clearUserPref("browser.tabs.opentabfor.doubleclick");
+ }
+
+ // Migrate the old requested locales prefs to use the new model
+ if (currentUIVersion < 10) {
+ const SELECTED_LOCALE_PREF = "general.useragent.locale";
+ const MATCHOS_LOCALE_PREF = "intl.locale.matchOS";
+
+ if (Services.prefs.prefHasUserValue(MATCHOS_LOCALE_PREF) ||
+ Services.prefs.prefHasUserValue(SELECTED_LOCALE_PREF)) {
+ if (Services.prefs.getBoolPref(MATCHOS_LOCALE_PREF, false)) {
+ Services.locale.setRequestedLocales([]);
+ } else {
+ let locale = Services.prefs.getComplexValue(SELECTED_LOCALE_PREF,
+ Ci.nsIPrefLocalizedString);
+ if (locale) {
+ try {
+ Services.locale.setRequestedLocales([locale.data]);
+ } catch (e) {
+ /* Don't panic if the value is not a valid locale code. */
+ }
+ }
+ }
+ Services.prefs.clearUserPref(SELECTED_LOCALE_PREF);
+ Services.prefs.clearUserPref(MATCHOS_LOCALE_PREF);
+ }
+ }
+
+ // Update the migration version.
+ Services.prefs.setIntPref("suite.migration.version", UI_VERSION);
+ },
+
+ /**
+ * Determine if the UI has been upgraded for this 2.57 or later release.
+ * If not reset or migrate some user configurations depending on the
+ * migration level.
+ * Only migration steps for 2.57 and higher are included in this function.
+ * When the 2.53 branch is retired this function can be merged with
+ * _migrateUI again.
+ */
+ _migrateUI2() {
+ const UI_VERSION2 = 1;
+
+ // If the pref is not set this is a new or pre SeaMonkey 2.57 profile.
+ // We can't tell so we just run migration with version 0.
+ let currentUIVersion2 =
+ Services.prefs.getIntPref("suite.migration.version2", 0);
+
+ if (currentUIVersion2 >= UI_VERSION2)
+ return;
+
+ // Run any migrations due prior to 2.57.
+ if (currentUIVersion2 < 1) {
+ // The XUL directory viewer is no longer provided.
+ try {
+ if (Services.prefs.getIntPref("network.dir.format") == 3) {
+ Services.prefs.setIntPref("network.dir.format", 2);
+ }
+ } catch (ex) {}
+ }
+
+ // Update the migration version.
+ Services.prefs.setIntPref("suite.migration.version2", UI_VERSION2);
+ },
+
+ // Copies additional profile files from the default profile tho the current profile.
+ // Only files not covered by the regular profile creation process.
+ // Currently only the userchrome examples.
+ _copyDefaultProfileFiles: function()
+ {
+ // Copy default chrome example files if they do not exist in the current profile.
+ var profileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ profileDir.append("chrome");
+
+ // The chrome directory in the current/new profile already exists so no copying.
+ if (profileDir.exists())
+ return;
+
+ let defaultProfileDir = Services.dirsvc.get("DefRt",
+ Ci.nsIFile);
+ defaultProfileDir.append("profile");
+ defaultProfileDir.append("chrome");
+
+ if (defaultProfileDir.exists() && defaultProfileDir.isDirectory()) {
+ try {
+ this._copyDir(defaultProfileDir, profileDir);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ // Simple copy function for copying complete aSource Directory to aDestiniation.
+ _copyDir: function(aSource, aDestination)
+ {
+ let enumerator = aSource.directoryEntries;
+
+ while (enumerator.hasMoreElements()) {
+ let file = enumerator.nextFile;
+
+ if (file.isDirectory()) {
+ let subdir = aDestination.clone();
+ subdir.append(file.leafName);
+
+ // Create the target directory. If it already exists continue copying files.
+ try {
+ subdir.create(Ci.nsIFile.DIRECTORY_TYPE,
+ FileUtils.PERMS_DIRECTORY);
+ } catch (ex) {
+ if (ex.result != Cr.NS_ERROR_FILE_ALREADY_EXISTS)
+ throw ex;
+ }
+ // Directory created. Now copy the files.
+ this._copyDir(file, subdir);
+ } else {
+ try {
+ file.copyTo(aDestination, null);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ }
+ },
+
+ // Browser startup complete. All initial windows have opened.
+ _onBrowserStartup: function(aWindow) {
+ // For any add-ons that were installed disabled and can be enabled offer
+ // them to the user.
+ var browser = aWindow.getBrowser();
+ var changedIDs = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED);
+ if (changedIDs.length) {
+ AddonManager.getAddonsByIDs(changedIDs, function(aAddons) {
+ aAddons.forEach(function(aAddon) {
+ // If the add-on isn't user disabled or can't be enabled then skip it.
+ if (!aAddon.userDisabled || !(aAddon.permissions & AddonManager.PERM_CAN_ENABLE))
+ return;
+
+ browser.selectedTab = browser.addTab("about:newaddon?id=" + aAddon.id);
+ })
+ });
+ }
+
+ var notifyBox = browser.getNotificationBox();
+
+ // Show about:rights notification, if needed.
+ if (this._shouldShowRights())
+ this._showRightsNotification(notifyBox);
+
+ // Load the "more info" page for a locked places.sqlite
+ // This property is set earlier by places-database-locked topic.
+ if (this._isPlacesDatabaseLocked) {
+ notifyBox.showPlacesLockedWarning();
+ }
+
+ // Detect if updates are off and warn for outdated builds.
+ if (this._shouldShowUpdateWarning())
+ notifyBox.showUpdateWarning();
+
+ this._checkForDefaultClient(aWindow);
+ },
+
+ // First mail or browser window loaded.
+ _onFirstWindowLoaded: function(aWindow) {
+ AutoCompletePopup.init();
+ DateTimePickerHelper.init();
+
+ if ("@mozilla.org/windows-taskbar;1" in Cc &&
+ Cc["@mozilla.org/windows-taskbar;1"]
+ .getService(Ci.nsIWinTaskbar).available) {
+ let temp = {};
+ ChromeUtils.import("resource:///modules/WindowsJumpLists.jsm", temp);
+ temp.WinTaskbarJumpList.startup();
+ }
+
+ // Initialize the download manager after the app starts so that
+ // auto-resume downloads begin (such as after crashing or quitting with
+ // active downloads) and speeds up the first-load of the download manager.
+ // If the user manually opens the download manager before the init is
+ // done, the downloads will start right away, and initializing again
+ // won't hurt.
+ // Afterwards init the taskbar and eventuall show the download progress if
+ // on a supported platform.
+ (async () => {
+ DownloadsCommon.init();
+ })().catch(ex => {
+ Cu.reportError(ex);
+ }).then(() => {
+ ChromeUtils.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.registerIndicator(aWindow);
+ });
+ },
+
+ /**
+ * Application shutdown handler.
+ */
+ _onQuitApplicationGranted: function()
+ {
+ if (this._saveSession) {
+ this._setPrefToSaveSession();
+ }
+ AutoCompletePopup.uninit();
+ DateTimePickerHelper.uninit();
+ },
+
+ _promptForMasterPassword: function()
+ {
+ if (!Services.prefs.getBoolPref("signon.startup.prompt"))
+ return;
+
+ // Try to avoid the multiple master password prompts on startup scenario
+ // by prompting for the master password upfront.
+ let token = Cc["@mozilla.org/security/pk11tokendb;1"]
+ .getService(Ci.nsIPK11TokenDB)
+ .getInternalKeyToken();
+
+ // Only log in to the internal token if it is already initialized,
+ // otherwise we get a "Change Master Password" dialog.
+ try {
+ if (!token.needsUserInit)
+ token.login(false);
+ } catch (ex) {
+ // If user cancels an exception is expected.
+ }
+ },
+
+ // If new add-ons were installed during startup, open the add-ons manager.
+ _checkForNewAddons: function()
+ {
+ const PREF_EM_NEW_ADDONS_LIST = "extensions.newAddons";
+
+ if (!Services.prefs.prefHasUserValue(PREF_EM_NEW_ADDONS_LIST))
+ return;
+
+ const args = Cc["@mozilla.org/array;1"]
+ .createInstance(Ci.nsIMutableArray);
+ let str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ args.appendElement(str);
+ str = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ str.data = Services.prefs.getCharPref(PREF_EM_NEW_ADDONS_LIST);
+ args.appendElement(str);
+ const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
+ // This window is the "first" to open.
+ // 'alwaysRaised' makes sure it stays in the foreground (though unfocused)
+ // so it is noticed.
+ const EMFEATURES = "all,dialog=no,alwaysRaised";
+ Services.ww.openWindow(null, EMURL, "_blank", EMFEATURES, args);
+
+ Services.prefs.clearUserPref(PREF_EM_NEW_ADDONS_LIST);
+ },
+
+ _onQuitRequest: function(aCancelQuit, aQuitType)
+ {
+ // If user has already dismissed quit request, then do nothing
+ if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
+ return;
+
+ var windowcount = 0;
+ var pagecount = 0;
+ var browserEnum = Services.wm.getEnumerator("navigator:browser");
+ while (browserEnum.hasMoreElements()) {
+ // XXXbz should we skip closed windows here?
+ windowcount++;
+
+ var browser = browserEnum.getNext();
+ var tabbrowser = browser.document.getElementById("content");
+ if (tabbrowser)
+ pagecount += tabbrowser.browsers.length;
+ }
+
+ this._saveSession = false;
+ if (pagecount < 2)
+ return;
+
+ if (aQuitType != "restart" && aQuitType != "lastwindow")
+ aQuitType = "quit";
+
+ var showPrompt = true;
+ try {
+ // browser.warnOnQuit is a hidden global boolean to override all quit prompts
+ // browser.warnOnRestart specifically covers app-initiated restarts where we restart the app
+ // browser.tabs.warnOnClose is the global "warn when closing multiple tabs" pref
+ if (Services.prefs.getIntPref("browser.startup.page") == 3 ||
+ Services.prefs.getBoolPref("browser.sessionstore.resume_session_once") ||
+ !Services.prefs.getBoolPref("browser.warnOnQuit"))
+ showPrompt = false;
+ else if (aQuitType == "restart")
+ showPrompt = Services.prefs.getBoolPref("browser.warnOnRestart");
+ else
+ showPrompt = Services.prefs.getBoolPref("browser.tabs.warnOnClose");
+ } catch (ex) {}
+
+ if (showPrompt) {
+ var quitBundle = Services.strings.createBundle("chrome://communicator/locale/quitDialog.properties");
+ var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+
+ var appName = brandBundle.GetStringFromName("brandShortName");
+ var quitDialogTitle = quitBundle.formatStringFromName(aQuitType + "DialogTitle",
+ [appName], 1);
+
+ var message;
+ if (aQuitType == "restart")
+ message = quitBundle.formatStringFromName("messageRestart",
+ [appName], 1);
+ else if (windowcount == 1) /* close browser only, or quit application with only 1 browser window */
+ message = quitBundle.formatStringFromName("messageNoWindows",
+ [appName], 1);
+ else /* quit application with 2 or more windows */
+ message = quitBundle.formatStringFromName("message",
+ [appName], 1);
+
+ var flags = Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+ Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_1 +
+ Services.prompt.BUTTON_POS_0_DEFAULT;
+
+ var neverAsk = {value:false};
+ var button0Title, button1Title, button2Title;
+ var neverAskText = quitBundle.GetStringFromName("neverAsk");
+
+ if (aQuitType == "restart") {
+ button0Title = quitBundle.GetStringFromName("restartNowTitle");
+ button1Title = quitBundle.GetStringFromName("restartLaterTitle");
+ } else {
+ flags += Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_2;
+ button0Title = quitBundle.GetStringFromName(
+ (aQuitType == "quit" ? "saveTitle" : "savelastwindowTitle"));
+ button1Title = quitBundle.GetStringFromName("cancelTitle");
+ button2Title = quitBundle.GetStringFromName(aQuitType + "Title"); /* "quitTitle" or "lastwindowTitle" */
+ }
+
+ var mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ var buttonChoice = Services.prompt.confirmEx(mostRecentBrowserWindow, quitDialogTitle, message,
+ flags, button0Title, button1Title, button2Title,
+ neverAskText, neverAsk);
+
+ switch (buttonChoice) {
+ case 2:
+ if (neverAsk.value)
+ Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
+ break;
+ case 1:
+ aCancelQuit.QueryInterface(Ci.nsISupportsPRBool);
+ aCancelQuit.data = true;
+ break;
+ case 0:
+ this._saveSession = true;
+ if (neverAsk.value) {
+ if (aQuitType == "restart")
+ Services.prefs.setBoolPref("browser.warnOnRestart", false);
+ else {
+ // always save state when shutting down
+ Services.prefs.setIntPref("browser.startup.page", 3);
+ }
+ }
+ break;
+ }
+ }
+ },
+
+ /*
+ * _shouldShowRights - Determines if the user should be shown the
+ * about:rights notification. The notification should *not* be shown if
+ * we've already shown the current version, or if the override pref says to
+ * never show it. The notification *should* be shown if it's never been seen
+ * before, if a newer version is available, or if the override pref says to
+ * always show it.
+ */
+ _shouldShowRights: function () {
+ // Look for an unconditional override pref. If set, do what it says.
+ // (true --> never show, false --> always show)
+ try {
+ return !Services.prefs.getBoolPref("browser.rights.override");
+ } catch (e) { }
+ // Ditto, for the legacy EULA pref (tinderbox testing profile sets this).
+ try {
+ return !Services.prefs.getBoolPref("browser.EULA.override");
+ } catch (e) { }
+
+ // Look to see if the user has seen the current version or not.
+ var currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ try {
+ return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
+ } catch (e) { }
+
+ // We haven't shown the notification before, so do so now.
+ return true;
+ },
+
+ _showRightsNotification: function(aNotifyBox) {
+ // Stick the notification onto the selected tab of the active browser window.
+ aNotifyBox.showRightsNotification();
+
+ // Set pref to indicate we've shown the notficiation.
+ var currentVersion = Services.prefs.getIntPref("browser.rights.version");
+ Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
+ },
+
+ /*
+ * _shouldShowUpdateWarning - Determines if the user should be warned about
+ * having updates off and an old build that likely should be updated.
+ */
+ _shouldShowUpdateWarning: function () {
+ // If the Updater is not available we don't show the warning.
+ if (!AppConstants.MOZ_UPDATER) {
+ return false;
+ }
+ // Look for an unconditional override pref. If set, do what it says.
+ // (true --> never show, false --> always show)
+ try {
+ return !Services.prefs.getBoolPref("app.updatecheck.override");
+ } catch (e) { }
+ // If updates are enabled, we don't need to worry.
+ if (Services.prefs.getBoolPref("app.update.enabled"))
+ return false;
+ var maxAge = 90 * 86400; // 90 days
+ var now = Math.round(Date.now() / 1000);
+ // If there was an automated update tried in the interval, don't worry.
+ const PREF_APP_UPDATE_LASTUPDATETIME = "app.update.lastUpdateTime.background-update-timer";
+ var lastUpdateTime = Services.prefs.prefHasUserValue(PREF_APP_UPDATE_LASTUPDATETIME) ?
+ Services.prefs.getIntPref(PREF_APP_UPDATE_LASTUPDATETIME) : 0;
+ if (lastUpdateTime + maxAge > now)
+ return false;
+
+ var buildID = Services.appinfo.appBuildID;
+ // construct build date from ID
+ var buildDate = new Date(buildID.substr(0, 4),
+ buildID.substr(4, 2) - 1,
+ buildID.substr(6, 2));
+ var buildTime = Math.round(buildDate / 1000);
+ // We should warn if the build is older than the max age.
+ return (buildTime + maxAge <= now);
+ },
+
+ // This method gets the shell service and has it check its settings.
+ // This will do nothing on platforms without a shell service.
+ _checkForDefaultClient: function checkForDefaultClient(aWindow)
+ {
+ if (ShellService) try {
+ var appTypes = ShellService.shouldBeDefaultClientFor;
+
+ // Show the default client dialog only if we should check for the default
+ // client and we aren't already the default for the stored app types in
+ // shell.checkDefaultApps.
+ if (appTypes && ShellService.shouldCheckDefaultClient &&
+ !ShellService.isDefaultClient(true, appTypes)) {
+ aWindow.openDialog("chrome://communicator/content/defaultClientDialog.xul",
+ "DefaultClient",
+ "modal,centerscreen,chrome,resizable=no");
+ }
+ } catch (e) {}
+ },
+
+ /**
+ * Initialize Places
+ * - imports the bookmarks html file if bookmarks database is empty, try to
+ * restore bookmarks from a JSON backup if the backend indicates that the
+ * database was corrupt.
+ *
+ * These prefs can be set up by the frontend:
+ *
+ * WARNING: setting these preferences to true will overwite existing bookmarks
+ *
+ * - browser.places.importBookmarksHTML
+ * Set to true will import the bookmarks.html file from the profile folder.
+ * - browser.places.smartBookmarksVersion
+ * Set during HTML import to indicate that Smart Bookmarks were created.
+ * Set to -1 to disable Smart Bookmarks creation.
+ * Set to 0 to restore current Smart Bookmarks.
+ * - browser.bookmarks.restore_default_bookmarks
+ * Set to true by safe-mode dialog to indicate we must restore default
+ * bookmarks.
+ */
+ _initPlaces: function BG__initPlaces(aInitialMigrationPerformed) {
+ // We must instantiate the history service since it will tell us if we
+ // need to import or restore bookmarks due to first-run, corruption or
+ // forced migration (due to a major schema change).
+ // If the database is corrupt or has been newly created we should
+ // import bookmarks.
+ let dbStatus = PlacesUtils.history.databaseStatus;
+
+ // The places.sqlite database is locked. We show a notification box for
+ // it in _onBrowserStartup.
+ if (dbStatus == PlacesUtils.history.DATABASE_STATUS_LOCKED) {
+ this._isPlacesDatabaseLocked = true;
+ Services.console.logStringMessage("places.sqlite is locked");
+ // Note: initPlaces should always happen when the first window is ready,
+ // in any case, better safe than sorry.
+ Services.obs.notifyObservers(null, "places-browser-init-complete");
+ return;
+ }
+
+ let importBookmarks = !aInitialMigrationPerformed &&
+ (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
+ dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT);
+
+ // Check if user or an extension has required to import bookmarks.html.
+ let importBookmarksHTML = false;
+ try {
+ importBookmarksHTML =
+ Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
+ if (importBookmarksHTML)
+ importBookmarks = true;
+ } catch (ex) {}
+
+ // Support legacy bookmarks.html format for apps that depend on that format.
+ // Default if the pref does not exists is 'Do not export'.
+ let autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML", false);
+
+ if (autoExportHTML) {
+ // Sqlite.jsm and Places shutdown happen at profile-before-change, thus,
+ // to be on the safe side, this should run earlier.
+ AsyncShutdown.profileChangeTeardown.addBlocker(
+ "Places: export bookmarks.html",
+ () => BookmarkHTMLUtils.exportToFile(Services.dirsvc.get("BMarks",
+ Ci.nsIFile).path));
+ }
+
+ (async () => {
+ // Check if Safe Mode or the user has required to restore bookmarks from
+ // default profile's bookmarks.html.
+ let restoreDefaultBookmarks = false;
+ try {
+ restoreDefaultBookmarks =
+ Services.prefs.getBoolPref("browser.bookmarks.restore_default_bookmarks");
+ if (restoreDefaultBookmarks) {
+ // Ensure that we already have a bookmarks backup for today.
+ await this._backupBookmarks();
+ importBookmarks = true;
+ }
+ } catch (ex) {}
+
+ // This may be reused later, check for "=== undefined" to see if it has
+ // been populated already.
+ let lastBackupFile;
+
+ // If the user did not require to restore default bookmarks, or import
+ // from bookmarks.html, we will try to restore from JSON.
+ if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
+ // Get latest JSON backup.
+ lastBackupFile = await PlacesBackups.getMostRecentBackup();
+ if (lastBackupFile) {
+ // Restore from JSON backup.
+ await BookmarkJSONUtils.importFromFile(lastBackupFile, true);
+ importBookmarks = false;
+ } else {
+ // We have created a new database but we don't have any backup available.
+ importBookmarks = true;
+ let bookmarksHTMLFile = Services.dirsvc.get("BMarks", Ci.nsIFile);
+ if (bookmarksHTMLFile.exists(bookmarksHTMLFile)) {
+ // If bookmarks.html is available in current profile import it...
+ importBookmarksHTML = true;
+ } else {
+ // ...otherwise we will restore defaults.
+ restoreDefaultBookmarks = true;
+ }
+ }
+ }
+
+ // If bookmarks are not imported, then initialize smart bookmarks. This
+ // happens during a common startup.
+ // Otherwise, if any kind of import runs, smart bookmarks creation should
+ // be delayed till the import operations has finished. Not doing so would
+ // cause them to be overwritten by the newly imported bookmarks.
+ if (!importBookmarks) {
+ try {
+ await this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ } else {
+ // An import operation is about to run.
+ // Don't try to recreate smart bookmarks if autoExportHTML is true or
+ // smart bookmarks are disabled.
+ let smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion", 0);
+ if (!autoExportHTML && smartBookmarksVersion != -1)
+ Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
+
+ let bookmarksURI = null;
+ if (restoreDefaultBookmarks) {
+ // User wants to restore bookmarks.html file from default profile folder
+ bookmarksURI = Services.io.newURI("resource:///defaults/profile/bookmarks.html");
+ } else {
+ let bookmarksFile = Services.dirsvc.get("BMarks", Ci.nsIFile);
+ if (bookmarksFile.exists(bookmarksFile)) {
+ bookmarksURI = Services.io.newFileURI(bookmarksFile);
+ }
+ }
+
+ if (bookmarksURI) {
+ // Import from bookmarks.html file.
+ try {
+ await BookmarkHTMLUtils.importFromURL(bookmarksURI.spec, true);
+ } catch (e) {
+ Cu.reportError("Bookmarks.html file could be corrupt. " + e);
+ }
+ try {
+ // Ensure that smart bookmarks are created once the operation is
+ // complete.
+ await this.ensurePlacesDefaultQueriesInitialized();
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ } else {
+ Cu.reportError(new Error("Unable to find bookmarks.html file."));
+ }
+
+ // Reset preferences, so we won't try to import again at next run
+ if (importBookmarksHTML)
+ Services.prefs.setBoolPref("browser.places.importBookmarksHTML", false);
+ if (restoreDefaultBookmarks)
+ Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks",
+ false);
+ }
+
+ AsyncShutdown.quitApplicationGranted.addBlocker(
+ "Places: export bookmarks at dawn",
+ () => this._backupBookmarks());
+
+ // Initialize bookmark archiving on idle.
+ if (!this._isIdleObserver) {
+ this._idleService.addIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME_SEC);
+ this._isIdleObserver = true;
+ }
+
+ })().catch(ex => {
+ Cu.reportError(ex);
+ }).then(() => {
+ // NB: deliberately after the catch so that we always do this, even if
+ // we threw halfway through initializing in the Task above.
+ Services.obs.notifyObservers(null, "places-browser-init-complete");
+ });
+ },
+
+ /**
+ * If a backup for today doesn't exist, this creates one.
+ */
+ _backupBookmarks: function BG__backupBookmarks() {
+ return (async function() {
+ let lastBackupFile = await PlacesBackups.getMostRecentBackup();
+ // Should backup bookmarks if there are no backups or the maximum
+ // interval between backups elapsed.
+ if (!lastBackupFile ||
+ new Date() - PlacesBackups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS * 86400000) {
+ let maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
+ await PlacesBackups.create(maxBackups);
+ }
+ })();
+ },
+
+ _updatePrefs: function()
+ {
+ // Make sure that the doNotTrack value conforms to the conversion from
+ // three-state to two-state. (This reverts a setting of "please track me"
+ // to the default "don't say anything").
+ try {
+ if (Services.prefs.getIntPref("privacy.donottrackheader.value") != 1) {
+ Services.prefs.clearUserPref("privacy.donottrackheader.enabled");
+ Services.prefs.clearUserPref("privacy.donottrackheader.value");
+ }
+ } catch (ex) {}
+
+ // Migration of document-color preference which changed from boolean to
+ // tri-state; 0=always but not accessibility themes, 1=always, 2=never
+ try {
+ if (!Services.prefs.getBoolPref("browser.display.use_document_colors")) {
+ Services.prefs.setIntPref("browser.display.document_color_use", 2);
+ Services.prefs.clearUserPref("browser.display.use_document_colors");
+ }
+ } catch (ex) {}
+
+ // Try to get dictionary preference and adjust if not valid.
+ var prefName = "spellchecker.dictionary";
+ var prefValue = Services.prefs.getCharPref(prefName);
+
+ // replace underscore with dash if found in language
+ if (/_/.test(prefValue)) {
+ prefValue = prefValue.replace(/_/g, "-");
+ Services.prefs.setCharPref(prefName, prefValue);
+ }
+
+ var spellChecker = Cc["@mozilla.org/spellchecker/engine;1"]
+ .getService(Ci.mozISpellCheckingEngine);
+ var dictList = spellChecker.getDictionaryList();
+ // If the preference contains an invalid dictionary, set it to a valid
+ // dictionary, any dictionary will do.
+ if (dictList.length && !dictList.includes(prefValue))
+ Services.prefs.setCharPref(prefName, dictList[0]);
+ },
+
+ _migrateDownloadPrefs: function()
+ {
+ // Migration of download-manager preferences
+ if (Services.prefs.getPrefType("browser.download.dir") == Services.prefs.PREF_INVALID ||
+ Services.prefs.getPrefType("browser.download.lastDir") != Services.prefs.PREF_INVALID)
+ return; //Do nothing if .dir does not exist, or if it exists and lastDir does not
+
+ try {
+ Services.prefs.setComplexValue("browser.download.lastDir",
+ Ci.nsIFile,
+ Services.prefs.getComplexValue("browser.download.dir",
+ Ci.nsIFile));
+ } catch (ex) {
+ // Ensure that even if we don't end up migrating to a lastDir that we
+ // don't attempt another update. This will throw when QI'ed to
+ // nsIFile, but it does fallback gracefully.
+ Services.prefs.setCharPref("browser.download.lastDir", "");
+ }
+
+ try {
+ Services.prefs.setBoolPref("browser.download.useDownloadDir",
+ Services.prefs.getBoolPref("browser.download.autoDownload"));
+ } catch (ex) {}
+
+ try {
+ Services.prefs.setIntPref("browser.download.manager.behavior",
+ Services.prefs.getIntPref("browser.downloadmanager.behavior"));
+ } catch (ex) {}
+
+ try {
+ Services.prefs.setBoolPref("browser.download.progress.closeWhenDone",
+ !Services.prefs.getBoolPref("browser.download.progressDnldDialog.keepAlive"));
+ } catch (ex) {}
+ },
+
+ /**
+ * Devtools Debugger
+ */
+ get dbgIsEnabled()
+ {
+ return Services.prefs.getBoolPref(DEBUGGER_REMOTE_ENABLED);
+ },
+
+ dbgStart: function()
+ {
+ var port = Services.prefs.getIntPref(DEBUGGER_REMOTE_PORT);
+
+ // Make sure chrome debugging is enabled, no sense in starting otherwise.
+ DebuggerServer.allowChromeProcess = true;
+
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ try {
+ let listener = DebuggerServer.createListener();
+ listener.portOrPath = port;
+
+ // Expose this listener via wifi discovery, if enabled.
+ if (Services.prefs.getBoolPref(DEBUGGER_WIFI_VISIBLE) &&
+ !Services.prefs.getBoolPref(DEBUGGER_FORCE_LOCAL)) {
+ listener.discoverable = true;
+ }
+
+ listener.open();
+ } catch(e) {}
+ },
+
+ dbgStop: function()
+ {
+ if (DebuggerServer.initialized)
+ DebuggerServer.closeAllListeners();
+ },
+
+ dbgRestart: function()
+ {
+ this.dbgStop();
+ this.dbgStart();
+ },
+
+ // ------------------------------
+ // public nsISuiteGlue members
+ // ------------------------------
+
+ showDownloadManager: function(newDownload)
+ {
+ if (!gDownloadManager) {
+ // Use an empty arguments string or the download manager window
+ // will miss the toolbar and other features.
+ var argString = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ argString.data = "";
+ gDownloadManager = Services.ww.openWindow(null, DOWNLOAD_MANAGER_URL,
+ null,
+ "all,dialog=no,non-private",
+ argString);
+ gDownloadManager.addEventListener("load", function() {
+ gDownloadManager.addEventListener("unload", function() {
+ gDownloadManager = null;
+ });
+ // Attach the taskbar progress meter to the download manager window.
+ ChromeUtils.import("resource:///modules/DownloadsTaskbar.jsm", {})
+ .DownloadsTaskbar.attachIndicator(gDownloadManager);
+ });
+ } else if (!newDownload ||
+ Services.prefs.getBoolPref(PREF_FOCUS_WHEN_STARTING)) {
+ gDownloadManager.focus();
+ } else {
+ // This preference may not be set, so defaulting to two.
+ var flashCount = 2;
+ try {
+ flashCount = Services.prefs.getIntPref(PREF_FLASH_COUNT);
+ } catch (e) { }
+ gDownloadManager.getAttentionWithCycleCount(flashCount);
+ }
+ },
+
+ sanitize(aParentWindow) {
+ Sanitizer.showUI(aParentWindow);
+ },
+
+ async ensurePlacesDefaultQueriesInitialized() {
+ // This is the current smart bookmarks version, it must be increased every
+ // time they change.
+ // When adding a new smart bookmark below, its newInVersion property must
+ // be set to the version it has been added in. We will compare its value
+ // to users' smartBookmarksVersion and add new smart bookmarks without
+ // recreating old deleted ones.
+ const SMART_BOOKMARKS_VERSION = 7;
+ const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
+ const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
+
+ // TODO bug 399268: should this be a pref?
+ const MAX_RESULTS = 10;
+
+ // Get current smart bookmarks version. If not set, create them.
+ let smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF, 0);
+
+ // If version is current, or smart bookmarks are disabled, bail out.
+ if (smartBookmarksCurrentVersion == -1 ||
+ smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) {
+ return;
+ }
+
+ try {
+ let menuIndex = 0;
+ let toolbarIndex = 0;
+ let bundle = Services.strings.createBundle("chrome://communicator/locale/places/places.properties");
+ let queryOptions = Ci.nsINavHistoryQueryOptions;
+
+ let smartBookmarks = {
+ MostVisited: {
+ title: bundle.GetStringFromName("mostVisitedTitle"),
+ url: "place:sort=" + queryOptions.SORT_BY_VISITCOUNT_DESCENDING +
+ "&maxResults=" + MAX_RESULTS,
+ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
+ newInVersion: 1
+ },
+ RecentlyBookmarked: {
+ title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
+ url: "place:folder=BOOKMARKS_MENU" + "&folder=UNFILED_BOOKMARKS" +
+ "&folder=TOOLBAR" +
+ "&queryType=" + queryOptions.QUERY_TYPE_BOOKMARKS +
+ "&sort=" + queryOptions.SORT_BY_DATEADDED_DESCENDING +
+ "&maxResults=" + MAX_RESULTS +
+ "&excludeQueries=1",
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ newInVersion: 1
+ },
+ RecentTags: {
+ title: bundle.GetStringFromName("recentTagsTitle"),
+ url: "place:type=" + queryOptions.RESULTS_AS_TAG_QUERY +
+ "&sort=" + queryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
+ "&maxResults=" + MAX_RESULTS,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ newInVersion: 1
+ },
+ };
+
+ // Set current guid, parentGuid and index of existing Smart Bookmarks.
+ // We will use those to create a new version of the bookmark at the same
+ // position.
+ let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
+ for (let itemId of smartBookmarkItemIds) {
+ let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ if (queryId in smartBookmarks) {
+ // Known smart bookmark.
+ let smartBookmark = smartBookmarks[queryId];
+ smartBookmark.guid = await PlacesUtils.promiseItemGuid(itemId);
+
+ if (!smartBookmark.url) {
+ await PlacesUtils.bookmarks.remove(smartBookmark.guid);
+ continue;
+ }
+
+ let bm = await PlacesUtils.bookmarks.fetch(smartBookmark.guid);
+ smartBookmark.parentGuid = bm.parentGuid;
+ smartBookmark.index = bm.index;
+ } else {
+ // We don't remove old Smart Bookmarks because user could still
+ // find them useful, or could have personalized them.
+ // Instead we remove the Smart Bookmark annotation.
+ PlacesUtils.annotations.removeItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
+ }
+ }
+
+ for (let queryId of Object.keys(smartBookmarks)) {
+ let smartBookmark = smartBookmarks[queryId];
+
+ // We update or create only changed or new smart bookmarks.
+ // Also we respect user choices, so we won't try to create a smart
+ // bookmark if it has been removed.
+ if (smartBookmarksCurrentVersion > 0 &&
+ smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
+ !smartBookmark.guid || !smartBookmark.url)
+ continue;
+
+ // Remove old version of the smart bookmark if it exists, since it
+ // will be replaced in place.
+ if (smartBookmark.guid) {
+ await PlacesUtils.bookmarks.remove(smartBookmark.guid);
+ }
+
+ // Create the new smart bookmark and store its updated guid.
+ if (!("index" in smartBookmark)) {
+ if (smartBookmark.parentGuid == PlacesUtils.bookmarks.toolbarGuid)
+ smartBookmark.index = toolbarIndex++;
+ else if (smartBookmark.parentGuid == PlacesUtils.bookmarks.menuGuid)
+ smartBookmark.index = menuIndex++;
+ }
+ smartBookmark = await PlacesUtils.bookmarks.insert(smartBookmark);
+ let itemId = await PlacesUtils.promiseItemId(smartBookmark.guid);
+ PlacesUtils.annotations.setItemAnnotation(itemId,
+ SMART_BOOKMARKS_ANNO,
+ queryId, 0,
+ PlacesUtils.annotations.EXPIRE_NEVER);
+ }
+
+ // If we are creating all Smart Bookmarks from ground up, add a
+ // separator below them in the bookmarks menu.
+ if (smartBookmarksCurrentVersion == 0 &&
+ smartBookmarkItemIds.length == 0) {
+ let bm = await PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: menuIndex });
+ // Don't add a separator if the menu was empty or there is one already.
+ if (bm && bm.type != PlacesUtils.bookmarks.TYPE_SEPARATOR) {
+ await PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
+ parentGuid: PlacesUtils.bookmarks.menuGuid,
+ index: menuIndex });
+ }
+ }
+ } catch (ex) {
+ Cu.reportError(ex);
+ } finally {
+ Services.prefs.setIntPref(SMART_BOOKMARKS_PREF, SMART_BOOKMARKS_VERSION);
+ Services.prefs.savePrefFile(null);
+ }
+ },
+
+ /**
+ * Called as an observer when Sync's "display URI" notification is fired.
+ */
+ _onDisplaySyncURI: function _onDisplaySyncURI(data) {
+ try {
+ var url = data.wrappedJSObject.object.uri;
+ var mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+ if (mostRecentBrowserWindow) {
+ mostRecentBrowserWindow.getBrowser().addTab(url, { focusNewTab: true });
+ mostRecentBrowserWindow.content.focus();
+ } else {
+ var args = Cc["@mozilla.org/supports-string;1"]
+ .createInstance(Ci.nsISupportsString);
+ args.data = url;
+ var chromeURL = Services.prefs.getCharPref("browser.chromeURL");
+ Services.ww.openWindow(null, chromeURL, "_blank", "chrome,all,dialog=no", args);
+ }
+ } catch (e) {
+ Cu.reportError("Error displaying tab received by Sync: " + e);
+ }
+ },
+
+ // for XPCOM
+ classID: Components.ID("{bbbbe845-5a1b-40ee-813c-f84b8faaa07c}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference,
+ Ci.nsISuiteGlue])
+
+}
+
+/**
+ * ContentPermissionIntegration is responsible for showing the user
+ * simple permission prompts when content requests additional
+ * capabilities.
+ *
+ * While there are some built-in permission prompts, createPermissionPrompt
+ * can also be overridden by system add-ons or tests to provide new ones.
+ *
+ * This override ability is provided by Integration.jsm. See
+ * PermissionUI.jsm for an example of how to provide a new prompt
+ * from an add-on.
+ */
+var ContentPermissionIntegration = {
+ /**
+ * Creates a PermissionPrompt for a given permission type and
+ * nsIContentPermissionRequest.
+ *
+ * @param {string} type
+ * The type of the permission request from content. This normally
+ * matches the "type" field of an nsIContentPermissionType, but it
+ * can be something else if the permission does not use the
+ * nsIContentPermissionRequest model. Note that this type might also
+ * be different from the permission key used in the permissions
+ * database.
+ * Example: "geolocation"
+ * @param {nsIContentPermissionRequest} request
+ * The request for a permission from content.
+ * @return {PermissionPrompt} (see PermissionUI.jsm),
+ * or undefined if the type cannot be handled.
+ */
+ createPermissionPrompt(type, request) {
+ switch (type) {
+ case "geolocation": {
+ return new PermissionUI.GeolocationPermissionPrompt(request);
+ }
+ case "desktop-notification": {
+ return new PermissionUI.DesktopNotificationPermissionPrompt(request);
+ }
+ case "persistent-storage": {
+ if (Services.prefs.getBoolPref("browser.storageManager.enabled")) {
+ return new PermissionUI.PersistentStoragePermissionPrompt(request);
+ }
+ }
+ }
+ return undefined;
+ },
+};
+
+function ContentPermissionPrompt() {}
+
+ContentPermissionPrompt.prototype = {
+ classID: Components.ID("{9d4c845d-3f09-402a-b66d-50f291d7d50f}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
+
+ /**
+ * This implementation of nsIContentPermissionPrompt.prompt ensures
+ * that there's only one nsIContentPermissionType in the request,
+ * and that it's of type nsIContentPermissionType. Failing to
+ * satisfy either of these conditions will result in this method
+ * throwing NS_ERRORs. If the combined ContentPermissionIntegration
+ * cannot construct a prompt for this particular request, an
+ * NS_ERROR_FAILURE will be thrown.
+ *
+ * Any time an error is thrown, the nsIContentPermissionRequest is
+ * cancelled automatically.
+ *
+ * @param {nsIContentPermissionRequest} request
+ * The request that we're to show a prompt for.
+ */
+ prompt(request) {
+ try {
+ // Only allow exactly one permission request here.
+ let types = request.types.QueryInterface(Ci.nsIArray);
+ if (types.length != 1) {
+ throw Components.Exception(
+ "Expected an nsIContentPermissionRequest with only 1 type.",
+ Cr.NS_ERROR_UNEXPECTED);
+ }
+
+ let type = types.queryElementAt(0, Ci.nsIContentPermissionType).type;
+ let combinedIntegration =
+ Integration.contentPermission.getCombined(ContentPermissionIntegration);
+
+ let permissionPrompt =
+ combinedIntegration.createPermissionPrompt(type, request);
+ if (!permissionPrompt) {
+ throw Components.Exception(
+ "Failed to handle permission of type ${type}",
+ Cr.NS_ERROR_FAILURE);
+ }
+
+ permissionPrompt.prompt();
+ } catch (ex) {
+ Cu.reportError(ex);
+ request.cancel();
+ throw ex;
+ }
+ },
+};
+
+//module initialization
+var NSGetFactory = XPCOMUtils.generateNSGetFactory([SuiteGlue, ContentPermissionPrompt]);