diff --git a/browser/components/distribution.js b/browser/components/distribution.js index 722abe215f3..be0e1867067 100644 --- a/browser/components/distribution.js +++ b/browser/components/distribution.js @@ -19,6 +19,7 @@ * * Contributor(s): * Dan Mills + * Marco Bonardo * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or @@ -41,87 +42,94 @@ const Cc = Components.classes; const Cr = Components.results; const Cu = Components.utils; +const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = + "distribution-customization-complete"; + function DistributionCustomizer() { - this._distroDir = this._dirSvc.get("XCurProcD", Ci.nsIFile); - this._distroDir.append("distribution"); - - let iniFile = this._distroDir.clone(); + let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. + getService(Ci.nsIProperties); + let iniFile = dirSvc.get("XCurProcD", Ci.nsIFile); + iniFile.append("distribution"); iniFile.append("distribution.ini"); - this._iniExists = iniFile.exists(); - - if (!this._iniExists) - return; - - this._ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. - getService(Ci.nsIINIParserFactory).createINIParser(iniFile); - - this._prefs = this._prefSvc.getBranch(null); - this._locale = this._prefs.getCharPref("general.useragent.locale"); - + if (iniFile.exists()) + this._iniFile = iniFile; } + DistributionCustomizer.prototype = { - __bmSvc: null, + _iniFile: null, + + get _ini() { + let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]. + getService(Ci.nsIINIParserFactory). + createINIParser(this._iniFile); + this.__defineGetter__("_ini", function() ini); + return this._ini; + }, + + get _locale() { + let locale; + try { + locale = this._prefs.getCharPref("general.useragent.locale"); + } + catch (e) { + locale = "en-US"; + } + this.__defineGetter__("_locale", function() locale); + return this._locale; + }, + get _bmSvc() { - if (!this.__bmSvc) - this.__bmSvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. - getService(Ci.nsINavBookmarksService); - return this.__bmSvc; + let svc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + this.__defineGetter__("_bmSvc", function() svc); + return this._bmSvc; }, - __annoSvc: null, get _annoSvc() { - if (!this.__annoSvc) - this.__annoSvc = Cc["@mozilla.org/browser/annotation-service;1"]. - getService(Ci.nsIAnnotationService); - return this.__annoSvc; + let svc = Cc["@mozilla.org/browser/annotation-service;1"]. + getService(Ci.nsIAnnotationService); + this.__defineGetter__("_annoSvc", function() svc); + return this._annoSvc; }, - __livemarkSvc: null, get _livemarkSvc() { - if (!this.__livemarkSvc) - this.__livemarkSvc = Cc["@mozilla.org/browser/livemark-service;2"]. - getService(Ci.nsILivemarkService); - return this.__livemarkSvc; + let svc = Cc["@mozilla.org/browser/livemark-service;2"]. + getService(Ci.nsILivemarkService); + this.__defineGetter__("_livemarkSvc", function() svc); + return this._livemarkSvc; }, - __dirSvc: null, - get _dirSvc() { - if (!this.__dirSvc) - this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - return this.__dirSvc; - }, - - __prefSvc: null, get _prefSvc() { - if (!this.__prefSvc) - this.__prefSvc = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefService); - return this.__prefSvc; + let svc = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefService); + this.__defineGetter__("_prefSvc", function() svc); + return this._prefSvc; }, - __iosvc: null, - get _iosvc() { - if (!this.__iosvc) - this.__iosvc = Cc["@mozilla.org/network/io-service;1"]. - getService(Ci.nsIIOService); - return this.__iosvc; + get _prefs() { + let branch = this._prefSvc.getBranch(null); + this.__defineGetter__("_prefs", function() branch); + return this._prefs; }, - _locale: "en-US", - _distroDir: null, - _iniExists: false, - _ini: null, - + get _ioSvc() { + let svc = Cc["@mozilla.org/network/io-service;1"]. + getService(Ci.nsIIOService); + this.__defineGetter__("_ioSvc", function() svc); + return this._ioSvc; + }, _makeURI: function DIST__makeURI(spec) { - return this._iosvc.newURI(spec, null, null); + return this._ioSvc.newURI(spec, null, null); }, - _parseBookmarksSection: function DIST_parseBookmarksSection(parentId, section) { + + _parseBookmarksSection: + function DIST_parseBookmarksSection(parentId, section) { let keys = []; for (let i in enumerate(this._ini.getKeys(section))) keys.push(i); keys.sort(); + let items = {}; let defaultItemId = -1; let maxItemId = -1; @@ -158,7 +166,7 @@ DistributionCustomizer.prototype = { if (!items[iid]) continue; - let index = -1; + let index = this._bmSvc.DEFAULT_INDEX; let newId; switch (items[iid]["type"]) { @@ -175,7 +183,8 @@ DistributionCustomizer.prototype = { items[iid]["folderId"]); if (items[iid]["description"]) - this._annoSvc.setItemAnnotation(newId, "bookmarkProperties/description", + this._annoSvc.setItemAnnotation(newId, + "bookmarkProperties/description", items[iid]["description"], 0, this._annoSvc.EXPIRE_NEVER); @@ -191,12 +200,13 @@ DistributionCustomizer.prototype = { if (iid < defaultItemId) index = prependIndex++; + // Don't bother updating the livemark contents on creation. newId = this._livemarkSvc. - createLivemark(parentId, - items[iid]["title"], - this._makeURI(items[iid]["siteLink"]), - this._makeURI(items[iid]["feedLink"]), - index); + createLivemarkFolderOnly(parentId, + items[iid]["title"], + this._makeURI(items[iid]["siteLink"]), + this._makeURI(items[iid]["feedLink"]), + index); break; case "bookmark": @@ -209,48 +219,61 @@ DistributionCustomizer.prototype = { index, items[iid]["title"]); if (items[iid]["description"]) - this._annoSvc.setItemAnnotation(newId, "bookmarkProperties/description", + this._annoSvc.setItemAnnotation(newId, + "bookmarkProperties/description", items[iid]["description"], 0, this._annoSvc.EXPIRE_NEVER); break; } } + return this._checkCustomizationComplete(); }, + + _customizationsApplied: false, applyCustomizations: function DIST_applyCustomizations() { - if (!this._iniExists) - return; + this._customizationsApplied = true; + if (!this._iniFile) + return this._checkCustomizationComplete(); // nsPrefService loads very early. Reload prefs so we can set // distribution defaults during the prefservice:after-app-defaults // notification (see applyPrefDefaults below) this._prefSvc.QueryInterface(Ci.nsIObserver); this._prefSvc.observe(null, "reload-default-prefs", null); + }, + + _bookmarksApplied: false, + applyBookmarks: function DIST_applyBookarks() { + this._bookmarksApplied = true; + if (!this._iniFile) + return this._checkCustomizationComplete(); let sections = enumToObject(this._ini.getSections()); // The global section, and several of its fields, is required // (we also check here to be consistent with applyPrefDefaults below) if (!sections["Global"]) - return; + return this._checkCustomizationComplete(); let globalPrefs = enumToObject(this._ini.getKeys("Global")); if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) - return; + return this._checkCustomizationComplete(); - let bmProcessed = false; let bmProcessedPref; - try { - bmProcessedPref = this._ini.getString("Global", - "bookmarks.initialized.pref"); - } catch (e) { + bmProcessedPref = this._ini.getString("Global", + "bookmarks.initialized.pref"); + } + catch (e) { bmProcessedPref = "distribution." + this._ini.getString("Global", "id") + ".bookmarksProcessed"; } + let bmProcessed = false; try { bmProcessed = this._prefs.getBoolPref(bmProcessedPref); - } catch (e) {} + } + catch (e) {} if (!bmProcessed) { if (sections["BookmarksMenu"]) @@ -261,19 +284,23 @@ DistributionCustomizer.prototype = { "BookmarksToolbar"); this._prefs.setBoolPref(bmProcessedPref, true); } + return this._checkCustomizationComplete(); }, + + _prefDefaultsApplied: false, applyPrefDefaults: function DIST_applyPrefDefaults() { - if (!this._iniExists) - return; + this._prefDefaultsApplied = true; + if (!this._iniFile) + return this._checkCustomizationComplete(); let sections = enumToObject(this._ini.getSections()); // The global section, and several of its fields, is required if (!sections["Global"]) - return; + return this._checkCustomizationComplete(); let globalPrefs = enumToObject(this._ini.getKeys("Global")); if (!(globalPrefs["id"] && globalPrefs["version"] && globalPrefs["about"])) - return; + return this._checkCustomizationComplete(); let defaults = this._prefSvc.getDefaultBranch(null); @@ -343,6 +370,18 @@ DistributionCustomizer.prototype = { } catch (e) { /* ignore bad prefs and move on */ } } } + + return this._checkCustomizationComplete(); + }, + + _checkCustomizationComplete: function DIST__checkCustomizationComplete() { + let prefDefaultsApplied = this._prefDefaultsApplied || !this._iniFile; + if (this._customizationsApplied && this._bookmarksApplied && + prefDefaultsApplied) { + let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null); + } } }; diff --git a/browser/components/nsBrowserGlue.js b/browser/components/nsBrowserGlue.js index 14f0aeca35d..4285bd7cdbe 100644 --- a/browser/components/nsBrowserGlue.js +++ b/browser/components/nsBrowserGlue.js @@ -95,6 +95,10 @@ function BrowserGlue() { "@mozilla.org/observer-service;1", "nsIObserverService"); + XPCOMUtils.defineLazyGetter(this, "_distributionCustomizer", function() { + return new DistributionCustomizer(); + }); + this._init(); } @@ -180,6 +184,10 @@ BrowserGlue.prototype = { // no longer needed, since history was initialized completely. this._observerService.removeObserver(this, "places-database-locked"); this._isPlacesLockedObserver = false; + + // Now apply distribution customized bookmarks. + // This should always run after Places initialization. + this._distributionCustomizer.applyBookmarks(); break; case "places-database-locked": this._isPlacesDatabaseLocked = true; @@ -192,6 +200,10 @@ BrowserGlue.prototype = { if (this._idleService.idleTime > BOOKMARKS_BACKUP_IDLE_TIME * 1000) this._backupBookmarks(); break; + case "distribution-customization-complete": + // Customization has finished, we don't need the customizer anymore. + delete this._distributionCustomizer; + break; } }, @@ -216,6 +228,7 @@ BrowserGlue.prototype = { this._isPlacesInitObserver = true; osvr.addObserver(this, "places-database-locked", false); this._isPlacesLockedObserver = true; + osvr.addObserver(this, "distribution-customization-complete", false); }, // cleanup (called on application shutdown) @@ -247,8 +260,7 @@ BrowserGlue.prototype = { { // apply distribution customizations (prefs) // other customizations are applied in _onProfileStartup() - var distro = new DistributionCustomizer(); - distro.applyPrefDefaults(); + this._distributionCustomizer.applyPrefDefaults(); }, // profile startup handler (contains profile initialization routines) @@ -267,8 +279,7 @@ BrowserGlue.prototype = { // apply distribution customizations // prefs are applied in _onAppDefaults() - var distro = new DistributionCustomizer(); - distro.applyCustomizations(); + this._distributionCustomizer.applyCustomizations(); // handle any UI migration this._migrateUI(); diff --git a/browser/components/places/tests/unit/distribution.ini b/browser/components/places/tests/unit/distribution.ini new file mode 100644 index 00000000000..f94a1be3c53 --- /dev/null +++ b/browser/components/places/tests/unit/distribution.ini @@ -0,0 +1,21 @@ +# Distribution Configuration File +# Bug 516444 demo + +[Global] +id=516444 +version=1.0 +about=Test distribution file + +[BookmarksToolbar] +item.1.title=Toolbar Link Before +item.1.link=http://mozilla.com/ +item.2.type=default +item.3.title=Toolbar Link After +item.3.link=http://mozilla.com/ + +[BookmarksMenu] +item.1.title=Menu Link Before +item.1.link=http://mozilla.com/ +item.2.type=default +item.3.title=Menu Link After +item.3.link=http://mozilla.com/ \ No newline at end of file diff --git a/browser/components/places/tests/unit/test_browserGlue_distribution.js b/browser/components/places/tests/unit/test_browserGlue_distribution.js new file mode 100644 index 00000000000..fbc1b445285 --- /dev/null +++ b/browser/components/places/tests/unit/test_browserGlue_distribution.js @@ -0,0 +1,141 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Places Unit Test code. + * + * The Initial Developer of the Original Code is Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2009 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Marco Bonardo + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +/** + * Tests that nsBrowserGlue does not overwrite bookmarks imported from the + * migrators. They usually run before nsBrowserGlue, so if we find any + * bookmark on init, we should not try to import. + */ + +const PREF_SMART_BOOKMARKS_VERSION = "browser.places.smartBookmarksVersion"; +const PREF_BMPROCESSED = "distribution.516444.bookmarksProcessed"; +const PREF_DISTRIBUTION_ID = "distribution.id"; + +const TOPIC_FINAL_UI_STARTUP = "final-ui-startup"; +const TOPIC_PLACES_INIT_COMPLETE = "places-init-complete"; +const TOPIC_CUSTOMIZATION_COMPLETE = "distribution-customization-complete"; + +let os = Cc["@mozilla.org/observer-service;1"]. + getService(Ci.nsIObserverService); + +let observer = { + observe: function(aSubject, aTopic, aData) { + if (aTopic == TOPIC_CUSTOMIZATION_COMPLETE) + do_timeout(0, "continue_test();"); + } +} +os.addObserver(observer, TOPIC_CUSTOMIZATION_COMPLETE, false) + +function run_test() { + // Copy distribution.ini file to our app dir. + let distroDir = dirSvc.get("XCurProcD", Ci.nsIFile); + distroDir.append("distribution"); + let iniFile = distroDir.clone(); + iniFile.append("distribution.ini"); + if (iniFile.exists()) { + iniFile.remove(false); + print("distribution.ini already exists, did some test forget to cleanup?"); + } + + let testDistributionFile = gTestDir.clone(); + testDistributionFile.append("distribution.ini"); + testDistributionFile.copyTo(distroDir, "distribution.ini"); + do_check_true(testDistributionFile.exists()); + + // Disable Smart Bookmarks creation. + let ps = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + ps.setIntPref(PREF_SMART_BOOKMARKS_VERSION, -1); + + // Initialize Places through the History Service, so it won't trigger + // browserGlue::_initPlaces since browserGlue is not yet in context. + let hs = Cc["@mozilla.org/browser/nav-history-service;1"]. + getService(Ci.nsINavHistoryService); + // Check a new database has been created. + // nsBrowserGlue will use databaseStatus to manage initialization. + do_check_eq(hs.databaseStatus, hs.DATABASE_STATUS_CREATE); + + // Initialize nsBrowserGlue. + Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIBrowserGlue); + + // Places initialization has already happened, so we need to simulate a new + // one. This will force browserGlue::_initPlaces(). + os.notifyObservers(null, TOPIC_FINAL_UI_STARTUP, null); + os.notifyObservers(null, TOPIC_PLACES_INIT_COMPLETE, null); + + do_test_pending(); + // Test will continue on customization complete notification. +} + +function continue_test() { + let bs = Cc["@mozilla.org/browser/nav-bookmarks-service;1"]. + getService(Ci.nsINavBookmarksService); + + // Check the custom bookmarks exist on menu. + let menuItemId = bs.getIdForItemAt(bs.bookmarksMenuFolder, 0); + do_check_neq(menuItemId, -1); + do_check_eq(bs.getItemTitle(menuItemId), "Menu Link Before"); + menuItemId = bs.getIdForItemAt(bs.bookmarksMenuFolder, 1 + DEFAULT_BOOKMARKS_ON_MENU); + do_check_neq(menuItemId, -1); + do_check_eq(bs.getItemTitle(menuItemId), "Menu Link After"); + + // Check the custom bookmarks exist on toolbar. + let toolbarItemId = bs.getIdForItemAt(bs.toolbarFolder, 0); + do_check_neq(toolbarItemId, -1); + do_check_eq(bs.getItemTitle(toolbarItemId), "Toolbar Link Before"); + toolbarItemId = bs.getIdForItemAt(bs.toolbarFolder, 1 + DEFAULT_BOOKMARKS_ON_TOOLBAR); + do_check_neq(toolbarItemId, -1); + do_check_eq(bs.getItemTitle(toolbarItemId), "Toolbar Link After"); + + // Check the bmprocessed pref has been created. + let ps = Cc["@mozilla.org/preferences-service;1"]. + getService(Ci.nsIPrefBranch); + do_check_true(ps.getBoolPref(PREF_BMPROCESSED)); + + // Check distribution prefs have been created. + do_check_eq(ps.getCharPref(PREF_DISTRIBUTION_ID), "516444"); + + // Remove the distribution file. + let iniFile = dirSvc.get("XCurProcD", Ci.nsIFile); + iniFile.append("distribution"); + iniFile.append("distribution.ini"); + iniFile.remove(false); + do_check_false(iniFile.exists()); + + do_test_finished(); +}