diff options
Diffstat (limited to 'comm/suite/components/nsSuiteGlue.js')
-rw-r--r-- | comm/suite/components/nsSuiteGlue.js | 1676 |
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]); |