Follow up fix for bug 623435. (r=brendan)
[mozilla-central.git] / browser / components / nsBrowserGlue.js
blob216dca3df00975bd866869bc9845ac60999610e7
1 # ***** BEGIN LICENSE BLOCK *****
2 # Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 # The contents of this file are subject to the Mozilla Public License Version
5 # 1.1 (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
7 # http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS IS" basis,
10 # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 # for the specific language governing rights and limitations under the
12 # License.
14 # The Original Code is the Browser Search Service.
16 # The Initial Developer of the Original Code is
17 # Giorgio Maone
18 # Portions created by the Initial Developer are Copyright (C) 2005
19 # the Initial Developer. All Rights Reserved.
21 # Contributor(s):
22 #   Giorgio Maone <g.maone@informaction.com>
23 #   Seth Spitzer <sspitzer@mozilla.com>
24 #   Asaf Romano <mano@mozilla.com>
25 #   Marco Bonardo <mak77@bonardo.net>
26 #   Dietrich Ayala <dietrich@mozilla.com>
27 #   Ehsan Akhgari <ehsan.akhgari@gmail.com>
28 #   Nils Maier <maierman@web.de>
29 #   Robert Strong <robert.bugzilla@gmail.com>
31 # Alternatively, the contents of this file may be used under the terms of
32 # either the GNU General Public License Version 2 or later (the "GPL"), or
33 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34 # in which case the provisions of the GPL or the LGPL are applicable instead
35 # of those above. If you wish to allow use of your version of this file only
36 # under the terms of either the GPL or the LGPL, and not to allow others to
37 # use your version of this file under the terms of the MPL, indicate your
38 # decision by deleting the provisions above and replace them with the notice
39 # and other provisions required by the GPL or the LGPL. If you do not delete
40 # the provisions above, a recipient may use your version of this file under
41 # the terms of any one of the MPL, the GPL or the LGPL.
43 # ***** END LICENSE BLOCK *****
45 const Ci = Components.interfaces;
46 const Cc = Components.classes;
47 const Cr = Components.results;
48 const Cu = Components.utils;
50 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
52 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
53 Cu.import("resource://gre/modules/Services.jsm");
55 XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
56   Cu.import("resource://gre/modules/NetUtil.jsm");
57   return NetUtil;
58 });
60 XPCOMUtils.defineLazyGetter(this, "PlacesUtils", function() {
61   Cu.import("resource://gre/modules/PlacesUtils.jsm");
62   return PlacesUtils;
63 });
65 const PREF_EM_NEW_ADDONS_LIST = "extensions.newAddons";
66 const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
67 const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
69 // We try to backup bookmarks at idle times, to avoid doing that at shutdown.
70 // Number of idle seconds before trying to backup bookmarks.  15 minutes.
71 const BOOKMARKS_BACKUP_IDLE_TIME = 15 * 60;
72 // Minimum interval in milliseconds between backups.
73 const BOOKMARKS_BACKUP_INTERVAL = 86400 * 1000;
74 // Maximum number of backups to create.  Old ones will be purged.
75 const BOOKMARKS_BACKUP_MAX_BACKUPS = 10;
77 // Factory object
78 const BrowserGlueServiceFactory = {
79   _instance: null,
80   createInstance: function BGSF_createInstance(outer, iid) {
81     if (outer != null)
82       throw Components.results.NS_ERROR_NO_AGGREGATION;
83     return this._instance == null ?
84       this._instance = new BrowserGlue() : this._instance;
85   }
88 // Constructor
90 function BrowserGlue() {
91   XPCOMUtils.defineLazyServiceGetter(this, "_idleService",
92                                      "@mozilla.org/widget/idleservice;1",
93                                      "nsIIdleService");
95   XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() {
96                                 Cu.import("resource:///modules/distribution.js");
97                                 return new DistributionCustomizer();
98                               });
100   XPCOMUtils.defineLazyGetter(this, "_sanitizer",
101     function() {
102       let sanitizerScope = {};
103       Services.scriptloader.loadSubScript("chrome://browser/content/sanitize.js", sanitizerScope);
104       return sanitizerScope.Sanitizer;
105     });
107   this._init();
110 #ifndef XP_MACOSX
111 # OS X has the concept of zero-window sessions and therefore ignores the
112 # browser-lastwindow-close-* topics.
113 #define OBSERVE_LASTWINDOW_CLOSE_TOPICS 1
114 #endif
116 BrowserGlue.prototype = {
117   _saveSession: false,
118   _isIdleObserver: false,
119   _isPlacesInitObserver: false,
120   _isPlacesLockedObserver: false,
121   _isPlacesShutdownObserver: false,
122   _isPlacesDatabaseLocked: false,
124   _setPrefToSaveSession: function BG__setPrefToSaveSession(aForce) {
125     if (!this._saveSession && !aForce)
126       return;
128     Services.prefs.setBoolPref("browser.sessionstore.resume_session_once", true);
130     // This method can be called via [NSApplication terminate:] on Mac, which
131     // ends up causing prefs not to be flushed to disk, so we need to do that
132     // explicitly here. See bug 497652.
133     Services.prefs.savePrefFile(null);
134   },
136 #ifdef MOZ_SERVICES_SYNC
137   _setSyncAutoconnectDelay: function BG__setSyncAutoconnectDelay() {
138     // Assume that a non-zero value for services.sync.autoconnectDelay should override
139     if (Services.prefs.prefHasUserValue("services.sync.autoconnectDelay")) {
140       let prefDelay = Services.prefs.getIntPref("services.sync.autoconnectDelay");
142       if (prefDelay > 0)
143         return;
144     }
146     // delays are in seconds
147     const MAX_DELAY = 300;
148     let delay = 3;
149     let browserEnum = Services.wm.getEnumerator("navigator:browser");
150     while (browserEnum.hasMoreElements()) {
151       delay += browserEnum.getNext().gBrowser.tabs.length;
152     }
153     delay = delay <= MAX_DELAY ? delay : MAX_DELAY;
155     let syncTemp = {};
156     Cu.import("resource://services-sync/service.js", syncTemp);
157     syncTemp.Weave.Service.delayedAutoConnect(delay);
158   },
159 #endif
161   // nsIObserver implementation 
162   observe: function BG_observe(subject, topic, data) {
163     switch (topic) {
164       case "xpcom-shutdown":
165         this._dispose();
166         break;
167       case "prefservice:after-app-defaults":
168         this._onAppDefaults();
169         break;
170       case "final-ui-startup":
171         this._onProfileStartup();
172         break;
173       case "browser-delayed-startup-finished":
174         this._onFirstWindowLoaded();
175         Services.obs.removeObserver(this, "browser-delayed-startup-finished");
176         break;
177       case "sessionstore-windows-restored":
178         this._onBrowserStartup();
179         break;
180       case "browser:purge-session-history":
181         // reset the console service's error buffer
182         Services.console.logStringMessage(null); // clear the console (in case it's open)
183         Services.console.reset();
184         break;
185       case "quit-application-requested":
186         this._onQuitRequest(subject, data);
187         break;
188       case "quit-application-granted":
189         // This pref must be set here because SessionStore will use its value
190         // on quit-application.
191         this._setPrefToSaveSession();
192         break;
193 #ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
194       case "browser-lastwindow-close-requested":
195         // The application is not actually quitting, but the last full browser
196         // window is about to be closed.
197         this._onQuitRequest(subject, "lastwindow");
198         break;
199       case "browser-lastwindow-close-granted":
200         this._setPrefToSaveSession();
201         break;
202 #endif
203 #ifdef MOZ_SERVICES_SYNC
204       case "weave:service:ready":
205         this._setSyncAutoconnectDelay();
206         break;
207 #endif
208       case "session-save":
209         this._setPrefToSaveSession(true);
210         subject.QueryInterface(Ci.nsISupportsPRBool);
211         subject.data = true;
212         break;
213       case "places-init-complete":
214         this._initPlaces();
215         Services.obs.removeObserver(this, "places-init-complete");
216         this._isPlacesInitObserver = false;
217         // no longer needed, since history was initialized completely.
218         Services.obs.removeObserver(this, "places-database-locked");
219         this._isPlacesLockedObserver = false;
221         // Now apply distribution customized bookmarks.
222         // This should always run after Places initialization.
223         this._distributionCustomizer.applyBookmarks();
224         break;
225       case "places-database-locked":
226         this._isPlacesDatabaseLocked = true;
227         // Stop observing, so further attempts to load history service
228         // will not show the prompt.
229         Services.obs.removeObserver(this, "places-database-locked");
230         this._isPlacesLockedObserver = false;
231         break;
232       case "places-shutdown":
233         if (this._isPlacesShutdownObserver) {
234           Services.obs.removeObserver(this, "places-shutdown");
235           this._isPlacesShutdownObserver = false;
236         }
237         // places-shutdown is fired when the profile is about to disappear.
238         this._onProfileShutdown();
239         break;
240       case "idle":
241         if (this._idleService.idleTime > BOOKMARKS_BACKUP_IDLE_TIME * 1000)
242           this._backupBookmarks();
243         break;
244       case "distribution-customization-complete":
245         Services.obs.removeObserver(this, "distribution-customization-complete");
246         // Customization has finished, we don't need the customizer anymore.
247         delete this._distributionCustomizer;
248         break;
249       case "bookmarks-restore-success":
250       case "bookmarks-restore-failed":
251         Services.obs.removeObserver(this, "bookmarks-restore-success");
252         Services.obs.removeObserver(this, "bookmarks-restore-failed");
253         if (topic == "bookmarks-restore-success" && data == "html-initial")
254           this.ensurePlacesDefaultQueriesInitialized();
255         break;
256       case "browser-glue-test": // used by tests
257         if (data == "post-update-notification") {
258           if (Services.prefs.prefHasUserValue("app.update.postupdate"))
259             this._showUpdateNotification();
260         }
261         else if (data == "force-ui-migration") {
262           this._migrateUI();
263         }
264         break;
265     }
266   }, 
268   // initialization (called on application startup) 
269   _init: function BG__init() {
270     let os = Services.obs;
271     os.addObserver(this, "xpcom-shutdown", false);
272     os.addObserver(this, "prefservice:after-app-defaults", false);
273     os.addObserver(this, "final-ui-startup", false);
274     os.addObserver(this, "browser-delayed-startup-finished", false);
275     os.addObserver(this, "sessionstore-windows-restored", false);
276     os.addObserver(this, "browser:purge-session-history", false);
277     os.addObserver(this, "quit-application-requested", false);
278     os.addObserver(this, "quit-application-granted", false);
279 #ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
280     os.addObserver(this, "browser-lastwindow-close-requested", false);
281     os.addObserver(this, "browser-lastwindow-close-granted", false);
282 #endif
283 #ifdef MOZ_SERVICES_SYNC
284     os.addObserver(this, "weave:service:ready", false);
285 #endif
286     os.addObserver(this, "session-save", false);
287     os.addObserver(this, "places-init-complete", false);
288     this._isPlacesInitObserver = true;
289     os.addObserver(this, "places-database-locked", false);
290     this._isPlacesLockedObserver = true;
291     os.addObserver(this, "distribution-customization-complete", false);
292     os.addObserver(this, "places-shutdown", false);
293     this._isPlacesShutdownObserver = true;
294   },
296   // cleanup (called on application shutdown)
297   _dispose: function BG__dispose() {
298     let os = Services.obs;
299     os.removeObserver(this, "xpcom-shutdown");
300     os.removeObserver(this, "prefservice:after-app-defaults");
301     os.removeObserver(this, "final-ui-startup");
302     os.removeObserver(this, "sessionstore-windows-restored");
303     os.removeObserver(this, "browser:purge-session-history");
304     os.removeObserver(this, "quit-application-requested");
305     os.removeObserver(this, "quit-application-granted");
306 #ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
307     os.removeObserver(this, "browser-lastwindow-close-requested");
308     os.removeObserver(this, "browser-lastwindow-close-granted");
309 #endif
310 #ifdef MOZ_SERVICES_SYNC
311     os.removeObserver(this, "weave:service:ready", false);
312 #endif
313     os.removeObserver(this, "session-save");
314     if (this._isIdleObserver)
315       this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
316     if (this._isPlacesInitObserver)
317       os.removeObserver(this, "places-init-complete");
318     if (this._isPlacesLockedObserver)
319       os.removeObserver(this, "places-database-locked");
320     if (this._isPlacesShutdownObserver)
321       os.removeObserver(this, "places-shutdown");
322   },
324   _onAppDefaults: function BG__onAppDefaults() {
325     // apply distribution customizations (prefs)
326     // other customizations are applied in _onProfileStartup()
327     this._distributionCustomizer.applyPrefDefaults();
328   },
330   // profile startup handler (contains profile initialization routines)
331   _onProfileStartup: function BG__onProfileStartup() {
332     this._sanitizer.onStartup();
333     // check if we're in safe mode
334     if (Services.appinfo.inSafeMode) {
335       Services.ww.openWindow(null, "chrome://browser/content/safeMode.xul", 
336                              "_blank", "chrome,centerscreen,modal,resizable=no", null);
337     }
339     // apply distribution customizations
340     // prefs are applied in _onAppDefaults()
341     this._distributionCustomizer.applyCustomizations();
343     // handle any UI migration
344     this._migrateUI();
346     // if ioService is managing the offline status, then ioservice.offline
347     // is already set correctly. We will continue to allow the ioService
348     // to manage its offline state until the user uses the "Work Offline" UI.
349     if (!Services.io.manageOfflineStatus) {
350       // set the initial state
351       try {
352         Services.io.offline = Services.prefs.getBoolPref("browser.offline");
353       }
354       catch (e) {
355         Services.io.offline = false;
356       }
357     }
359     Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
360   },
362   // the first browser window has finished initializing
363   _onFirstWindowLoaded: function BG__onFirstWindowLoaded() {
364 #ifdef XP_WIN
365 #ifndef WINCE
366     // For windows seven, initialize the jump list module.
367     const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
368     if (WINTASKBAR_CONTRACTID in Cc &&
369         Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar).available) {
370       let temp = {};
371       Cu.import("resource://gre/modules/WindowsJumpLists.jsm", temp);
372       temp.WinTaskbarJumpList.startup();
373     }
374 #endif
375 #endif
376   },
378   // profile shutdown handler (contains profile cleanup routines)
379   _onProfileShutdown: function BG__onProfileShutdown() {
380 #ifdef MOZ_UPDATER
381 #ifdef WINCE
382     // If there's a pending update, clear cache to free up disk space.
383     try {
384       let um = Cc["@mozilla.org/updates/update-manager;1"].
385                getService(Ci.nsIUpdateManager);
386       if (um.activeUpdate && um.activeUpdate.state == "pending") {
387         let cacheService = Cc["@mozilla.org/network/cache-service;1"].
388                            getService(Ci.nsICacheService);
389         cacheService.evictEntries(Ci.nsICache.STORE_ANYWHERE);
390       }
391     } catch (e) { }
392 #endif
393 #endif
394     this._shutdownPlaces();
395     this._sanitizer.onShutdown();
396   },
398   // Browser startup complete. All initial windows have opened.
399   _onBrowserStartup: function BG__onBrowserStartup() {
400     // Show about:rights notification, if needed.
401     if (this._shouldShowRights())
402       this._showRightsNotification();
404     // Show update notification, if needed.
405     if (Services.prefs.prefHasUserValue("app.update.postupdate"))
406       this._showUpdateNotification();
408     // If new add-ons were installed during startup open the add-ons manager.
409     if (Services.prefs.prefHasUserValue(PREF_EM_NEW_ADDONS_LIST)) {
410       var args = Cc["@mozilla.org/supports-array;1"].
411                  createInstance(Ci.nsISupportsArray);
412       var str = Cc["@mozilla.org/supports-string;1"].
413                 createInstance(Ci.nsISupportsString);
414       str.data = "";
415       args.AppendElement(str);
416       var str = Cc["@mozilla.org/supports-string;1"].
417                 createInstance(Ci.nsISupportsString);
418       str.data = Services.prefs.getCharPref(PREF_EM_NEW_ADDONS_LIST);
419       args.AppendElement(str);
420       const EMURL = "chrome://mozapps/content/extensions/extensions.xul";
421       const EMFEATURES = "chrome,menubar,extra-chrome,toolbar,dialog=no,resizable";
422       Services.ww.openWindow(null, EMURL, "_blank", EMFEATURES, args);
423       Services.prefs.clearUserPref(PREF_EM_NEW_ADDONS_LIST);
424     }
426     // Load the "more info" page for a locked places.sqlite
427     // This property is set earlier by places-database-locked topic.
428     if (this._isPlacesDatabaseLocked) {
429       this._showPlacesLockedNotificationBox();
430     }
432     // If there are plugins installed that are outdated, and the user hasn't
433     // been warned about them yet, open the plugins update page.
434     if (Services.prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER))
435       this._showPluginUpdatePage();
436   },
438   _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
439     // If user has already dismissed quit request, then do nothing
440     if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
441       return;
443     var windowcount = 0;
444     var pagecount = 0;
445     var browserEnum = Services.wm.getEnumerator("navigator:browser");
446     while (browserEnum.hasMoreElements()) {
447       windowcount++;
449       var browser = browserEnum.getNext();
450       var tabbrowser = browser.document.getElementById("content");
451       if (tabbrowser)
452         pagecount += tabbrowser.browsers.length - tabbrowser._numPinnedTabs;
453     }
455     this._saveSession = false;
456     if (pagecount < 2)
457       return;
459     if (aQuitType != "restart")
460       aQuitType = "quit";
462     var showPrompt = true;
463     try {
464       // browser.warnOnQuit is a hidden global boolean to override all quit prompts
465       // browser.warnOnRestart specifically covers app-initiated restarts where we restart the app
466       // browser.tabs.warnOnClose is the global "warn when closing multiple tabs" pref
468       var sessionWillBeSaved = Services.prefs.getIntPref("browser.startup.page") == 3 ||
469                                Services.prefs.getBoolPref("browser.sessionstore.resume_session_once");
470       if (sessionWillBeSaved || !Services.prefs.getBoolPref("browser.warnOnQuit"))
471         showPrompt = false;
472       else if (aQuitType == "restart")
473         showPrompt = Services.prefs.getBoolPref("browser.warnOnRestart");
474       else
475         showPrompt = Services.prefs.getBoolPref("browser.tabs.warnOnClose");
476     } catch (ex) {}
478     // Never show a prompt inside the private browsing mode
479     var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
480                             getService(Ci.nsIPrivateBrowsingService).
481                             privateBrowsingEnabled;
482     if (!showPrompt || inPrivateBrowsing)
483       return;
485     var quitBundle = Services.strings.createBundle("chrome://browser/locale/quitDialog.properties");
486     var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
488     var appName = brandBundle.GetStringFromName("brandShortName");
489     var quitDialogTitle = quitBundle.formatStringFromName(aQuitType + "DialogTitle",
490                                                           [appName], 1);
492     var message;
493     if (aQuitType == "restart")
494       message = quitBundle.formatStringFromName("messageRestart",
495                                                 [appName], 1);
496     else if (windowcount == 1)
497       message = quitBundle.formatStringFromName("messageNoWindows",
498                                                 [appName], 1);
499     else
500       message = quitBundle.formatStringFromName("message",
501                                                 [appName], 1);
503     var promptService = Services.prompt;
505     var flags = promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_0 +
506                 promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_1 +
507                 promptService.BUTTON_POS_0_DEFAULT;
509     var neverAsk = {value:false};
510     var button0Title, button2Title;
511     var button1Title = quitBundle.GetStringFromName("cancelTitle");
512     var neverAskText = quitBundle.GetStringFromName("neverAsk");
514     if (aQuitType == "restart")
515       button0Title = quitBundle.GetStringFromName("restartTitle");
516     else {
517       flags += promptService.BUTTON_TITLE_IS_STRING * promptService.BUTTON_POS_2;
518       button0Title = quitBundle.GetStringFromName("saveTitle");
519       button2Title = quitBundle.GetStringFromName("quitTitle");
520     }
522     var mostRecentBrowserWindow = Services.wm.getMostRecentWindow("navigator:browser");
523     var buttonChoice =
524       promptService.confirmEx(mostRecentBrowserWindow, quitDialogTitle, message,
525                               flags, button0Title, button1Title, button2Title,
526                               neverAskText, neverAsk);
528     switch (buttonChoice) {
529     case 2: // Quit
530       if (neverAsk.value)
531         Services.prefs.setBoolPref("browser.tabs.warnOnClose", false);
532       break;
533     case 1: // Cancel
534       aCancelQuit.QueryInterface(Ci.nsISupportsPRBool);
535       aCancelQuit.data = true;
536       break;
537     case 0: // Save & Quit
538       this._saveSession = true;
539       if (neverAsk.value) {
540         if (aQuitType == "restart")
541           Services.prefs.setBoolPref("browser.warnOnRestart", false);
542         else {
543           // always save state when shutting down
544           Services.prefs.setIntPref("browser.startup.page", 3);
545         }
546       }
547       break;
548     }
549   },
551   /*
552    * _shouldShowRights - Determines if the user should be shown the
553    * about:rights notification. The notification should *not* be shown if
554    * we've already shown the current version, or if the override pref says to
555    * never show it. The notification *should* be shown if it's never been seen
556    * before, if a newer version is available, or if the override pref says to
557    * always show it.
558    */
559   _shouldShowRights: function BG__shouldShowRights() {
560     // Look for an unconditional override pref. If set, do what it says.
561     // (true --> never show, false --> always show)
562     try {
563       return !Services.prefs.getBoolPref("browser.rights.override");
564     } catch (e) { }
565     // Ditto, for the legacy EULA pref.
566     try {
567       return !Services.prefs.getBoolPref("browser.EULA.override");
568     } catch (e) { }
570 #ifndef OFFICIAL_BUILD
571     // Non-official builds shouldn't shouldn't show the notification.
572     return false;
573 #endif
575     // Look to see if the user has seen the current version or not.
576     var currentVersion = Services.prefs.getIntPref("browser.rights.version");
577     try {
578       return !Services.prefs.getBoolPref("browser.rights." + currentVersion + ".shown");
579     } catch (e) { }
581     // Legacy: If the user accepted a EULA, we won't annoy them with the
582     // equivalent about:rights page until the version changes.
583     try {
584       return !Services.prefs.getBoolPref("browser.EULA." + currentVersion + ".accepted");
585     } catch (e) { }
587     // We haven't shown the notification before, so do so now.
588     return true;
589   },
591   _showRightsNotification: function BG__showRightsNotification() {
592     // Stick the notification onto the selected tab of the active browser window.
593     var win = this.getMostRecentBrowserWindow();
594     var browser = win.gBrowser; // for closure in notification bar callback
595     var notifyBox = browser.getNotificationBox();
597     var brandBundle  = Services.strings.createBundle("chrome://branding/locale/brand.properties");
598     var rightsBundle = Services.strings.createBundle("chrome://global/locale/aboutRights.properties");
600     var buttonLabel      = rightsBundle.GetStringFromName("buttonLabel");
601     var buttonAccessKey  = rightsBundle.GetStringFromName("buttonAccessKey");
602     var productName      = brandBundle.GetStringFromName("brandFullName");
603     var notifyRightsText = rightsBundle.formatStringFromName("notifyRightsText", [productName], 1);
605     var buttons = [
606                     {
607                       label:     buttonLabel,
608                       accessKey: buttonAccessKey,
609                       popup:     null,
610                       callback: function(aNotificationBar, aButton) {
611                         browser.selectedTab = browser.addTab("about:rights");
612                       }
613                     }
614                   ];
616     // Set pref to indicate we've shown the notification.
617     var currentVersion = Services.prefs.getIntPref("browser.rights.version");
618     Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
620     var box = notifyBox.appendNotification(notifyRightsText, "about-rights", null, notifyBox.PRIORITY_INFO_LOW, buttons);
621     box.persistence = 3; // arbitrary number, just so bar sticks around for a bit
622   },
624   _showUpdateNotification: function BG__showUpdateNotification() {
625     Services.prefs.clearUserPref("app.update.postupdate");
627     var um = Cc["@mozilla.org/updates/update-manager;1"].
628              getService(Ci.nsIUpdateManager);
629     try {
630       // If the updates.xml file is deleted then getUpdateAt will throw.
631       var update = um.getUpdateAt(0).QueryInterface(Ci.nsIPropertyBag);
632     }
633     catch (e) {
634       // This should never happen.
635       Cu.reportError("Unable to find update: " + e);
636       return;
637     }
639     var actions = update.getProperty("actions");
640     if (!actions || actions.indexOf("silent") != -1)
641       return;
643     var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
644                     getService(Ci.nsIURLFormatter);
645     var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
646     var brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
647     var appName = brandBundle.GetStringFromName("brandShortName");
649     function getNotifyString(aPropData) {
650       var propValue = update.getProperty(aPropData.propName);
651       if (!propValue) {
652         if (aPropData.prefName)
653           propValue = formatter.formatURLPref(aPropData.prefName);
654         else if (aPropData.stringParams)
655           propValue = browserBundle.formatStringFromName(aPropData.stringName,
656                                                          aPropData.stringParams,
657                                                          aPropData.stringParams.length);
658         else
659           propValue = browserBundle.GetStringFromName(aPropData.stringName);
660       }
661       return propValue;
662     }
664     if (actions.indexOf("showNotification") != -1) {
665       let text = getNotifyString({propName: "notificationText",
666                                   stringName: "puNotifyText",
667                                   stringParams: [appName]});
668       let url = getNotifyString({propName: "notificationURL",
669                                  prefName: "startup.homepage_override_url"});
670       let label = getNotifyString({propName: "notificationButtonLabel",
671                                    stringName: "pu.notifyButton.label"});
672       let key = getNotifyString({propName: "notificationButtonAccessKey",
673                                  stringName: "pu.notifyButton.accesskey"});
675       let win = this.getMostRecentBrowserWindow();
676       let browser = win.gBrowser; // for closure in notification bar callback
677       let notifyBox = browser.getNotificationBox();
679       let buttons = [
680                       {
681                         label:     label,
682                         accessKey: key,
683                         popup:     null,
684                         callback: function(aNotificationBar, aButton) {
685                           browser.selectedTab = browser.addTab(url);
686                         }
687                       }
688                     ];
690       let box = notifyBox.appendNotification(text, "post-update-notification",
691                                              null, notifyBox.PRIORITY_INFO_LOW,
692                                              buttons);
693       box.persistence = 3;
694     }
696     if (actions.indexOf("showAlert") == -1)
697       return;
699     let notifier;
700     try {
701       notifier = Cc["@mozilla.org/alerts-service;1"].
702                  getService(Ci.nsIAlertsService);
703     }
704     catch (e) {
705       // nsIAlertsService is not available for this platform
706       return;
707     }
709     let title = getNotifyString({propName: "alertTitle",
710                                  stringName: "puAlertTitle",
711                                  stringParams: [appName]});
712     let text = getNotifyString({propName: "alertText",
713                                 stringName: "puAlertText",
714                                 stringParams: [appName]});
715     let url = getNotifyString({propName: "alertURL",
716                                prefName: "startup.homepage_override_url"});
718     var self = this;
719     function clickCallback(subject, topic, data) {
720       // This callback will be called twice but only once with this topic
721       if (topic != "alertclickcallback")
722         return;
723       let win = self.getMostRecentBrowserWindow();
724       let browser = win.gBrowser;
725       browser.selectedTab = browser.addTab(data);
726     }
728     try {
729       // This will throw NS_ERROR_NOT_AVAILABLE if the notification cannot
730       // be displayed per the idl.
731       notifier.showAlertNotification("post-update-notification", title, text,
732                                      true, url, clickCallback);
733     }
734     catch (e) {
735     }
736   },
738   _showPluginUpdatePage: function BG__showPluginUpdatePage() {
739     Services.prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
741     var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
742                     getService(Ci.nsIURLFormatter);
743     var updateUrl = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
745     var win = this.getMostRecentBrowserWindow();
746     var browser = win.gBrowser;
747     browser.selectedTab = browser.addTab(updateUrl);
748   },
750   /**
751    * Initialize Places
752    * - imports the bookmarks html file if bookmarks database is empty, try to
753    *   restore bookmarks from a JSON backup if the backend indicates that the
754    *   database was corrupt.
755    *
756    * These prefs can be set up by the frontend:
757    *
758    * WARNING: setting these preferences to true will overwite existing bookmarks
759    *
760    * - browser.places.importBookmarksHTML
761    *   Set to true will import the bookmarks.html file from the profile folder.
762    * - browser.places.smartBookmarksVersion
763    *   Set during HTML import to indicate that Smart Bookmarks were created.
764    *   Set to -1 to disable Smart Bookmarks creation.
765    *   Set to 0 to restore current Smart Bookmarks.
766    * - browser.bookmarks.restore_default_bookmarks
767    *   Set to true by safe-mode dialog to indicate we must restore default
768    *   bookmarks.
769    */
770   _initPlaces: function BG__initPlaces() {
771     // We must instantiate the history service since it will tell us if we
772     // need to import or restore bookmarks due to first-run, corruption or
773     // forced migration (due to a major schema change).
774     // If the database is corrupt or has been newly created we should
775     // import bookmarks.
776     var dbStatus = PlacesUtils.history.databaseStatus;
777     var importBookmarks = dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE ||
778                           dbStatus == PlacesUtils.history.DATABASE_STATUS_CORRUPT;
780     if (dbStatus == PlacesUtils.history.DATABASE_STATUS_CREATE) {
781       // If the database has just been created, but we already have any
782       // bookmark, this is not the initial import.  This can happen after a
783       // migration from a different browser since migrators run before us.
784       // In such a case we should not import, unless some pref has been set.
785       if (PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarksMenuFolderId, 0) != -1 ||
786           PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.toolbarFolderId, 0) != -1)
787         importBookmarks = false;
788     }
790     // Check if user or an extension has required to import bookmarks.html
791     var importBookmarksHTML = false;
792     try {
793       importBookmarksHTML =
794         Services.prefs.getBoolPref("browser.places.importBookmarksHTML");
795       if (importBookmarksHTML)
796         importBookmarks = true;
797     } catch(ex) {}
799     // Check if Safe Mode or the user has required to restore bookmarks from
800     // default profile's bookmarks.html
801     var restoreDefaultBookmarks = false;
802     try {
803       restoreDefaultBookmarks =
804         Services.prefs.getBoolPref("browser.bookmarks.restore_default_bookmarks");
805       if (restoreDefaultBookmarks) {
806         // Ensure that we already have a bookmarks backup for today.
807         this._backupBookmarks();
808         importBookmarks = true;
809       }
810     } catch(ex) {}
812     // If the user did not require to restore default bookmarks, or import
813     // from bookmarks.html, we will try to restore from JSON
814     if (importBookmarks && !restoreDefaultBookmarks && !importBookmarksHTML) {
815       // get latest JSON backup
816       var bookmarksBackupFile = PlacesUtils.backups.getMostRecent("json");
817       if (bookmarksBackupFile) {
818         // restore from JSON backup
819         PlacesUtils.restoreBookmarksFromJSONFile(bookmarksBackupFile);
820         importBookmarks = false;
821       }
822       else {
823         // We have created a new database but we don't have any backup available
824         importBookmarks = true;
825         var dirService = Cc["@mozilla.org/file/directory_service;1"].
826                          getService(Ci.nsIProperties);
827         var bookmarksHTMLFile = dirService.get("BMarks", Ci.nsILocalFile);
828         if (bookmarksHTMLFile.exists()) {
829           // If bookmarks.html is available in current profile import it...
830           importBookmarksHTML = true;
831         }
832         else {
833           // ...otherwise we will restore defaults
834           restoreDefaultBookmarks = true;
835         }
836       }
837     }
839     // If bookmarks are not imported, then initialize smart bookmarks.  This
840     // happens during a common startup.
841     // Otherwise, if any kind of import runs, smart bookmarks creation should be
842     // delayed till the import operations has finished.  Not doing so would
843     // cause them to be overwritten by the newly imported bookmarks.
844     if (!importBookmarks) {
845       this.ensurePlacesDefaultQueriesInitialized();
846     }
847     else {
848       // An import operation is about to run.
849       // Don't try to recreate smart bookmarks if autoExportHTML is true or
850       // smart bookmarks are disabled.
851       var autoExportHTML = false;
852       try {
853         autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
854       } catch(ex) {}
855       var smartBookmarksVersion = 0;
856       try {
857         smartBookmarksVersion = Services.prefs.getIntPref("browser.places.smartBookmarksVersion");
858       } catch(ex) {}
859       if (!autoExportHTML && smartBookmarksVersion != -1)
860         Services.prefs.setIntPref("browser.places.smartBookmarksVersion", 0);
862       // Get bookmarks.html file location
863       var dirService = Cc["@mozilla.org/file/directory_service;1"].
864                        getService(Ci.nsIProperties);
866       var bookmarksURI = null;
867       if (restoreDefaultBookmarks) {
868         // User wants to restore bookmarks.html file from default profile folder
869         bookmarksURI = NetUtil.newURI("resource:///defaults/profile/bookmarks.html");
870       }
871       else {
872         var bookmarksFile = dirService.get("BMarks", Ci.nsILocalFile);
873         if (bookmarksFile.exists())
874           bookmarksURI = NetUtil.newURI(bookmarksFile);
875       }
877       if (bookmarksURI) {
878         // Add an import observer.  It will ensure that smart bookmarks are
879         // created once the operation is complete.
880         Services.obs.addObserver(this, "bookmarks-restore-success", false);
881         Services.obs.addObserver(this, "bookmarks-restore-failed", false);
883         // Import from bookmarks.html file.
884         try {
885           var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
886                          getService(Ci.nsIPlacesImportExportService);
887           importer.importHTMLFromURI(bookmarksURI, true /* overwrite existing */);
888         } catch (err) {
889           // Report the error, but ignore it.
890           Cu.reportError("Bookmarks.html file could be corrupt. " + err);
891           Services.obs.removeObserver(this, "bookmarks-restore-success");
892           Services.obs.removeObserver(this, "bookmarks-restore-failed");
893         }
894       }
895       else
896         Cu.reportError("Unable to find bookmarks.html file.");
898       // Reset preferences, so we won't try to import again at next run
899       if (importBookmarksHTML)
900         Services.prefs.setBoolPref("browser.places.importBookmarksHTML", false);
901       if (restoreDefaultBookmarks)
902         Services.prefs.setBoolPref("browser.bookmarks.restore_default_bookmarks",
903                                    false);
904     }
906     // Initialize bookmark archiving on idle.
907     // Once a day, either on idle or shutdown, bookmarks are backed up.
908     if (!this._isIdleObserver) {
909       this._idleService.addIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
910       this._isIdleObserver = true;
911     }
912   },
914   /**
915    * Places shut-down tasks
916    * - back up bookmarks if needed.
917    * - export bookmarks as HTML, if so configured.
918    *
919    * Note: quit-application-granted notification is received twice
920    *       so replace this method with a no-op when first called.
921    */
922   _shutdownPlaces: function BG__shutdownPlaces() {
923     if (this._isIdleObserver) {
924       this._idleService.removeIdleObserver(this, BOOKMARKS_BACKUP_IDLE_TIME);
925       this._isIdleObserver = false;
926     }
927     this._backupBookmarks();
929     // Backup bookmarks to bookmarks.html to support apps that depend
930     // on the legacy format.
931     var autoExportHTML = false;
932     try {
933       autoExportHTML = Services.prefs.getBoolPref("browser.bookmarks.autoExportHTML");
934     } catch(ex) { /* Don't export */ }
936     if (autoExportHTML) {
937       Cc["@mozilla.org/browser/places/import-export-service;1"].
938         getService(Ci.nsIPlacesImportExportService).
939         backupBookmarksFile();
940     }
941   },
943   /**
944    * Backup bookmarks if needed.
945    */
946   _backupBookmarks: function BG__backupBookmarks() {
947     let lastBackupFile = PlacesUtils.backups.getMostRecent();
949     // Backup bookmarks if there are no backups or the maximum interval between
950     // backups elapsed.
951     if (!lastBackupFile ||
952         new Date() - PlacesUtils.backups.getDateForFile(lastBackupFile) > BOOKMARKS_BACKUP_INTERVAL) {
953       let maxBackups = BOOKMARKS_BACKUP_MAX_BACKUPS;
954       try {
955         maxBackups = Services.prefs.getIntPref("browser.bookmarks.max_backups");
956       }
957       catch(ex) { /* Use default. */ }
959       PlacesUtils.backups.create(maxBackups); // Don't force creation.
960     }
961   },
963   /**
964    * Show the notificationBox for a locked places database.
965    */
966   _showPlacesLockedNotificationBox: function BG__showPlacesLockedNotificationBox() {
967     var brandBundle  = Services.strings.createBundle("chrome://branding/locale/brand.properties");
968     var applicationName = brandBundle.GetStringFromName("brandShortName");
969     var placesBundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
970     var title = placesBundle.GetStringFromName("lockPrompt.title");
971     var text = placesBundle.formatStringFromName("lockPrompt.text", [applicationName], 1);
972     var buttonText = placesBundle.GetStringFromName("lockPromptInfoButton.label");
973     var accessKey = placesBundle.GetStringFromName("lockPromptInfoButton.accessKey");
975     var helpTopic = "places-locked";
976     var url = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
977               getService(Components.interfaces.nsIURLFormatter).
978               formatURLPref("app.support.baseURL");
979     url += helpTopic;
981     var browser = this.getMostRecentBrowserWindow().gBrowser;
983     var buttons = [
984                     {
985                       label:     buttonText,
986                       accessKey: accessKey,
987                       popup:     null,
988                       callback:  function(aNotificationBar, aButton) {
989                         browser.selectedTab = browser.addTab(url);
990                       }
991                     }
992                   ];
994     var notifyBox = browser.getNotificationBox();
995     var box = notifyBox.appendNotification(text, title, null,
996                                            notifyBox.PRIORITY_CRITICAL_MEDIUM,
997                                            buttons);
998     box.persistence = -1; // Until user closes it
999   },
1001   _migrateUI: function BG__migrateUI() {
1002     const UI_VERSION = 5;
1003     const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
1004     let currentUIVersion = 0;
1005     try {
1006       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
1007     } catch(ex) {}
1008     if (currentUIVersion >= UI_VERSION)
1009       return;
1011     this._rdf = Cc["@mozilla.org/rdf/rdf-service;1"].getService(Ci.nsIRDFService);
1012     this._dataSource = this._rdf.GetDataSource("rdf:local-store");
1013     this._dirty = false;
1015     if (currentUIVersion < 1) {
1016       // this code should always migrate pre-FF3 profiles to the current UI state
1017       let currentsetResource = this._rdf.GetResource("currentset");
1018       let toolbars = ["nav-bar", "toolbar-menubar", "PersonalToolbar"];
1019       for (let i = 0; i < toolbars.length; i++) {
1020         let toolbar = this._rdf.GetResource(BROWSER_DOCURL + toolbars[i]);
1021         let currentset = this._getPersist(toolbar, currentsetResource);
1022         if (!currentset) {
1023           // toolbar isn't customized
1024           if (i == 0)
1025             // new button is in the defaultset, nothing to migrate
1026             break;
1027           continue;
1028         }
1029         if (/(?:^|,)unified-back-forward-button(?:$|,)/.test(currentset))
1030           // new button is already there, nothing to migrate
1031           break;
1032         if (/(?:^|,)back-button(?:$|,)/.test(currentset)) {
1033           let newset = currentset.replace(/(^|,)back-button($|,)/,
1034                                           "$1unified-back-forward-button,back-button$2")
1035           this._setPersist(toolbar, currentsetResource, newset);
1036           // done migrating
1037           break;
1038         }
1039       }
1040     }
1042     if (currentUIVersion < 2) {
1043       // This code adds the customizable bookmarks button.
1044       let currentsetResource = this._rdf.GetResource("currentset");
1045       let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
1046       let currentset = this._getPersist(toolbarResource, currentsetResource);
1047       // Need to migrate only if toolbar is customized and the element is not found.
1048       if (currentset &&
1049           currentset.indexOf("bookmarks-menu-button-container") == -1) {
1050         if (currentset.indexOf("fullscreenflex") != -1) {
1051           currentset = currentset.replace(/(^|,)fullscreenflex($|,)/,
1052                                           "$1bookmarks-menu-button-container,fullscreenflex$2")
1053         }
1054         else {
1055           currentset += ",bookmarks-menu-button-container";
1056         }
1057         this._setPersist(toolbarResource, currentsetResource, currentset);
1058       }
1059     }
1061     if (currentUIVersion < 3) {
1062       // This code merges the reload/stop/go button into the url bar.
1063       let currentsetResource = this._rdf.GetResource("currentset");
1064       let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
1065       let currentset = this._getPersist(toolbarResource, currentsetResource);
1066       // Need to migrate only if toolbar is customized and all 3 elements are found.
1067       if (currentset &&
1068           currentset.indexOf("reload-button") != -1 &&
1069           currentset.indexOf("stop-button") != -1 &&
1070           currentset.indexOf("urlbar-container") != -1 &&
1071           currentset.indexOf("urlbar-container,reload-button,stop-button") == -1) {
1072         currentset = currentset.replace(/(^|,)reload-button($|,)/, "$1$2")
1073                                .replace(/(^|,)stop-button($|,)/, "$1$2")
1074                                .replace(/(^|,)urlbar-container($|,)/,
1075                                         "$1urlbar-container,reload-button,stop-button$2");
1076         this._setPersist(toolbarResource, currentsetResource, currentset);
1077       }
1078     }
1080     if (currentUIVersion < 4) {
1081       // This code moves the home button to the immediate left of the bookmarks menu button.
1082       let currentsetResource = this._rdf.GetResource("currentset");
1083       let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
1084       let currentset = this._getPersist(toolbarResource, currentsetResource);
1085       // Need to migrate only if toolbar is customized and the elements are found.
1086       if (currentset &&
1087           currentset.indexOf("home-button") != -1 &&
1088           currentset.indexOf("bookmarks-menu-button-container") != -1) {
1089         currentset = currentset.replace(/(^|,)home-button($|,)/, "$1$2")
1090                                .replace(/(^|,)bookmarks-menu-button-container($|,)/,
1091                                         "$1home-button,bookmarks-menu-button-container$2");
1092         this._setPersist(toolbarResource, currentsetResource, currentset);
1093       }
1094     }
1096     if (currentUIVersion < 5) {
1097       // This code uncollapses PersonalToolbar if its collapsed status is not
1098       // persisted, and user customized it or changed default bookmarks.
1099       let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "PersonalToolbar");
1100       let collapsedResource = this._rdf.GetResource("collapsed");
1101       let collapsed = this._getPersist(toolbarResource, collapsedResource);
1102       // If the user does not have a persisted value for the toolbar's
1103       // "collapsed" attribute, try to determine whether it's customized.
1104       if (collapsed === null) {
1105         // We consider the toolbar customized if it has more than
1106         // 3 children, or if it has a persisted currentset value.
1107         let currentsetResource = this._rdf.GetResource("currentset");
1108         let toolbarIsCustomized = !!this._getPersist(toolbarResource,
1109                                                      currentsetResource);
1110         function getToolbarFolderCount() {
1111           let toolbarFolder =
1112             PlacesUtils.getFolderContents(PlacesUtils.toolbarFolderId).root;
1113           let toolbarChildCount = toolbarFolder.childCount;
1114           toolbarFolder.containerOpen = false;
1115           return toolbarChildCount;
1116         }
1118         if (toolbarIsCustomized || getToolbarFolderCount() > 3) {
1119           this._setPersist(toolbarResource, collapsedResource, "false");
1120         }
1121       }
1122     }
1124     if (this._dirty)
1125       this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
1127     delete this._rdf;
1128     delete this._dataSource;
1130     // Update the migration version.
1131     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
1132   },
1134   _getPersist: function BG__getPersist(aSource, aProperty) {
1135     var target = this._dataSource.GetTarget(aSource, aProperty, true);
1136     if (target instanceof Ci.nsIRDFLiteral)
1137       return target.Value;
1138     return null;
1139   },
1141   _setPersist: function BG__setPersist(aSource, aProperty, aTarget) {
1142     this._dirty = true;
1143     try {
1144       var oldTarget = this._dataSource.GetTarget(aSource, aProperty, true);
1145       if (oldTarget) {
1146         if (aTarget)
1147           this._dataSource.Change(aSource, aProperty, oldTarget, this._rdf.GetLiteral(aTarget));
1148         else
1149           this._dataSource.Unassert(aSource, aProperty, oldTarget);
1150       }
1151       else {
1152         this._dataSource.Assert(aSource, aProperty, this._rdf.GetLiteral(aTarget), true);
1153       }
1155       // Add the entry to the persisted set for this document if it's not there.
1156       // This code is mostly borrowed from nsXULDocument::Persist.
1157       let docURL = aSource.ValueUTF8.split("#")[0];
1158       let docResource = this._rdf.GetResource(docURL);
1159       let persistResource = this._rdf.GetResource("http://home.netscape.com/NC-rdf#persist");
1160       if (!this._dataSource.HasAssertion(docResource, persistResource, aSource, true)) {
1161         this._dataSource.Assert(docResource, persistResource, aSource, true);
1162       }
1163     }
1164     catch(ex) {}
1165   },
1167   // ------------------------------
1168   // public nsIBrowserGlue members
1169   // ------------------------------
1171   sanitize: function BG_sanitize(aParentWindow) {
1172     this._sanitizer.sanitize(aParentWindow);
1173   },
1175   ensurePlacesDefaultQueriesInitialized:
1176   function BG_ensurePlacesDefaultQueriesInitialized() {
1177     // This is actual version of the smart bookmarks, must be increased every
1178     // time smart bookmarks change.
1179     // When adding a new smart bookmark below, its newInVersion property must
1180     // be set to the version it has been added in, we will compare its value
1181     // to users' smartBookmarksVersion and add new smart bookmarks without
1182     // recreating old deleted ones.
1183     const SMART_BOOKMARKS_VERSION = 2;
1184     const SMART_BOOKMARKS_ANNO = "Places/SmartBookmark";
1185     const SMART_BOOKMARKS_PREF = "browser.places.smartBookmarksVersion";
1187     // TODO bug 399268: should this be a pref?
1188     const MAX_RESULTS = 10;
1190     // Get current smart bookmarks version.  If not set, create them.
1191     let smartBookmarksCurrentVersion = 0;
1192     try {
1193       smartBookmarksCurrentVersion = Services.prefs.getIntPref(SMART_BOOKMARKS_PREF);
1194     } catch(ex) {}
1196     // If version is current or smart bookmarks are disabled, just bail out.
1197     if (smartBookmarksCurrentVersion == -1 ||
1198         smartBookmarksCurrentVersion >= SMART_BOOKMARKS_VERSION) {
1199       return;
1200     }
1202     let batch = {
1203       runBatched: function BG_EPDQI_runBatched() {
1204         let menuIndex = 0;
1205         let toolbarIndex = 0;
1206         let bundle = Services.strings.createBundle("chrome://browser/locale/places/places.properties");
1208         let smartBookmarks = {
1209           MostVisited: {
1210             title: bundle.GetStringFromName("mostVisitedTitle"),
1211             uri: NetUtil.newURI("place:redirectsMode=" +
1212                                 Ci.nsINavHistoryQueryOptions.REDIRECTS_MODE_TARGET +
1213                                 "&sort=" +
1214                                 Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING +
1215                                 "&maxResults=" + MAX_RESULTS),
1216             parent: PlacesUtils.toolbarFolderId,
1217             position: toolbarIndex++,
1218             newInVersion: 1
1219           },
1220           RecentlyBookmarked: {
1221             title: bundle.GetStringFromName("recentlyBookmarkedTitle"),
1222             uri: NetUtil.newURI("place:folder=BOOKMARKS_MENU" +
1223                                 "&folder=UNFILED_BOOKMARKS" +
1224                                 "&folder=TOOLBAR" +
1225                                 "&queryType=" +
1226                                 Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS +
1227                                 "&sort=" +
1228                                 Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING +
1229                                 "&excludeItemIfParentHasAnnotation=livemark%2FfeedURI" +
1230                                 "&maxResults=" + MAX_RESULTS +
1231                                 "&excludeQueries=1"),
1232             parent: PlacesUtils.bookmarksMenuFolderId,
1233             position: menuIndex++,
1234             newInVersion: 1
1235           },
1236           RecentTags: {
1237             title: bundle.GetStringFromName("recentTagsTitle"),
1238             uri: NetUtil.newURI("place:"+
1239                                 "type=" +
1240                                 Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY +
1241                                 "&sort=" +
1242                                 Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING +
1243                                 "&maxResults=" + MAX_RESULTS),
1244             parent: PlacesUtils.bookmarksMenuFolderId,
1245             position: menuIndex++,
1246             newInVersion: 1
1247           },
1248         };
1250         // Set current itemId, parent and position if Smart Bookmark exists,
1251         // we will use these informations to create the new version at the same
1252         // position.
1253         let smartBookmarkItemIds = PlacesUtils.annotations.getItemsWithAnnotation(SMART_BOOKMARKS_ANNO);
1254         smartBookmarkItemIds.forEach(function (itemId) {
1255           let queryId = PlacesUtils.annotations.getItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
1256           if (queryId in smartBookmarks) {
1257             let smartBookmark = smartBookmarks[queryId];
1258             smartBookmarks[queryId].itemId = itemId;
1259             smartBookmarks[queryId].parent = PlacesUtils.bookmarks.getFolderIdForItem(itemId);
1260             smartBookmarks[queryId].position = PlacesUtils.bookmarks.getItemIndex(itemId);
1261           }
1262           else {
1263             // We don't remove old Smart Bookmarks because user could still
1264             // find them useful, or could have personalized them.
1265             // Instead we remove the Smart Bookmark annotation.
1266             PlacesUtils.annotations.removeItemAnnotation(itemId, SMART_BOOKMARKS_ANNO);
1267           }
1268         });
1270         for (let queryId in smartBookmarks) {
1271           let smartBookmark = smartBookmarks[queryId];
1273           // We update or create only changed or new smart bookmarks.
1274           // Also we respect user choices, so we won't try to create a smart
1275           // bookmark if it has been removed.
1276           if (smartBookmarksCurrentVersion > 0 &&
1277               smartBookmark.newInVersion <= smartBookmarksCurrentVersion &&
1278               !smartBookmark.itemId)
1279             continue;
1281           // Remove old version of the smart bookmark if it exists, since it
1282           // will be replaced in place.
1283           if (smartBookmark.itemId) {
1284             PlacesUtils.bookmarks.removeItem(smartBookmark.itemId);
1285           }
1287           // Create the new smart bookmark and store its updated itemId.
1288           smartBookmark.itemId =
1289             PlacesUtils.bookmarks.insertBookmark(smartBookmark.parent,
1290                                                  smartBookmark.uri,
1291                                                  smartBookmark.position,
1292                                                  smartBookmark.title);
1293           PlacesUtils.annotations.setItemAnnotation(smartBookmark.itemId,
1294                                                     SMART_BOOKMARKS_ANNO,
1295                                                     queryId, 0,
1296                                                     PlacesUtils.annotations.EXPIRE_NEVER);
1297         }
1299         // If we are creating all Smart Bookmarks from ground up, add a
1300         // separator below them in the bookmarks menu.
1301         if (smartBookmarksCurrentVersion == 0 &&
1302             smartBookmarkItemIds.length == 0) {
1303           let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.bookmarksMenuFolderId,
1304                                                         menuIndex);
1305           // Don't add a separator if the menu was empty or there is one already.
1306           if (id != -1 &&
1307               PlacesUtils.bookmarks.getItemType(id) != PlacesUtils.bookmarks.TYPE_SEPARATOR) {
1308             PlacesUtils.bookmarks.insertSeparator(PlacesUtils.bookmarksMenuFolderId,
1309                                                   menuIndex);
1310           }
1311         }
1312       }
1313     };
1315     try {
1316       PlacesUtils.bookmarks.runInBatchMode(batch, null);
1317     }
1318     catch(ex) {
1319       Components.utils.reportError(ex);
1320     }
1321     finally {
1322       Services.prefs.setIntPref(SMART_BOOKMARKS_PREF, SMART_BOOKMARKS_VERSION);
1323       Services.prefs.savePrefFile(null);
1324     }
1325   },
1327 #ifndef XP_WIN
1328 #define BROKEN_WM_Z_ORDER
1329 #endif
1331   // this returns the most recent non-popup browser window
1332   getMostRecentBrowserWindow: function BG_getMostRecentBrowserWindow() {
1333     function isFullBrowserWindow(win) {
1334       return !win.closed &&
1335              !win.document.documentElement.getAttribute("chromehidden");
1336     }
1338 #ifdef BROKEN_WM_Z_ORDER
1339     var win = Services.wm.getMostRecentWindow("navigator:browser");
1341     // if we're lucky, this isn't a popup, and we can just return this
1342     if (win && !isFullBrowserWindow(win)) {
1343       win = null;
1344       let windowList = Services.wm.getEnumerator("navigator:browser");
1345       // this is oldest to newest, so this gets a bit ugly
1346       while (windowList.hasMoreElements()) {
1347         let nextWin = windowList.getNext();
1348         if (isFullBrowserWindow(nextWin))
1349           win = nextWin;
1350       }
1351     }
1352     return win;
1353 #else
1354     var windowList = Services.wm.getZOrderDOMWindowEnumerator("navigator:browser", true);
1355     while (windowList.hasMoreElements()) {
1356       let win = windowList.getNext();
1357       if (isFullBrowserWindow(win))
1358         return win;
1359     }
1360     return null;
1361 #endif
1362   },
1365   // for XPCOM
1366   classID:          Components.ID("{eab9012e-5f74-4cbc-b2b5-a590235513cc}"),
1368   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
1369                                          Ci.nsISupportsWeakReference,
1370                                          Ci.nsIBrowserGlue]),
1372   // redefine the default factory for XPCOMUtils
1373   _xpcom_factory: BrowserGlueServiceFactory,
1376 function ContentPermissionPrompt() {}
1378 ContentPermissionPrompt.prototype = {
1379   classID:          Components.ID("{d8903bf6-68d5-4e97-bcd1-e4d3012f721a}"),
1381   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
1383   prompt: function CPP_prompt(request) {
1385     if (request.type != "geolocation") {
1386         return;
1387     }
1389     var requestingURI = request.uri;
1391     // Ignore requests from non-nsIStandardURLs
1392     if (!(requestingURI instanceof Ci.nsIStandardURL))
1393       return;
1395     var result = Services.perms.testExactPermission(requestingURI, "geo");
1397     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
1398       request.allow();
1399       return;
1400     }
1402     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
1403       request.cancel();
1404       return;
1405     }
1407     function getChromeWindow(aWindow) {
1408       var chromeWin = aWindow 
1409         .QueryInterface(Ci.nsIInterfaceRequestor)
1410         .getInterface(Ci.nsIWebNavigation)
1411         .QueryInterface(Ci.nsIDocShellTreeItem)
1412         .rootTreeItem
1413         .QueryInterface(Ci.nsIInterfaceRequestor)
1414         .getInterface(Ci.nsIDOMWindow)
1415         .QueryInterface(Ci.nsIDOMChromeWindow);
1416       return chromeWin;
1417     }
1419     var browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
1421     var mainAction = {
1422       label: browserBundle.GetStringFromName("geolocation.shareLocation"),
1423       accessKey: browserBundle.GetStringFromName("geolocation.shareLocation.accesskey"),
1424       callback: function(notification) {
1425         request.allow();
1426       },
1427     };
1429     // XXX Bug 573536
1430     // browserBundle.GetStringFromName("geolocation.learnMore")
1431     //var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].getService(Ci.nsIURLFormatter);
1432     //link.href = formatter.formatURLPref("browser.geolocation.warning.infoURL");
1434     var message;
1435     var secondaryActions = [];
1437     // Different message/options if it is a local file
1438     if (requestingURI.schemeIs("file")) {
1439       message = browserBundle.formatStringFromName("geolocation.fileWantsToKnow",
1440                                                    [requestingURI.path], 1);
1441     } else {
1442       message = browserBundle.formatStringFromName("geolocation.siteWantsToKnow",
1443                                                    [requestingURI.host], 1);
1445       // Don't offer to "always/never share" in PB mode
1446       var inPrivateBrowsing = Cc["@mozilla.org/privatebrowsing;1"].
1447                               getService(Ci.nsIPrivateBrowsingService).
1448                               privateBrowsingEnabled;
1450       if (!inPrivateBrowsing) {
1451         secondaryActions.push({
1452           label: browserBundle.GetStringFromName("geolocation.alwaysShare"),
1453           accessKey: browserBundle.GetStringFromName("geolocation.alwaysShare.accesskey"),
1454           callback: function () {
1455             Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.ALLOW_ACTION);
1456             request.allow();
1457           }
1458         });
1459         secondaryActions.push({
1460           label: browserBundle.GetStringFromName("geolocation.neverShare"),
1461           accessKey: browserBundle.GetStringFromName("geolocation.neverShare.accesskey"),
1462           callback: function () {
1463             Services.perms.add(requestingURI, "geo", Ci.nsIPermissionManager.DENY_ACTION);
1464             request.cancel();
1465           }
1466         });
1467       }
1468     }
1470     var requestingWindow = request.window.top;
1471     var chromeWin = getChromeWindow(requestingWindow).wrappedJSObject;
1472     var browser = chromeWin.gBrowser.getBrowserForDocument(requestingWindow.document);
1474     chromeWin.PopupNotifications.show(browser, "geolocation", message, "geo-notification-icon",
1475                                       mainAction, secondaryActions);
1476   }
1479 var components = [BrowserGlue, ContentPermissionPrompt];
1480 var NSGetFactory = XPCOMUtils.generateNSGetFactory(components);