diff -r 000000000000 -r 6474c204b198 browser/components/nsBrowserGlue.js --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/browser/components/nsBrowserGlue.js Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,2198 @@ +# -*- indent-tabs-mode: nil -*- +# 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 Ci = Components.interfaces; +const Cc = Components.classes; +const Cr = Components.results; +const Cu = Components.utils; + +const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AboutHome", + "resource:///modules/AboutHome.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AddonManager", + "resource://gre/modules/AddonManager.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "ContentClick", + "resource:///modules/ContentClick.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "DirectoryLinksProvider", + "resource://gre/modules/DirectoryLinksProvider.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", + "resource://gre/modules/NetUtil.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", + "resource://gre/modules/FileUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", + "resource://gre/modules/PlacesUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils", + "resource://gre/modules/BookmarkHTMLUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils", + "resource://gre/modules/BookmarkJSONUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "WebappManager", + "resource:///modules/WebappManager.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PageThumbs", + "resource://gre/modules/PageThumbs.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "NewTabUtils", + "resource://gre/modules/NewTabUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "BrowserNewTabPreloader", + "resource:///modules/BrowserNewTabPreloader.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "CustomizationTabPreloader", + "resource:///modules/CustomizationTabPreloader.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PdfJs", + "resource://pdf.js/PdfJs.jsm"); + +#ifdef NIGHTLY_BUILD +XPCOMUtils.defineLazyModuleGetter(this, "ShumwayUtils", + "resource://shumway/ShumwayUtils.jsm"); +#endif + +XPCOMUtils.defineLazyModuleGetter(this, "webrtcUI", + "resource:///modules/webrtcUI.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", + "resource://gre/modules/PrivateBrowsingUtils.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow", + "resource:///modules/RecentWindow.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "Task", + "resource://gre/modules/Task.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups", + "resource://gre/modules/PlacesBackups.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "OS", + "resource://gre/modules/osfile.jsm"); + + XPCOMUtils.defineLazyModuleGetter(this, "RemotePrompt", + "resource:///modules/RemotePrompt.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "SessionStore", + "resource:///modules/sessionstore/SessionStore.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry", + "resource:///modules/BrowserUITelemetry.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "AsyncShutdown", + "resource://gre/modules/AsyncShutdown.jsm"); + +#ifdef NIGHTLY_BUILD +XPCOMUtils.defineLazyModuleGetter(this, "SignInToWebsiteUX", + "resource:///modules/SignInToWebsite.jsm"); +#endif + +XPCOMUtils.defineLazyModuleGetter(this, "ContentSearch", + "resource:///modules/ContentSearch.jsm"); + +const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser"; +const PREF_PLUGINS_UPDATEURL = "plugins.update.url"; + +// Seconds of idle before trying to create a bookmarks backup. +const BOOKMARKS_BACKUP_IDLE_TIME_SEC = 8 * 60; +// Minimum interval between backups. We try to not create more than one backup +// per interval. +const BOOKMARKS_BACKUP_MIN_INTERVAL_DAYS = 1; +// Maximum interval between backups. If the last backup is older than these +// days we will try to create a new one more aggressively. +const BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS = 3; + +// Factory object +const BrowserGlueServiceFactory = { + _instance: null, + createInstance: function BGSF_createInstance(outer, iid) { + if (outer != null) + throw Components.results.NS_ERROR_NO_AGGREGATION; + return this._instance == null ? + this._instance = new BrowserGlue() : this._instance; + } +}; + +// Constructor + +function BrowserGlue() { + XPCOMUtils.defineLazyServiceGetter(this, "_idleService", + "@mozilla.org/widget/idleservice;1", + "nsIIdleService"); + + XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() { + Cu.import("resource:///modules/distribution.js"); + return new DistributionCustomizer(); + }); + + XPCOMUtils.defineLazyGetter(this, "_sanitizer", + function() { + let sanitizerScope = {}; + Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope); + return sanitizerScope.Sanitizer; + }); + + this._init(); +} + +#ifndef XP_MACOSX +# OS X has the concept of zero-window sessions and therefore ignores the +# browser-lastwindow-close-* topics. +#define OBSERVE_LASTWINDOW_CLOSE_TOPICS 1 +#endif + +BrowserGlue.prototype = { + _saveSession: false, + _isPlacesInitObserver: false, + _isPlacesLockedObserver: false, + _isPlacesShutdownObserver: false, + _isPlacesDatabaseLocked: false, + _migrationImportsDefaultBookmarks: false, + + _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) { + if (!this._saveSession && !aForce) + return; + + Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true); + + // This method can be called via [NSApplication terminate:] on Mac, which + // ends up causing prefs not to be flushed to disk, so we need to do that + // explicitly here. See bug 497652. + Services.prefs.savePrefFile(null); + }, + +#ifdef MOZ_SERVICES_SYNC + _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; + + Cu.import("resource://services-sync/main.js"); + Weave.Service.scheduler.delayedAutoConnect(delay); + }, +#endif + + // nsIObserver implementation + observe: function BG_observe(subject, topic, data) { + switch (topic) { + case "prefservice:after-app-defaults": + this._onAppDefaults(); + break; + case "final-ui-startup": + this._finalUIStartup(); + break; + case "browser-delayed-startup-finished": + this._onFirstWindowLoaded(subject); + Services.obs.removeObserver(this, "browser-delayed-startup-finished"); + break; + case "sessionstore-windows-restored": + this._onWindowsRestored(); + 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; +#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS + 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": + this._setPrefToSaveSession(); + break; +#endif +#ifdef MOZ_SERVICES_SYNC + case "weave:service:ready": + this._setSyncAutoconnectDelay(); + break; + case "weave:engine:clients:display-uri": + this._onDisplaySyncURI(subject); + break; +#endif + case "session-save": + this._setPrefToSaveSession(true); + 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"); + this._isPlacesInitObserver = false; + // no longer needed, since history was initialized completely. + Services.obs.removeObserver(this, "places-database-locked"); + this._isPlacesLockedObserver = false; + break; + case "places-database-locked": + this._isPlacesDatabaseLocked = true; + // Stop observing, so further attempts to load history service + // will not show the prompt. + Services.obs.removeObserver(this, "places-database-locked"); + this._isPlacesLockedObserver = false; + break; + case "places-shutdown": + if (this._isPlacesShutdownObserver) { + Services.obs.removeObserver(this, "places-shutdown"); + this._isPlacesShutdownObserver = false; + } + // places-shutdown is fired when the profile is about to disappear. + this._onPlacesShutdown(); + break; + case "idle": + this._backupBookmarks(); + break; + case "distribution-customization-complete": + Services.obs.removeObserver(this, "distribution-customization-complete"); + // Customization has finished, we don't need the customizer anymore. + delete this._distributionCustomizer; + break; + case "browser-glue-test": // used by tests + if (data == "post-update-notification") { + if (Services.prefs.prefHasUserValue("app.update.postupdate")) + this._showUpdateNotification(); + } + else if (data == "force-ui-migration") { + this._migrateUI(); + } + else if (data == "force-distribution-customization") { + this._distributionCustomizer.applyPrefDefaults(); + this._distributionCustomizer.applyCustomizations(); + // To apply distribution bookmarks use "places-init-complete". + } + else if (data == "force-places-init") { + this._initPlaces(false); + } + break; + case "initial-migration-will-import-default-bookmarks": + this._migrationImportsDefaultBookmarks = true; + break; + case "initial-migration-did-import-default-bookmarks": + this._initPlaces(true); + break; + case "handle-xul-text-link": + let linkHandled = subject.QueryInterface(Ci.nsISupportsPRBool); + if (!linkHandled.data) { + let win = this.getMostRecentBrowserWindow(); + if (win) { + win.openUILinkIn(data, "tab"); + linkHandled.data = true; + } + } + 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; +#ifdef MOZ_SERVICES_HEALTHREPORT + case "keyword-search": + // This is very similar to code in + // browser.js:BrowserSearch.recordSearchInHealthReport(). The code could + // be consolidated if there is will. We need the observer in + // nsBrowserGlue to prevent double counting. + let reporter = Cc["@mozilla.org/datareporting/service;1"] + .getService() + .wrappedJSObject + .healthReporter; + + if (!reporter) { + return; + } + + reporter.onInit().then(function record() { + try { + let engine = subject.QueryInterface(Ci.nsISearchEngine); + reporter.getProvider("org.mozilla.searches").recordSearch(engine, "urlbar"); + } catch (ex) { + Cu.reportError(ex); + } + }); + break; +#endif + case "browser-search-engine-modified": + if (data != "engine-default" && data != "engine-current") { + break; + } + // Enforce that the search service's defaultEngine is always equal to + // its currentEngine. The search service will notify us any time either + // of them are changed (either by directly setting the relevant prefs, + // i.e. if add-ons try to change this directly, or if the + // nsIBrowserSearchService setters are called). + // No need to initialize the search service, since it's guaranteed to be + // initialized already when this notification fires. + let ss = Services.search; + if (ss.currentEngine.name == ss.defaultEngine.name) + return; + if (data == "engine-current") + ss.defaultEngine = ss.currentEngine; + else + ss.currentEngine = ss.defaultEngine; + break; + case "browser-search-service": + if (data != "init-complete") + return; + Services.obs.removeObserver(this, "browser-search-service"); + this._syncSearchEngines(); + break; + } + }, + + _syncSearchEngines: function () { + // Only do this if the search service is already initialized. This function + // gets called in finalUIStartup and from a browser-search-service observer, + // to catch both cases (search service initialization occurring before and + // after final-ui-startup) + if (Services.search.isInitialized) { + Services.search.defaultEngine = Services.search.currentEngine; + } + }, + + // initialization (called on application startup) + _init: function BG__init() { + let os = Services.obs; + os.addObserver(this, "prefservice:after-app-defaults", false); + os.addObserver(this, "final-ui-startup", false); + os.addObserver(this, "browser-delayed-startup-finished", false); + os.addObserver(this, "sessionstore-windows-restored", false); + os.addObserver(this, "browser:purge-session-history", false); + os.addObserver(this, "quit-application-requested", false); + os.addObserver(this, "quit-application-granted", false); +#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS + os.addObserver(this, "browser-lastwindow-close-requested", false); + os.addObserver(this, "browser-lastwindow-close-granted", false); +#endif +#ifdef MOZ_SERVICES_SYNC + os.addObserver(this, "weave:service:ready", false); + os.addObserver(this, "weave:engine:clients:display-uri", false); +#endif + os.addObserver(this, "session-save", false); + os.addObserver(this, "places-init-complete", false); + this._isPlacesInitObserver = true; + os.addObserver(this, "places-database-locked", false); + this._isPlacesLockedObserver = true; + os.addObserver(this, "distribution-customization-complete", false); + os.addObserver(this, "places-shutdown", false); + this._isPlacesShutdownObserver = true; + os.addObserver(this, "handle-xul-text-link", false); + os.addObserver(this, "profile-before-change", false); +#ifdef MOZ_SERVICES_HEALTHREPORT + os.addObserver(this, "keyword-search", false); +#endif + os.addObserver(this, "browser-search-engine-modified", false); + os.addObserver(this, "browser-search-service", false); + }, + + // cleanup (called on application shutdown) + _dispose: function BG__dispose() { + let os = Services.obs; + os.removeObserver(this, "prefservice:after-app-defaults"); + os.removeObserver(this, "final-ui-startup"); + os.removeObserver(this, "sessionstore-windows-restored"); + os.removeObserver(this, "browser:purge-session-history"); + os.removeObserver(this, "quit-application-requested"); + os.removeObserver(this, "quit-application-granted"); +#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS + os.removeObserver(this, "browser-lastwindow-close-requested"); + os.removeObserver(this, "browser-lastwindow-close-granted"); +#endif +#ifdef MOZ_SERVICES_SYNC + os.removeObserver(this, "weave:service:ready"); + os.removeObserver(this, "weave:engine:clients:display-uri"); +#endif + os.removeObserver(this, "session-save"); + if (this._bookmarksBackupIdleTime) { + this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime); + delete this._bookmarksBackupIdleTime; + } + if (this._isPlacesInitObserver) + os.removeObserver(this, "places-init-complete"); + if (this._isPlacesLockedObserver) + os.removeObserver(this, "places-database-locked"); + if (this._isPlacesShutdownObserver) + os.removeObserver(this, "places-shutdown"); + os.removeObserver(this, "handle-xul-text-link"); + os.removeObserver(this, "profile-before-change"); +#ifdef MOZ_SERVICES_HEALTHREPORT + os.removeObserver(this, "keyword-search"); +#endif + os.removeObserver(this, "browser-search-engine-modified"); + try { + os.removeObserver(this, "browser-search-service"); + // may have already been removed by the observer + } catch (ex) {} + }, + + _onAppDefaults: function BG__onAppDefaults() { + // apply distribution customizations (prefs) + // other customizations are applied in _finalUIStartup() + this._distributionCustomizer.applyPrefDefaults(); + }, + + // runs on startup, before the first command line handler is invoked + // (i.e. before the first window is opened) + _finalUIStartup: function BG__finalUIStartup() { + this._sanitizer.onStartup(); + // check if we're in safe mode + if (Services.appinfo.inSafeMode) { + Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul", + "_blank", "chrome,centerscreen,modal,resizable=no", null); + } + + // apply distribution customizations + // prefs are applied in _onAppDefaults() + this._distributionCustomizer.applyCustomizations(); + + // handle any UI migration + this._migrateUI(); + + this._syncSearchEngines(); + + WebappManager.init(); + PageThumbs.init(); + NewTabUtils.init(); + DirectoryLinksProvider.init(); + NewTabUtils.links.addProvider(DirectoryLinksProvider); + BrowserNewTabPreloader.init(); +#ifdef NIGHTLY_BUILD + if (Services.prefs.getBoolPref("dom.identity.enabled")) { + SignInToWebsiteUX.init(); + } +#endif + PdfJs.init(); +#ifdef NIGHTLY_BUILD + ShumwayUtils.init(); +#endif + webrtcUI.init(); + AboutHome.init(); + SessionStore.init(); + BrowserUITelemetry.init(); + ContentSearch.init(); + + if (Services.appinfo.browserTabsRemote) { + ContentClick.init(); + RemotePrompt.init(); + } + + Services.obs.notifyObservers(null, "browser-ui-startup-complete", ""); + }, + + _checkForOldBuildUpdates: function () { + // check for update if our build is old + if (Services.prefs.getBoolPref("app.update.enabled") && + Services.prefs.getBoolPref("app.update.checkInstallTime")) { + + let buildID = Services.appinfo.appBuildID; + let today = new Date().getTime(); + let buildDate = new Date(buildID.slice(0,4), // year + buildID.slice(4,6) - 1, // months are zero-based. + buildID.slice(6,8), // day + buildID.slice(8,10), // hour + buildID.slice(10,12), // min + buildID.slice(12,14)) // ms + .getTime(); + + const millisecondsIn24Hours = 86400000; + let acceptableAge = Services.prefs.getIntPref("app.update.checkInstallTime.days") * millisecondsIn24Hours; + + if (buildDate + acceptableAge < today) { + Cc["@mozilla.org/updates/update-service;1"].getService(Ci.nsIApplicationUpdateService).checkForBackgroundUpdates(); + } + } + }, + + _trackSlowStartup: function () { + if (Services.startup.interrupted || + Services.prefs.getBoolPref("browser.slowStartup.notificationDisabled")) + return; + + let currentTime = Date.now() - Services.startup.getStartupInfo().process; + let averageTime = 0; + let samples = 0; + try { + averageTime = Services.prefs.getIntPref("browser.slowStartup.averageTime"); + samples = Services.prefs.getIntPref("browser.slowStartup.samples"); + } catch (e) { } + + let totalTime = (averageTime * samples) + currentTime; + samples++; + averageTime = totalTime / samples; + + if (samples >= Services.prefs.getIntPref("browser.slowStartup.maxSamples")) { + if (averageTime > Services.prefs.getIntPref("browser.slowStartup.timeThreshold")) + this._showSlowStartupNotification(); + averageTime = 0; + samples = 0; + } + + Services.prefs.setIntPref("browser.slowStartup.averageTime", averageTime); + Services.prefs.setIntPref("browser.slowStartup.samples", samples); + }, + + _showSlowStartupNotification: function () { + let win = this.getMostRecentBrowserWindow(); + if (!win) + return; + + let productName = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandFullName"); + let message = win.gNavigatorBundle.getFormattedString("slowStartup.message", [productName]); + + let buttons = [ + { + label: win.gNavigatorBundle.getString("slowStartup.helpButton.label"), + accessKey: win.gNavigatorBundle.getString("slowStartup.helpButton.accesskey"), + callback: function () { + win.openUILinkIn("https://support.mozilla.org/kb/reset-firefox-easily-fix-most-problems", "tab"); + } + }, + { + label: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.label"), + accessKey: win.gNavigatorBundle.getString("slowStartup.disableNotificationButton.accesskey"), + callback: function () { + Services.prefs.setBoolPref("browser.slowStartup.notificationDisabled", true); + } + } + ]; + + let nb = win.document.getElementById("global-notificationbox"); + nb.appendNotification(message, "slow-startup", + "chrome://browser/skin/slowStartup-16.png", + nb.PRIORITY_INFO_LOW, buttons); + }, + + /** + * Show a notification bar offering a reset if the profile has been unused for some time. + */ + _resetUnusedProfileNotification: function () { + let win = this.getMostRecentBrowserWindow(); + if (!win) + return; + + Cu.import("resource://gre/modules/ResetProfile.jsm"); + if (!ResetProfile.resetSupported()) + return; + + let productName = Services.strings + .createBundle("chrome://branding/locale/brand.properties") + .GetStringFromName("brandShortName"); + let resetBundle = Services.strings + .createBundle("chrome://global/locale/resetProfile.properties"); + + let message = resetBundle.formatStringFromName("resetUnusedProfile.message", [productName], 1); + let buttons = [ + { + label: resetBundle.formatStringFromName("resetProfile.resetButton.label", [productName], 1), + accessKey: resetBundle.GetStringFromName("resetProfile.resetButton.accesskey"), + callback: function () { + ResetProfile.openConfirmationDialog(win); + } + }, + ]; + + let nb = win.document.getElementById("global-notificationbox"); + nb.appendNotification(message, "reset-unused-profile", + "chrome://global/skin/icons/question-16.png", + nb.PRIORITY_INFO_LOW, buttons); + }, + + // the first browser window has finished initializing + _onFirstWindowLoaded: function BG__onFirstWindowLoaded(aWindow) { +#ifdef XP_WIN + // For windows seven, initialize the jump list module. + const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; + if (WINTASKBAR_CONTRACTID in Cc && + Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) { + let temp = {}; + Cu.import("resource:///modules/WindowsJumpLists.jsm", temp); + temp.WinTaskbarJumpList.startup(); + } +#endif + + this._trackSlowStartup(); + + // Offer to reset a user's profile if it hasn't been used for 60 days. + const OFFER_PROFILE_RESET_INTERVAL_MS = 60 * 24 * 60 * 60 * 1000; + let lastUse = Services.appinfo.replacedLockTime; + let disableResetPrompt = false; + try { + disableResetPrompt = Services.prefs.getBoolPref("browser.disableResetPrompt"); + } catch(e) {} + if (!disableResetPrompt && lastUse && + Date.now() - lastUse >= OFFER_PROFILE_RESET_INTERVAL_MS) { + this._resetUnusedProfileNotification(); + } + + this._checkForOldBuildUpdates(); + }, + + /** + * Application shutdown handler. + */ + _onQuitApplicationGranted: function () { + // This pref must be set here because SessionStore will use its value + // on quit-application. + this._setPrefToSaveSession(); + + // Call trackStartupCrashEnd here in case the delayed call on startup hasn't + // yet occurred (see trackStartupCrashEnd caller in browser.js). + try { + let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"] + .getService(Ci.nsIAppStartup); + appStartup.trackStartupCrashEnd(); + } catch (e) { + Cu.reportError("Could not end startup crash tracking in quit-application-granted: " + e); + } + + BrowserNewTabPreloader.uninit(); + CustomizationTabPreloader.uninit(); + WebappManager.uninit(); +#ifdef NIGHTLY_BUILD + if (Services.prefs.getBoolPref("dom.identity.enabled")) { + SignInToWebsiteUX.uninit(); + } +#endif + webrtcUI.uninit(); + }, + + // All initial windows have opened. + _onWindowsRestored: function BG__onWindowsRestored() { + // Show update notification, if needed. + if (Services.prefs.prefHasUserValue("app.update.postupdate")) + this._showUpdateNotification(); + + // Load the "more info" page for a locked places.sqlite + // This property is set earlier by places-database-locked topic. + if (this._isPlacesDatabaseLocked) { + this._showPlacesLockedNotificationBox(); + } + + // If there are plugins installed that are outdated, and the user hasn't + // been warned about them yet, open the plugins update page. + if (Services.prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER)) + this._showPluginUpdatePage(); + + // For any add-ons that were installed disabled and can be enabled offer + // them to the user. + let changedIDs = AddonManager.getStartupChanges(AddonManager.STARTUP_CHANGE_INSTALLED); + if (changedIDs.length > 0) { + let win = this.getMostRecentBrowserWindow(); + 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; + + win.openUILinkIn("about:newaddon?id=" + aAddon.id, "tab"); + }) + }); + } + + // Perform default browser checking. + var shell; + try { + shell = Components.classes["@mozilla.org/browser/shell-service;1"] + .getService(Components.interfaces.nsIShellService); + } catch (e) { } + if (shell) { +#ifdef DEBUG + let shouldCheck = false; +#else + let shouldCheck = shell.shouldCheckDefaultBrowser; +#endif + let willRecoverSession = false; + try { + let ss = Cc["@mozilla.org/browser/sessionstartup;1"]. + getService(Ci.nsISessionStartup); + willRecoverSession = + (ss.sessionType == Ci.nsISessionStartup.RECOVER_SESSION); + } + catch (ex) { /* never mind; suppose SessionStore is broken */ } + + let isDefault = shell.isDefaultBrowser(true, false); // startup check, check all assoc + try { + // Report default browser status on startup to telemetry + // so we can track whether we are the default. + Services.telemetry.getHistogramById("BROWSER_IS_USER_DEFAULT") + .add(isDefault); + } + catch (ex) { /* Don't break the default prompt if telemetry is broken. */ } + + if (shouldCheck && !isDefault && !willRecoverSession) { + Services.tm.mainThread.dispatch(function() { + var win = this.getMostRecentBrowserWindow(); + var brandBundle = win.document.getElementById("bundle_brand"); + var shellBundle = win.document.getElementById("bundle_shell"); + + var brandShortName = brandBundle.getString("brandShortName"); + var promptTitle = shellBundle.getString("setDefaultBrowserTitle"); + var promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage", + [brandShortName]); + var checkboxLabel = shellBundle.getFormattedString("setDefaultBrowserDontAsk", + [brandShortName]); + var checkEveryTime = { value: shouldCheck }; + var ps = Services.prompt; + var rv = ps.confirmEx(win, promptTitle, promptMessage, + ps.STD_YES_NO_BUTTONS, + null, null, null, checkboxLabel, checkEveryTime); + if (rv == 0) { + var claimAllTypes = true; +#ifdef XP_WIN + try { + // In Windows 8, the UI for selecting default protocol is much + // nicer than the UI for setting file type associations. So we + // only show the protocol association screen on Windows 8. + // Windows 8 is version 6.2. + let version = Cc["@mozilla.org/system-info;1"] + .getService(Ci.nsIPropertyBag2) + .getProperty("version"); + claimAllTypes = (parseFloat(version) < 6.2); + } catch (ex) { } +#endif + shell.setDefaultBrowser(claimAllTypes, false); + } + shell.shouldCheckDefaultBrowser = checkEveryTime.value; + }.bind(this), Ci.nsIThread.DISPATCH_NORMAL); + } + } + }, + + _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) { + // If user has already dismissed quit request, then do nothing + if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data) + return; + + // There are several cases where we won't show a dialog here: + // 1. There is only 1 tab open in 1 window + // 2. The session will be restored at startup, indicated by + // browser.startup.page == 3 or browser.sessionstore.resume_session_once == true + // 3. browser.warnOnQuit == false + // 4. The browser is currently in Private Browsing mode + // 5. The browser will be restarted. + // + // Otherwise these are the conditions and the associated dialogs that will be shown: + // 1. aQuitType == "lastwindow" or "quit" and browser.showQuitWarning == true + // - The quit dialog will be shown + // 2. aQuitType == "lastwindow" && browser.tabs.warnOnClose == true + // - The "closing multiple tabs" dialog will be shown + // + // aQuitType == "lastwindow" is overloaded. "lastwindow" is used to indicate + // "the last window is closing but we're not quitting (a non-browser window is open)" + // and also "we're quitting by closing the last window". + + if (aQuitType == "restart") + return; + + var windowcount = 0; + var pagecount = 0; + var browserEnum = Services.wm.getEnumerator("navigator:browser"); + let allWindowsPrivate = true; + while (browserEnum.hasMoreElements()) { + // XXXbz should we skip closed windows here? + windowcount++; + + var browser = browserEnum.getNext(); + if (!PrivateBrowsingUtils.isWindowPrivate(browser)) + allWindowsPrivate = false; + var tabbrowser = browser.document.getElementById("content"); + if (tabbrowser) + pagecount += tabbrowser.browsers.length - tabbrowser._numPinnedTabs; + } + + this._saveSession = false; + if (pagecount < 2) + return; + + if (!aQuitType) + aQuitType = "quit"; + + var mostRecentBrowserWindow; + + // browser.warnOnQuit is a hidden global boolean to override all quit prompts + // browser.showQuitWarning specifically covers quitting + // browser.tabs.warnOnClose is the global "warn when closing multiple tabs" pref + + var sessionWillBeRestored = Services.prefs.getIntPref("browser.startup.page") == 3 || + Services.prefs.getBoolPref("browser.sessionstore.resume_session_once"); + if (sessionWillBeRestored || !Services.prefs.getBoolPref("browser.warnOnQuit")) + return; + + // On last window close or quit && showQuitWarning, we want to show the + // quit warning. + if (!Services.prefs.getBoolPref("browser.showQuitWarning")) { + if (aQuitType == "lastwindow") { + // If aQuitType is "lastwindow" and we aren't showing the quit warning, + // we should show the window closing warning instead. warnAboutClosing + // tabs checks browser.tabs.warnOnClose and returns if it's ok to close + // the window. It doesn't actually close the window. + mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + let allTabs = mostRecentBrowserWindow.gBrowser.closingTabsEnum.ALL; + aCancelQuit.data = !mostRecentBrowserWindow.gBrowser.warnAboutClosingTabs(allTabs) + } + return; + } + + // Never show a prompt inside private browsing mode + if (allWindowsPrivate) + return; + + var quitBundle = Services.strings.createBundle("chrome://browser/locale/quitDialog.properties"); + var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + + var appName = brandBundle.GetStringFromName("brandShortName"); + var quitTitleString = "quitDialogTitle"; + var quitDialogTitle = quitBundle.formatStringFromName(quitTitleString, [appName], 1); + + var message; + if (windowcount == 1) + message = quitBundle.formatStringFromName("messageNoWindows", + [appName], 1); + else + message = quitBundle.formatStringFromName("message", + [appName], 1); + + var promptService = Services.prompt; + + var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0 + + promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 + + promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2 + + promptService.BUTTON_POS_0_DEFAULT; + + var neverAsk = {value:false}; + var button0Title = quitBundle.GetStringFromName("saveTitle"); + var button1Title = quitBundle.GetStringFromName("cancelTitle"); + var button2Title = quitBundle.GetStringFromName("quitTitle"); + var neverAskText = quitBundle.GetStringFromName("neverAsk2"); + + // This wouldn't have been set above since we shouldn't be here for + // aQuitType == "lastwindow" + mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser"); + + var buttonChoice = + promptService.confirmEx(mostRecentBrowserWindow, quitDialogTitle, message, + flags, button0Title, button1Title, button2Title, + neverAskText, neverAsk); + + switch (buttonChoice) { + case 2: // Quit + if (neverAsk.value) + Services.prefs.setBoolPref("browser.showQuitWarning", false); + break; + case 1: // Cancel + aCancelQuit.QueryInterface(Ci.nsISupportsPRBool); + aCancelQuit.data = true; + break; + case 0: // Save & Quit + this._saveSession = true; + if (neverAsk.value) { + // always save state when shutting down + Services.prefs.setIntPref("browser.startup.page", 3); + } + break; + } + }, + + _showUpdateNotification: function BG__showUpdateNotification() { + Services.prefs.clearUserPref("app.update.postupdate"); + + var um = Cc["@mozilla.org/updates/update-manager;1"]. + getService(Ci.nsIUpdateManager); + try { + // If the updates.xml file is deleted then getUpdateAt will throw. + var update = um.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag); + } + catch (e) { + // This should never happen. + Cu.reportError("Unable to find update: " + e); + return; + } + + var actions = update.getProperty("actions"); + if (!actions || actions.indexOf("silent") != -1) + return; + + var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. + getService(Ci.nsIURLFormatter); + var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + var appName = brandBundle.GetStringFromName("brandShortName"); + + function getNotifyString(aPropData) { + var propValue = update.getProperty(aPropData.propName); + if (!propValue) { + if (aPropData.prefName) + propValue = formatter.formatURLPref(aPropData.prefName); + else if (aPropData.stringParams) + propValue = browserBundle.formatStringFromName(aPropData.stringName, + aPropData.stringParams, + aPropData.stringParams.length); + else + propValue = browserBundle.GetStringFromName(aPropData.stringName); + } + return propValue; + } + + if (actions.indexOf("showNotification") != -1) { + let text = getNotifyString({propName: "notificationText", + stringName: "puNotifyText", + stringParams: [appName]}); + let url = getNotifyString({propName: "notificationURL", + prefName: "startup.homepage_override_url"}); + let label = getNotifyString({propName: "notificationButtonLabel", + stringName: "pu.notifyButton.label"}); + let key = getNotifyString({propName: "notificationButtonAccessKey", + stringName: "pu.notifyButton.accesskey"}); + + let win = this.getMostRecentBrowserWindow(); + let notifyBox = win.gBrowser.getNotificationBox(); + + let buttons = [ + { + label: label, + accessKey: key, + popup: null, + callback: function(aNotificationBar, aButton) { + win.openUILinkIn(url, "tab"); + } + } + ]; + + let notification = notifyBox.appendNotification(text, "post-update-notification", + null, notifyBox.PRIORITY_INFO_LOW, + buttons); + notification.persistence = -1; // Until user closes it + } + + if (actions.indexOf("showAlert") == -1) + return; + + let notifier; + try { + notifier = Cc["@mozilla.org/alerts-service;1"]. + getService(Ci.nsIAlertsService); + } + catch (e) { + // nsIAlertsService is not available for this platform + return; + } + + let title = getNotifyString({propName: "alertTitle", + stringName: "puAlertTitle", + stringParams: [appName]}); + let text = getNotifyString({propName: "alertText", + stringName: "puAlertText", + stringParams: [appName]}); + let url = getNotifyString({propName: "alertURL", + prefName: "startup.homepage_override_url"}); + + var self = this; + function clickCallback(subject, topic, data) { + // This callback will be called twice but only once with this topic + if (topic != "alertclickcallback") + return; + let win = self.getMostRecentBrowserWindow(); + win.openUILinkIn(data, "tab"); + } + + try { + // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot + // be displayed per the idl. + notifier.showAlertNotification(null, title, text, + true, url, clickCallback); + } + catch (e) { + } + }, + + _showPluginUpdatePage: function BG__showPluginUpdatePage() { + Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false); + + var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. + getService(Ci.nsIURLFormatter); + var updateUrl = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL); + + var win = this.getMostRecentBrowserWindow(); + win.openUILinkIn(updateUrl, "tab"); + }, + + /** + * 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; + 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. + let autoExportHTML = false; + try { + autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML"); + } catch (ex) {} // Do not export. + 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(BookmarkHTMLUtils.defaultPath)); + } + + Task.spawn(function() { + // 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. + yield 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 = yield PlacesBackups.getMostRecentBackup("json"); + if (lastBackupFile) { + // restore from JSON backup + yield BookmarkJSONUtils.importFromFile(lastBackupFile, true); + importBookmarks = false; + } + else { + // We have created a new database but we don't have any backup available + importBookmarks = true; + if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) { + // 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) { + // Now apply distribution customized bookmarks. + // This should always run after Places initialization. + this._distributionCustomizer.applyBookmarks(); + this.ensurePlacesDefaultQueriesInitialized(); + } + 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 = 0; + try { + smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion"); + } catch(ex) {} + if (!autoExportHTML && smartBookmarksVersion != -1) + Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0); + + let bookmarksUrl = null; + if (restoreDefaultBookmarks) { + // User wants to restore bookmarks.html file from default profile folder + bookmarksUrl = "resource:///defaults/profile/bookmarks.html"; + } + else if (yield OS.File.exists(BookmarkHTMLUtils.defaultPath)) { + bookmarksUrl = OS.Path.toFileURI(BookmarkHTMLUtils.defaultPath); + } + + if (bookmarksUrl) { + // Import from bookmarks.html file. + try { + BookmarkHTMLUtils.importFromURL(bookmarksUrl, true).then(null, + function onFailure() { + Cu.reportError("Bookmarks.html file could be corrupt."); + } + ).then( + function onComplete() { + // Now apply distribution customized bookmarks. + // This should always run after Places initialization. + this._distributionCustomizer.applyBookmarks(); + // Ensure that smart bookmarks are created once the operation is + // complete. + this.ensurePlacesDefaultQueriesInitialized(); + }.bind(this) + ); + } catch (err) { + Cu.reportError("Bookmarks.html file could be corrupt. " + err); + } + } + else { + Cu.reportError("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); + } + + // Initialize bookmark archiving on idle. + if (!this._bookmarksBackupIdleTime) { + this._bookmarksBackupIdleTime = BOOKMARKS_BACKUP_IDLE_TIME_SEC; + + // If there is no backup, or the last bookmarks backup is too old, use + // a more aggressive idle observer. + if (lastBackupFile === undefined) + lastBackupFile = yield PlacesBackups.getMostRecentBackup(); + if (!lastBackupFile) { + this._bookmarksBackupIdleTime /= 2; + } + else { + let lastBackupTime = PlacesBackups.getDateForFile(lastBackupFile); + let profileLastUse = Services.appinfo.replacedLockTime || Date.now(); + + // If there is a backup after the last profile usage date it's fine, + // regardless its age. Otherwise check how old is the last + // available backup compared to that session. + if (profileLastUse > lastBackupTime) { + let backupAge = Math.round((profileLastUse - lastBackupTime) / 86400000); + // Report the age of the last available backup. + try { + Services.telemetry + .getHistogramById("PLACES_BACKUPS_DAYSFROMLAST") + .add(backupAge); + } catch (ex) { + Components.utils.reportError("Unable to report telemetry."); + } + + if (backupAge > BOOKMARKS_BACKUP_MAX_INTERVAL_DAYS) + this._bookmarksBackupIdleTime /= 2; + } + } + this._idleService.addIdleObserver(this, this._bookmarksBackupIdleTime); + } + + Services.obs.notifyObservers(null, "places-browser-init-complete", ""); + }.bind(this)); + }, + + /** + * Places shut-down tasks + * - finalize components depending on Places. + * - export bookmarks as HTML, if so configured. + */ + _onPlacesShutdown: function BG__onPlacesShutdown() { + this._sanitizer.onShutdown(); + PageThumbs.uninit(); + + if (this._bookmarksBackupIdleTime) { + this._idleService.removeIdleObserver(this, this._bookmarksBackupIdleTime); + delete this._bookmarksBackupIdleTime; + } + }, + + /** + * If a backup for today doesn't exist, this creates one. + */ + _backupBookmarks: function BG__backupBookmarks() { + return Task.spawn(function() { + let lastBackupFile = yield 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"); + yield PlacesBackups.create(maxBackups); + } + }); + }, + + /** + * Show the notificationBox for a locked places database. + */ + _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() { + var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties"); + var applicationName = brandBundle.GetStringFromName("brandShortName"); + var placesBundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties"); + var title = placesBundle.GetStringFromName("lockPrompt.title"); + var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1); + var buttonText = placesBundle.GetStringFromName("lockPromptInfoButton.label"); + var accessKey = placesBundle.GetStringFromName("lockPromptInfoButton.accessKey"); + + var helpTopic = "places-locked"; + var url = Cc["@mozilla.org/toolkit/URLFormatterService;1"]. + getService(Components.interfaces.nsIURLFormatter). + formatURLPref("app.support.baseURL"); + url += helpTopic; + + var win = this.getMostRecentBrowserWindow(); + + var buttons = [ + { + label: buttonText, + accessKey: accessKey, + popup: null, + callback: function(aNotificationBar, aButton) { + win.openUILinkIn(url, "tab"); + } + } + ]; + + var notifyBox = win.gBrowser.getNotificationBox(); + var notification = notifyBox.appendNotification(text, title, null, + notifyBox.PRIORITY_CRITICAL_MEDIUM, + buttons); + notification.persistence = -1; // Until user closes it + }, + + _migrateUI: function BG__migrateUI() { + const UI_VERSION = 22; + const BROWSER_DOCURL = "chrome://browser/content/browser.xul#"; + let currentUIVersion = 0; + try { + currentUIVersion = Services.prefs.getIntPref("browser.migration.version"); + } catch(ex) {} + if (currentUIVersion >= UI_VERSION) + return; + + this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService); + this._dataSource = this._rdf.GetDataSource("rdf:local-store"); + this._dirty = false; + + if (currentUIVersion < 2) { + // This code adds the customizable bookmarks button. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + // Need to migrate only if toolbar is customized and the element is not found. + if (currentset && + currentset.indexOf("bookmarks-menu-button-container") == -1) { + currentset += ",bookmarks-menu-button-container"; + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + + if (currentUIVersion < 3) { + // This code merges the reload/stop/go button into the url bar. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + // Need to migrate only if toolbar is customized and all 3 elements are found. + if (currentset && + currentset.indexOf("reload-button") != -1 && + currentset.indexOf("stop-button") != -1 && + currentset.indexOf("urlbar-container") != -1 && + currentset.indexOf("urlbar-container,reload-button,stop-button") == -1) { + currentset = currentset.replace(/(^|,)reload-button($|,)/, "$1$2") + .replace(/(^|,)stop-button($|,)/, "$1$2") + .replace(/(^|,)urlbar-container($|,)/, + "$1urlbar-container,reload-button,stop-button$2"); + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + + if (currentUIVersion < 4) { + // This code moves the home button to the immediate left of the bookmarks menu button. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + // Need to migrate only if toolbar is customized and the elements are found. + if (currentset && + currentset.indexOf("home-button") != -1 && + currentset.indexOf("bookmarks-menu-button-container") != -1) { + currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2") + .replace(/(^|,)bookmarks-menu-button-container($|,)/, + "$1home-button,bookmarks-menu-button-container$2"); + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + + if (currentUIVersion < 5) { + // This code uncollapses PersonalToolbar if its collapsed status is not + // persisted, and user customized it or changed default bookmarks. + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar"); + let collapsedResource = this._rdf.GetResource("collapsed"); + let collapsed = this._getPersist(toolbarResource, collapsedResource); + // If the user does not have a persisted value for the toolbar's + // "collapsed" attribute, try to determine whether it's customized. + if (collapsed === null) { + // We consider the toolbar customized if it has more than + // 3 children, or if it has a persisted currentset value. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarIsCustomized = !!this._getPersist(toolbarResource, + currentsetResource); + function getToolbarFolderCount() { + let toolbarFolder = + PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root; + let toolbarChildCount = toolbarFolder.childCount; + toolbarFolder.containerOpen = false; + return toolbarChildCount; + } + + if (toolbarIsCustomized || getToolbarFolderCount() > 3) { + this._setPersist(toolbarResource, collapsedResource, "false"); + } + } + } + + if (currentUIVersion < 6) { + // convert tabsontop attribute to pref + let toolboxResource = this._rdf.GetResource(BROWSER_DOCURL + "navigator-toolbox"); + let tabsOnTopResource = this._rdf.GetResource("tabsontop"); + let tabsOnTopAttribute = this._getPersist(toolboxResource, tabsOnTopResource); + if (tabsOnTopAttribute) + Services.prefs.setBoolPref("browser.tabs.onTop", tabsOnTopAttribute == "true"); + } + + // Migration at version 7 only occurred for users who wanted to try the new + // Downloads Panel feature before its release. Since migration at version + // 9 adds the button by default, this step has been removed. + + if (currentUIVersion < 8) { + // Reset homepage pref for users who have it set to google.com/firefox + let uri = Services.prefs.getComplexValue("browser.startup.homepage", + Ci.nsIPrefLocalizedString).data; + if (uri && /^https?:\/\/(www\.)?google(\.\w{2,3}){1,2}\/firefox\/?$/.test(uri)) { + Services.prefs.clearUserPref("browser.startup.homepage"); + } + } + + if (currentUIVersion < 9) { + // This code adds the customizable downloads buttons. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + + // Since the Downloads button is located in the navigation bar by default, + // migration needs to happen only if the toolbar was customized using a + // previous UI version, and the button was not already placed on the + // toolbar manually. + if (currentset && + currentset.indexOf("downloads-button") == -1) { + // The element is added either after the search bar or before the home + // button. As a last resort, the element is added just before the + // non-customizable window controls. + if (currentset.indexOf("search-container") != -1) { + currentset = currentset.replace(/(^|,)search-container($|,)/, + "$1search-container,downloads-button$2") + } else if (currentset.indexOf("home-button") != -1) { + currentset = currentset.replace(/(^|,)home-button($|,)/, + "$1downloads-button,home-button$2") + } else { + currentset = currentset.replace(/(^|,)window-controls($|,)/, + "$1downloads-button,window-controls$2") + } + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + +#ifdef XP_WIN + if (currentUIVersion < 10) { + // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale + // will return a value > 1.0), we want to discard any saved full-zoom settings, + // as we'll now be scaling the content according to the system resolution + // scale factor (Windows "logical DPI" setting) + let sm = Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager); + if (sm.systemDefaultScale > 1.0) { + let cps2 = Cc["@mozilla.org/content-pref/service;1"]. + getService(Ci.nsIContentPrefService2); + cps2.removeByName("browser.content.full-zoom", null); + } + } +#endif + + if (currentUIVersion < 11) { + Services.prefs.clearUserPref("dom.disable_window_move_resize"); + Services.prefs.clearUserPref("dom.disable_window_flip"); + Services.prefs.clearUserPref("dom.event.contextmenu.enabled"); + Services.prefs.clearUserPref("javascript.enabled"); + Services.prefs.clearUserPref("permissions.default.image"); + } + + if (currentUIVersion < 12) { + // Remove bookmarks-menu-button-container, then place + // bookmarks-menu-button into its position. + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + // Need to migrate only if toolbar is customized. + if (currentset) { + if (currentset.contains("bookmarks-menu-button-container")) { + currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/, + "$1bookmarks-menu-button$2"); + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + } + + if (currentUIVersion < 13) { + try { + if (Services.prefs.getBoolPref("plugins.hide_infobar_for_missing_plugin")) + Services.prefs.setBoolPref("plugins.notifyMissingFlash", false); + } + catch (ex) {} + } + + if (currentUIVersion < 14) { + // DOM Storage doesn't specially handle about: pages anymore. + let path = OS.Path.join(OS.Constants.Path.profileDir, + "chromeappsstore.sqlite"); + OS.File.remove(path); + } + + // Version 15 was obsoleted in favour of 18. + + if (currentUIVersion < 16) { + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let collapsedResource = this._rdf.GetResource("collapsed"); + let isCollapsed = this._getPersist(toolbarResource, collapsedResource); + if (isCollapsed == "true") { + this._setPersist(toolbarResource, collapsedResource, "false"); + } + } + + // Insert the bookmarks-menu-button into the nav-bar if it isn't already + // there. + if (currentUIVersion < 17) { + let currentsetResource = this._rdf.GetResource("currentset"); + let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar"); + let currentset = this._getPersist(toolbarResource, currentsetResource); + // Need to migrate only if toolbar is customized. + if (currentset) { + if (!currentset.contains("bookmarks-menu-button")) { + // The button isn't in the nav-bar, so let's look for an appropriate + // place to put it. + if (currentset.contains("downloads-button")) { + currentset = currentset.replace(/(^|,)downloads-button($|,)/, + "$1bookmarks-menu-button,downloads-button$2"); + } else if (currentset.contains("home-button")) { + currentset = currentset.replace(/(^|,)home-button($|,)/, + "$1bookmarks-menu-button,home-button$2"); + } else { + // Just append. + currentset = currentset.replace(/(^|,)window-controls($|,)/, + "$1bookmarks-menu-button,window-controls$2") + } + this._setPersist(toolbarResource, currentsetResource, currentset); + } + } + } + + if (currentUIVersion < 18) { + // Remove iconsize and mode from all the toolbars + let toolbars = ["navigator-toolbox", "nav-bar", "PersonalToolbar", + "addon-bar", "TabsToolbar", "toolbar-menubar"]; + for (let resourceName of ["mode", "iconsize"]) { + let resource = this._rdf.GetResource(resourceName); + for (let toolbarId of toolbars) { + let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbarId); + if (this._getPersist(toolbar, resource)) { + this._setPersist(toolbar, resource); + } + } + } + } + + if (currentUIVersion < 19) { + let detector = null; + try { + detector = Services.prefs.getComplexValue("intl.charset.detector", + Ci.nsIPrefLocalizedString).data; + } catch (ex) {} + if (!(detector == "" || + detector == "ja_parallel_state_machine" || + detector == "ruprob" || + detector == "ukprob")) { + // If the encoding detector pref value is not reachable from the UI, + // reset to default (varies by localization). + Services.prefs.clearUserPref("intl.charset.detector"); + } + } + + if (currentUIVersion < 20) { + // Remove persisted collapsed state from TabsToolbar. + let resource = this._rdf.GetResource("collapsed"); + let toolbar = this._rdf.GetResource(BROWSER_DOCURL + "TabsToolbar"); + if (this._getPersist(toolbar, resource)) { + this._setPersist(toolbar, resource); + } + } + + if (currentUIVersion < 21) { + // Make sure the 'toolbarbutton-1' class will always be present from here + // on out. + let button = this._rdf.GetResource(BROWSER_DOCURL + "bookmarks-menu-button"); + let classResource = this._rdf.GetResource("class"); + if (this._getPersist(button, classResource)) { + this._setPersist(button, classResource); + } + } + + if (currentUIVersion < 22) { + // Reset the Sync promobox count to promote the new FxAccount-based Sync. + Services.prefs.clearUserPref("browser.syncPromoViewsLeft"); + Services.prefs.clearUserPref("browser.syncPromoViewsLeftMap"); + } + + if (this._dirty) + this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush(); + + delete this._rdf; + delete this._dataSource; + + // Update the migration version. + Services.prefs.setIntPref("browser.migration.version", UI_VERSION); + }, + + _getPersist: function BG__getPersist(aSource, aProperty) { + var target = this._dataSource.GetTarget(aSource, aProperty, true); + if (target instanceof Ci.nsIRDFLiteral) + return target.Value; + return null; + }, + + _setPersist: function BG__setPersist(aSource, aProperty, aTarget) { + this._dirty = true; + try { + var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true); + if (oldTarget) { + if (aTarget) + this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget)); + else + this._dataSource.Unassert(aSource, aProperty, oldTarget); + } + else { + this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true); + } + + // Add the entry to the persisted set for this document if it's not there. + // This code is mostly borrowed from XULDocument::Persist. + let docURL = aSource.ValueUTF8.split("#")[0]; + let docResource = this._rdf.GetResource(docURL); + let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist"); + if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) { + this._dataSource.Assert(docResource, persistResource, aSource, true); + } + } + catch(ex) {} + }, + + // ------------------------------ + // public nsIBrowserGlue members + // ------------------------------ + + sanitize: function BG_sanitize(aParentWindow) { + this._sanitizer.sanitize(aParentWindow); + }, + + ensurePlacesDefaultQueriesInitialized: + function BG_ensurePlacesDefaultQueriesInitialized() { + // This is actual version of the smart bookmarks, must be increased every + // time smart bookmarks 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 = 0; + try { + smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF); + } catch(ex) {} + + // If version is current or smart bookmarks are disabled, just bail out. + if (smartBookmarksCurrentVersion == -1 || + smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) { + return; + } + + let batch = { + runBatched: function BG_EPDQI_runBatched() { + let menuIndex = 0; + let toolbarIndex = 0; + let bundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties"); + + let smartBookmarks = { + MostVisited: { + title: bundle.GetStringFromName("mostVisitedTitle"), + uri: NetUtil.newURI("place:sort=" + + Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING + + "&maxResults=" + MAX_RESULTS), + parent: PlacesUtils.toolbarFolderId, + get position() { return toolbarIndex++; }, + newInVersion: 1 + }, + RecentlyBookmarked: { + title: bundle.GetStringFromName("recentlyBookmarkedTitle"), + uri: NetUtil.newURI("place:folder=BOOKMARKS_MENU" + + "&folder=UNFILED_BOOKMARKS" + + "&folder=TOOLBAR" + + "&queryType=" + + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS + + "&sort=" + + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING + + "&maxResults=" + MAX_RESULTS + + "&excludeQueries=1"), + parent: PlacesUtils.bookmarksMenuFolderId, + get position() { return menuIndex++; }, + newInVersion: 1 + }, + RecentTags: { + title: bundle.GetStringFromName("recentTagsTitle"), + uri: NetUtil.newURI("place:"+ + "type=" + + Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY + + "&sort=" + + Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING + + "&maxResults=" + MAX_RESULTS), + parent: PlacesUtils.bookmarksMenuFolderId, + get position() { return menuIndex++; }, + newInVersion: 1 + }, + }; + + if (Services.metro && Services.metro.supported) { + smartBookmarks.Windows8Touch = { + title: PlacesUtils.getString("windows8TouchTitle"), + get uri() { + let metroBookmarksRoot = PlacesUtils.annotations.getItemsWithAnnotation('metro/bookmarksRoot', {}); + if (metroBookmarksRoot.length > 0) { + return NetUtil.newURI("place:folder=" + + metroBookmarksRoot[0] + + "&queryType=" + + Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS + + "&sort=" + + Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING + + "&maxResults=" + MAX_RESULTS + + "&excludeQueries=1") + } + return null; + }, + parent: PlacesUtils.bookmarksMenuFolderId, + get position() { return menuIndex++; }, + newInVersion: 7 + }; + } + + // Set current itemId, parent and position if Smart Bookmark exists, + // we will use these informations to create the new version at the same + // position. + let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO); + smartBookmarkItemIds.forEach(function (itemId) { + let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO); + if (queryId in smartBookmarks) { + let smartBookmark = smartBookmarks[queryId]; + if (!smartBookmark.uri) { + PlacesUtils.bookmarks.removeItem(itemId); + return; + } + smartBookmark.itemId = itemId; + smartBookmark.parent = PlacesUtils.bookmarks.getFolderIdForItem(itemId); + smartBookmark.updatedPosition = PlacesUtils.bookmarks.getItemIndex(itemId); + } + 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 in 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.itemId || !smartBookmark.uri) + continue; + + // Remove old version of the smart bookmark if it exists, since it + // will be replaced in place. + if (smartBookmark.itemId) { + PlacesUtils.bookmarks.removeItem(smartBookmark.itemId); + } + + // Create the new smart bookmark and store its updated itemId. + smartBookmark.itemId = + PlacesUtils.bookmarks.insertBookmark(smartBookmark.parent, + smartBookmark.uri, + smartBookmark.updatedPosition || smartBookmark.position, + smartBookmark.title); + PlacesUtils.annotations.setItemAnnotation(smartBookmark.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 id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarksMenuFolderId, + menuIndex); + // Don't add a separator if the menu was empty or there is one already. + if (id != -1 && + PlacesUtils.bookmarks.getItemType(id) != PlacesUtils.bookmarks.TYPE_SEPARATOR) { + PlacesUtils.bookmarks.insertSeparator(PlacesUtils.bookmarksMenuFolderId, + menuIndex); + } + } + } + }; + + try { + PlacesUtils.bookmarks.runInBatchMode(batch, null); + } + catch(ex) { + Components.utils.reportError(ex); + } + finally { + Services.prefs.setIntPref(SMART_BOOKMARKS_PREF, SMART_BOOKMARKS_VERSION); + Services.prefs.savePrefFile(null); + } + }, + + // this returns the most recent non-popup browser window + getMostRecentBrowserWindow: function BG_getMostRecentBrowserWindow() { + return RecentWindow.getMostRecentBrowserWindow(); + }, + +#ifdef MOZ_SERVICES_SYNC + /** + * Called as an observer when Sync's "display URI" notification is fired. + * + * We open the received URI in a background tab. + * + * Eventually, this will likely be replaced by a more robust tab syncing + * feature. This functionality is considered somewhat evil by UX because it + * opens a new tab automatically without any prompting. However, it is a + * lesser evil than sending a tab to a specific device (from e.g. Fennec) + * and having nothing happen on the receiving end. + */ + _onDisplaySyncURI: function _onDisplaySyncURI(data) { + try { + let tabbrowser = RecentWindow.getMostRecentBrowserWindow({private: false}).gBrowser; + + // The payload is wrapped weirdly because of how Sync does notifications. + tabbrowser.addTab(data.wrappedJSObject.object.uri); + } catch (ex) { + Cu.reportError("Error displaying tab received by Sync: " + ex); + } + }, +#endif + + // for XPCOM + classID: Components.ID("{eab9012e-5f74-4cbc-b2b5-a590235513cc}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, + Ci.nsISupportsWeakReference, + Ci.nsIBrowserGlue]), + + // redefine the default factory for XPCOMUtils + _xpcom_factory: BrowserGlueServiceFactory, +} + +function ContentPermissionPrompt() {} + +ContentPermissionPrompt.prototype = { + classID: Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]), + + _getBrowserForRequest: function (aRequest) { + // "element" is only defined in e10s mode. + let browser = aRequest.element; + if (!browser) { + // Find the requesting browser. + browser = aRequest.window.QueryInterface(Ci.nsIInterfaceRequestor) + .getInterface(Ci.nsIWebNavigation) + .QueryInterface(Ci.nsIDocShell) + .chromeEventHandler; + } + return browser; + }, + + /** + * Show a permission prompt. + * + * @param aRequest The permission request. + * @param aMessage The message to display on the prompt. + * @param aPermission The type of permission to prompt. + * @param aActions An array of actions of the form: + * [main action, secondary actions, ...] + * Actions are of the form { stringId, action, expireType, callback } + * Permission is granted if action is null or ALLOW_ACTION. + * @param aNotificationId The id of the PopupNotification. + * @param aAnchorId The id for the PopupNotification anchor. + * @param aOptions Options for the PopupNotification + */ + _showPrompt: function CPP_showPrompt(aRequest, aMessage, aPermission, aActions, + aNotificationId, aAnchorId, aOptions) { + function onFullScreen() { + popup.remove(); + } + + var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + + var browser = this._getBrowserForRequest(aRequest); + var chromeWin = browser.ownerDocument.defaultView; + var requestPrincipal = aRequest.principal; + + // Transform the prompt actions into PopupNotification actions. + var popupNotificationActions = []; + for (var i = 0; i < aActions.length; i++) { + let promptAction = aActions[i]; + + // Don't offer action in PB mode if the action remembers permission for more than a session. + if (PrivateBrowsingUtils.isWindowPrivate(chromeWin) && + promptAction.expireType != Ci.nsIPermissionManager.EXPIRE_SESSION && + promptAction.action) { + continue; + } + + var action = { + label: browserBundle.GetStringFromName(promptAction.stringId), + accessKey: browserBundle.GetStringFromName(promptAction.stringId + ".accesskey"), + callback: function() { + if (promptAction.callback) { + promptAction.callback(); + } + + // Remember permissions. + if (promptAction.action) { + Services.perms.addFromPrincipal(requestPrincipal, aPermission, + promptAction.action, promptAction.expireType); + } + + // Grant permission if action is null or ALLOW_ACTION. + if (!promptAction.action || promptAction.action == Ci.nsIPermissionManager.ALLOW_ACTION) { + aRequest.allow(); + } else { + aRequest.cancel(); + } + }, + }; + + popupNotificationActions.push(action); + } + + var mainAction = popupNotificationActions.length ? + popupNotificationActions[0] : null; + var secondaryActions = popupNotificationActions.splice(1); + + // Only allow exactly one permission rquest here. + let types = aRequest.types.QueryInterface(Ci.nsIArray); + if (types.length != 1) { + aRequest.cancel(); + return; + } + + let perm = types.queryElementAt(0, Ci.nsIContentPermissionType); + + if (perm.type == "pointerLock") { + // If there's no mainAction, this is the autoAllow warning prompt. + let autoAllow = !mainAction; + + if (!aOptions) + aOptions = {}; + + aOptions.removeOnDismissal = autoAllow; + aOptions.eventCallback = type => { + if (type == "removed") { + browser.removeEventListener("mozfullscreenchange", onFullScreen, true); + if (autoAllow) { + aRequest.allow(); + } + } + } + + } + + var popup = chromeWin.PopupNotifications.show(browser, aNotificationId, aMessage, aAnchorId, + mainAction, secondaryActions, aOptions); + if (perm.type == "pointerLock") { + // pointerLock is automatically allowed in fullscreen mode (and revoked + // upon exit), so if the page enters fullscreen mode after requesting + // pointerLock (but before the user has granted permission), we should + // remove the now-impotent notification. + browser.addEventListener("mozfullscreenchange", onFullScreen, true); + } + }, + + _promptGeo : function(aRequest) { + var secHistogram = Services.telemetry.getHistogramById("SECURITY_UI"); + var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + var requestingURI = aRequest.principal.URI; + + var message; + + // Share location action. + var actions = [{ + stringId: "geolocation.shareLocation", + action: null, + expireType: null, + callback: function() { + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_SHARE_LOCATION); + }, + }]; + + if (requestingURI.schemeIs("file")) { + message = browserBundle.formatStringFromName("geolocation.shareWithFile", + [requestingURI.path], 1); + } else { + message = browserBundle.formatStringFromName("geolocation.shareWithSite", + [requestingURI.host], 1); + // Always share location action. + actions.push({ + stringId: "geolocation.alwaysShareLocation", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: null, + callback: function() { + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_ALWAYS_SHARE); + }, + }); + + // Never share location action. + actions.push({ + stringId: "geolocation.neverShareLocation", + action: Ci.nsIPermissionManager.DENY_ACTION, + expireType: null, + callback: function() { + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST_NEVER_SHARE); + }, + }); + } + + var options = { + learnMoreURL: Services.urlFormatter.formatURLPref("browser.geolocation.warning.infoURL"), + }; + + secHistogram.add(Ci.nsISecurityUITelemetry.WARNING_GEOLOCATION_REQUEST); + + this._showPrompt(aRequest, message, "geo", actions, "geolocation", + "geo-notification-icon", options); + }, + + _promptWebNotifications : function(aRequest) { + var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + var requestingURI = aRequest.principal.URI; + + var message = browserBundle.formatStringFromName("webNotifications.showFromSite", + [requestingURI.host], 1); + + var actions = [ + { + stringId: "webNotifications.showForSession", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, + callback: function() {}, + }, + { + stringId: "webNotifications.alwaysShow", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: null, + callback: function() {}, + }, + { + stringId: "webNotifications.neverShow", + action: Ci.nsIPermissionManager.DENY_ACTION, + expireType: null, + callback: function() {}, + }, + ]; + + this._showPrompt(aRequest, message, "desktop-notification", actions, + "web-notifications", + "web-notifications-notification-icon", null); + }, + + _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) { + + let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties"); + let requestingURI = aRequest.principal.URI; + + let originString = requestingURI.schemeIs("file") ? requestingURI.path : requestingURI.host; + let message = browserBundle.formatStringFromName(autoAllow ? + "pointerLock.autoLock.title2" : "pointerLock.title2", + [originString], 1); + // If this is an autoAllow info prompt, offer no actions. + // _showPrompt() will allow the request when it's dismissed. + let actions = []; + if (!autoAllow) { + actions = [ + { + stringId: "pointerLock.allow2", + action: null, + expireType: null, + callback: function() {}, + }, + { + stringId: "pointerLock.alwaysAllow", + action: Ci.nsIPermissionManager.ALLOW_ACTION, + expireType: null, + callback: function() {}, + }, + { + stringId: "pointerLock.neverAllow", + action: Ci.nsIPermissionManager.DENY_ACTION, + expireType: null, + callback: function() {}, + }, + ]; + } + + this._showPrompt(aRequest, message, "pointerLock", actions, "pointerLock", + "pointerLock-notification-icon", null); + }, + + prompt: function CPP_prompt(request) { + + // Only allow exactly one permission rquest here. + let types = request.types.QueryInterface(Ci.nsIArray); + if (types.length != 1) { + request.cancel(); + return; + } + let perm = types.queryElementAt(0, Ci.nsIContentPermissionType); + + const kFeatureKeys = { "geolocation" : "geo", + "desktop-notification" : "desktop-notification", + "pointerLock" : "pointerLock", + }; + + // Make sure that we support the request. + if (!(perm.type in kFeatureKeys)) { + return; + } + + var requestingPrincipal = request.principal; + var requestingURI = requestingPrincipal.URI; + + // Ignore requests from non-nsIStandardURLs + if (!(requestingURI instanceof Ci.nsIStandardURL)) + return; + + var autoAllow = false; + var permissionKey = kFeatureKeys[perm.type]; + var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey); + + if (result == Ci.nsIPermissionManager.DENY_ACTION) { + request.cancel(); + return; + } + + if (result == Ci.nsIPermissionManager.ALLOW_ACTION) { + autoAllow = true; + // For pointerLock, we still want to show a warning prompt. + if (perm.type != "pointerLock") { + request.allow(); + return; + } + } + + var browser = this._getBrowserForRequest(request); + var chromeWin = browser.ownerDocument.defaultView; + if (!chromeWin.PopupNotifications) + // Ignore requests from browsers hosted in windows that don't support + // PopupNotifications. + return; + + // Show the prompt. + switch (perm.type) { + case "geolocation": + this._promptGeo(request); + break; + case "desktop-notification": + this._promptWebNotifications(request); + break; + case "pointerLock": + this._promptPointerLock(request, autoAllow); + break; + } + }, + +}; + +var components = [BrowserGlue, ContentPermissionPrompt]; +this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);