Bug 559992. Implement a caching mechanism for the contentprefs service and use it to retrieve page-specific zoom values, avoiding zoom flicker with back/forward navigation. r=gavin sr=vlad a=blocking-betaN
authorFelipe Gomes <felipc@gmail.com>
Tue, 28 Dec 2010 12:45:04 -0800
changeset 59709 9365fe1a31650bb825107a60991c04c0a79abb26
parent 59708 4e381da26dc70f21426075b205558d256205ee26
child 59710 b016aa1878fa02eed7e1828e49400c02f17311d9
push id17749
push userfelipc@gmail.com
push dateTue, 28 Dec 2010 20:46:58 +0000
treeherdermozilla-central@9365fe1a3165 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin, vlad, blocking-betaN
bugs559992
milestone2.0b9pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 559992. Implement a caching mechanism for the contentprefs service and use it to retrieve page-specific zoom values, avoiding zoom flicker with back/forward navigation. r=gavin sr=vlad a=blocking-betaN
browser/base/content/browser-fullZoom.js
browser/base/content/test/browser_bug559991.js
dom/interfaces/base/nsIContentPrefService.idl
toolkit/components/contentprefs/src/nsContentPrefService.js
toolkit/components/contentprefs/tests/unit/tail_contentPrefs.js
toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
toolkit/content/Services.jsm
--- a/browser/base/content/browser-fullZoom.js
+++ b/browser/base/content/browser-fullZoom.js
@@ -248,24 +248,30 @@ var FullZoom = {
       return;
 
     // Avoid the cps roundtrip and apply the default/global pref.
     if (aURI.spec == "about:blank") {
       this._applyPrefToSetting(undefined, aBrowser);
       return;
     }
 
-    var self = this;
-    Services.contentPrefs.getPref(aURI, this.name, function (aResult) {
-      // Check that we're still where we expect to be in case this took a while.
-      let browser = aBrowser || gBrowser.selectedBrowser;
-      if (aURI.equals(browser.currentURI)) {
-        self._applyPrefToSetting(aResult, browser);
-      }
-    });
+    let browser = aBrowser || gBrowser.selectedBrowser;
+
+    if (Services.contentPrefs.hasCachedPref(aURI, this.name)) {
+      let zoomValue = Services.contentPrefs.getPref(aURI, this.name);
+      this._applyPrefToSetting(zoomValue, browser);
+    } else {
+      var self = this;
+      Services.contentPrefs.getPref(aURI, this.name, function (aResult) {
+        // Check that we're still where we expect to be in case this took a while.
+        if (aURI.equals(browser.currentURI)) {
+          self._applyPrefToSetting(aResult, browser);
+        }
+      });
+    }
   },
 
   // update state of zoom type menu item
 
   updateMenu: function FullZoom_updateMenu() {
     var menuItem = document.getElementById("toggle_zoom");
 
     menuItem.setAttribute("checked", !ZoomManager.useFullZoom);
--- a/browser/base/content/test/browser_bug559991.js
+++ b/browser/base/content/test/browser_bug559991.js
@@ -13,29 +13,22 @@ function test() {
   };
 
   gPrefService.setBoolPref("browser.zoom.updateBackgroundTabs", true);
   gPrefService.setBoolPref("browser.zoom.siteSpecific", true);
 
   let oldAPTS = FullZoom._applyPrefToSetting;
   let uri = "http://example.org/browser/browser/base/content/test/dummy_page.html";
 
-  // ------------------------------------------------------
-  // Test 1 - Zoom should not be called if URIs don't match
-  FullZoom._applyPrefToSetting = function() {
-    ok(false, "This should not be called");
-  };
-  FullZoom.onLocationChange(makeURI(uri), true);
-
   let tab = gBrowser.addTab();
   tab.linkedBrowser.addEventListener("load", function(event) {
     tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
 
     // -------------------------------------------------------------------
-    // Test 2 - Trigger a tab switch that should now update the zoom level
+    // Test - Trigger a tab switch that should update the zoom level
     FullZoom._applyPrefToSetting = function() {
       ok(true, "applyPrefToSetting was called");
       endTest();
     }
     gBrowser.selectedTab = tab;
 
   }, true); 
   tab.linkedBrowser.loadURI(uri);
--- a/dom/interfaces/base/nsIContentPrefService.idl
+++ b/dom/interfaces/base/nsIContentPrefService.idl
@@ -125,17 +125,17 @@ interface nsIContentPrefService : nsISup
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to check for the global pref (applies to all sites)
    * @param    aName       the name of the pref to check for
    * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
    * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
    */
   boolean hasPref(in nsIVariant aGroup, in AString aName);
-  
+
   /**
    * Remove a pref.
    *
    * @param    aGroup      the group for which to remove the pref, as an nsIURI
    *                       from which the hostname will be used, a string
    *                       (typically in the format of a hostname), or null
    *                       to remove the global pref (applies to all sites) 
    * @param    aName       the name of the pref to remove
@@ -212,12 +212,29 @@ interface nsIContentPrefService : nsISup
    * specific or for which there is not yet a generic method, although generic
    * functionality useful to multiple callers should generally be added to this
    * unfrozen interface.  Also useful for testing the database creation
    * and migration code.
    */
   readonly attribute mozIStorageConnection DBConnection;
 };
 
+[scriptable, uuid(126f07cb-edfe-497e-87dd-ba906506b287)]
+interface nsIContentPrefService_MOZILLA_2_0 : nsIContentPrefService
+{
+  /**
+   * Check whether or not the value of a pref (or its non-existance) is cached.
+   *
+   * @param    aGroup      the group for which to check for the pref, as an nsIURI
+   *                       from which the hostname will be used, a string
+   *                       (typically in the format of a hostname), or null
+   *                       to check for the global pref (applies to all sites)
+   * @param    aName       the name of the pref to check for
+   * @throws   NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
+   * @throws   NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
+   */
+  boolean hasCachedPref(in nsIVariant aGroup, in AString aName);
+};
+
 %{C++
 // The contractID for the generic implementation built in to xpcom.
 #define NS_CONTENT_PREF_SERVICE_CONTRACTID "@mozilla.org/content-pref/service;1"
 %}
--- a/toolkit/components/contentprefs/src/nsContentPrefService.js
+++ b/toolkit/components/contentprefs/src/nsContentPrefService.js
@@ -36,16 +36,18 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cr = Components.results;
 const Cu = Components.utils;
 
+const CACHE_MAX_GROUP_ENTRIES = 100;
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /**
  * Remotes the service. All the remoting/electrolysis code is in here,
  * so the regular service code below remains uncluttered and maintainable.
  */
 function electrolify(service) {
   // FIXME: For now, use the wrappedJSObject hack, until bug
@@ -136,16 +138,17 @@ function ContentPrefService() {
 }
 
 ContentPrefService.prototype = {
   //**************************************************************************//
   // XPCOM Plumbing
 
   classID:          Components.ID("{e6a3f533-4ffa-4615-8eb4-d4e72d883fa7}"),
   QueryInterface:   XPCOMUtils.generateQI([Ci.nsIContentPrefService,
+                                           Ci.nsIContentPrefService_MOZILLA_2_0,
                                            Ci.nsIFrameMessageListener]),
 
 
   //**************************************************************************//
   // Convenience Getters
 
   // Observer Service
   __observerSvc: null,
@@ -207,16 +210,85 @@ ContentPrefService.prototype = {
       case "xpcom-shutdown":
         this._destroy();
         break;
     }
   },
 
 
   //**************************************************************************//
+  // Prefs cache
+
+  _cache: {
+    _prefCache: {},
+
+    cachePref: function(aName, aValue, aGroup) {
+      aGroup = aGroup || "__GlobalPrefs__";
+
+      if (!this._prefCache[aGroup]) {
+        this._possiblyCleanCache();
+        this._prefCache[aGroup] = {};
+      }
+
+      this._prefCache[aGroup][aName] = aValue;
+    },
+
+    getPref: function(aName, aGroup) {
+      aGroup = aGroup || "__GlobalPrefs__";
+
+      if (this._prefCache[aGroup] && this._prefCache[aGroup].hasOwnProperty(aName)) {
+        let value = this._prefCache[aGroup][aName];
+        return [true, value];
+      }
+      return [false, undefined];
+    },
+
+    setPref: function(aName, aValue, aGroup) {
+      if (typeof aValue == "boolean")
+        aValue = aValue ? 1 : 0;
+      else if (aValue === undefined)
+        aValue = null;
+
+      this.cachePref(aName, aValue, aGroup);
+    },
+
+    removePref: function(aName, aGroup) {
+      aGroup = aGroup || "__GlobalPrefs__";
+
+      if (this._prefCache[aGroup].hasOwnProperty(aName)) {
+        delete this._prefCache[aGroup][aName];
+        if (Object.keys(this._prefCache[aGroup]).length == 0) {
+          // remove empty group
+          delete this._prefCache[aGroup];
+        }
+      }
+    },
+
+    invalidate: function() {
+      this._prefCache = {};
+    },
+
+    _possiblyCleanCache: function() {
+      let groupCount = Object.keys(this._prefCache).length;
+
+      if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
+        // Clean half of the entries
+        for (let entry in this._prefCache) {
+          delete this._prefCache[entry];
+          groupCount--;
+
+          if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
+            break;
+        }
+      }
+    }
+  },
+
+
+  //**************************************************************************//
   // nsIContentPrefService
 
   getPref: function ContentPrefService_getPref(aGroup, aName, aCallback) {
     if (!aName)
       throw Components.Exception("aName cannot be null or an empty string",
                                  Cr.NS_ERROR_ILLEGAL_VALUE);
 
     var group = this._parseGroupParam(aGroup);
@@ -257,32 +329,43 @@ ContentPrefService.prototype = {
     }
 
     // Update the existing record, if any, or create a new one.
     if (prefID)
       this._updatePref(prefID, aValue);
     else
       this._insertPref(groupID, settingID, aValue);
 
+    this._cache.setPref(aName, aValue, group);
     for each (var observer in this._getObservers(aName)) {
       try {
         observer.onContentPrefSet(group, aName, aValue);
       }
       catch(ex) {
         Cu.reportError(ex);
       }
     }
   },
 
   hasPref: function ContentPrefService_hasPref(aGroup, aName) {
     // XXX If consumers end up calling this method regularly, then we should
     // optimize this to query the database directly.
     return (typeof this.getPref(aGroup, aName) != "undefined");
   },
 
+  hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName) {
+    if (!aName)
+      throw Components.Exception("aName cannot be null or an empty string",
+                                 Cr.NS_ERROR_ILLEGAL_VALUE);
+
+    let group = this._parseGroupParam(aGroup);
+    let [cached,] = this._cache.getPref(aName, group);
+    return cached;
+  },
+
   removePref: function ContentPrefService_removePref(aGroup, aName) {
     // If there's no old value, then there's nothing to remove.
     if (!this.hasPref(aGroup, aName))
       return;
 
 
     var settingID = this._selectSettingID(aName);
     var group = this._parseGroupParam(aGroup);
@@ -298,20 +381,22 @@ ContentPrefService.prototype = {
 
     this._deletePref(prefID);
 
     // Get rid of extraneous records that are no longer being used.
     this._deleteSettingIfUnused(settingID);
     if (groupID)
       this._deleteGroupIfUnused(groupID);
 
+    this._cache.removePref(aName, group);
     this._notifyPrefRemoved(group, aName);
   },
 
   removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs() {
+    this._cache.invalidate();
     this._dbConnection.beginTransaction();
     try {
       this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE groupID IS NOT NULL");
       this._dbConnection.executeSimpleSQL("DELETE FROM groups");
       this._dbConnection.executeSimpleSQL(
         "DELETE FROM settings " +
         "WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)"
       );
@@ -356,16 +441,17 @@ ContentPrefService.prototype = {
     if (this.hasPref(null, aName)) {
       groupNames.push(null);
     }
 
     this._dbConnection.executeSimpleSQL("DELETE FROM prefs WHERE settingID = " + settingID);
     this._dbConnection.executeSimpleSQL("DELETE FROM settings WHERE id = " + settingID);
 
     for (var i = 0; i < groupNames.length; i++) {
+      this._cache.removePref(aName, groupNames[i]);
       this._notifyPrefRemoved(groupNames[i], aName);
       if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
         this._deleteGroupIfUnused(groupIDs[i]);
     }
   },
 
   getPrefs: function ContentPrefService_getPrefs(aGroup) {
     var group = this._parseGroupParam(aGroup);
@@ -470,27 +556,49 @@ ContentPrefService.prototype = {
         "JOIN settings ON prefs.settingID = settings.id " +
         "WHERE groups.name = :group " +
         "AND settings.name = :setting"
       );
 
     return this.__stmtSelectPref;
   },
 
+  _scheduleCallback: function(func) {
+    let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+    tm.mainThread.dispatch(func, Ci.nsIThread.DISPATCH_NORMAL);
+  },
+
   _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
-    var value;
+
+    let [cached, value] = this._cache.getPref(aSetting, aGroup);
+    if (cached) {
+      if (aCallback) {
+        this._scheduleCallback(function(){aCallback.onResult(value);});
+        return;
+      }
+      return value;
+    }
 
     try {
       this._stmtSelectPref.params.group = aGroup;
       this._stmtSelectPref.params.setting = aSetting;
 
-      if (aCallback)
-        new AsyncStatement(this._stmtSelectPref).execute(aCallback);
-      else if (this._stmtSelectPref.executeStep())
-        value = this._stmtSelectPref.row["value"];
+      if (aCallback) {
+        let cache = this._cache;
+        new AsyncStatement(this._stmtSelectPref).execute({onResult: function(aResult) {
+          cache.cachePref(aSetting, aResult, aGroup);
+          aCallback.onResult(aResult);
+        }});
+      }
+      else {
+        if (this._stmtSelectPref.executeStep()) {
+          value = this._stmtSelectPref.row["value"];
+        }
+        this._cache.cachePref(aSetting, value, aGroup);
+      }
     }
     finally {
       this._stmtSelectPref.reset();
     }
 
     return value;
   },
 
@@ -504,25 +612,42 @@ ContentPrefService.prototype = {
         "WHERE prefs.groupID IS NULL " +
         "AND settings.name = :name"
       );
 
     return this.__stmtSelectGlobalPref;
   },
 
   _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
-    var value;
+
+    let [cached, value] = this._cache.getPref(aName, null);
+    if (cached) {
+      if (aCallback) {
+        this._scheduleCallback(function(){aCallback.onResult(value);});
+        return;
+      }
+      return value;
+    }
 
     try {
       this._stmtSelectGlobalPref.params.name = aName;
 
-      if (aCallback)
-        new AsyncStatement(this._stmtSelectGlobalPref).execute(aCallback);
-      else if (this._stmtSelectGlobalPref.executeStep())
-        value = this._stmtSelectGlobalPref.row["value"];
+      if (aCallback) {
+        let cache = this._cache;
+        new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult: function(aResult) {
+          cache.cachePref(aName, aResult);
+          aCallback.onResult(aResult);
+        }});
+      }
+      else {
+        if (this._stmtSelectGlobalPref.executeStep()) {
+          value = this._stmtSelectGlobalPref.row["value"];
+        }
+        this._cache.cachePref(aName, value);
+      }
     }
     finally {
       this._stmtSelectGlobalPref.reset();
     }
 
     return value;
   },
 
--- a/toolkit/components/contentprefs/tests/unit/tail_contentPrefs.js
+++ b/toolkit/components/contentprefs/tests/unit/tail_contentPrefs.js
@@ -30,8 +30,9 @@
  * 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 ***** */
 
 ContentPrefTest.deleteDatabase();
+ContentPrefTest.__dirSvc = null;
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
@@ -0,0 +1,243 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+let cps = Cc["@mozilla.org/content-pref/service;1"].
+          getService(Ci.nsIContentPrefService_MOZILLA_2_0);
+
+function run_test() {
+  testCacheWorks("test1.example.com", "test-pref1");
+  testHasCachedPrefFunction("test2.example.com", "test-pref2");
+  testSetCaches("test3.example.com", "test-pref3");
+  testGetCaches("test4.example.com", "test-pref4");
+  testRemovePrefs("test5.example.com", "test-pref5");
+  testTypeConversions("test6.example.com", "test-pref6");
+  testNonExistingPrefCachesAsUndefined("test7.example.com", "test-pref7");
+  testCacheEviction("test8.example.com", "test-pref8");
+}
+
+function testCacheWorks(uri, prefName) {
+  const CACHED_VALUE = 3;
+  const NEW_VALUE = 5;
+
+  cps.setPref(uri, prefName, CACHED_VALUE);
+  do_check_eq(cps.getPref(uri, prefName), CACHED_VALUE);
+
+  // Now change the value directly through the DB and check
+  // that the cached value is different
+
+  let groupId = selectValue("SELECT id FROM groups WHERE name = :param1", "id", uri);
+  let settingId = selectValue("SELECT id FROM settings WHERE name = :param1", "id", prefName);
+  let prefId = selectValue("SELECT id FROM prefs WHERE groupID = :param1 AND settingID = :param2",
+                           "id", groupId, settingId);
+
+  let stmt = cps.DBConnection.createStatement("UPDATE prefs SET value = :value WHERE id = :id");
+  stmt.params.value = NEW_VALUE;
+  stmt.params.id = prefId;
+  stmt.execute();
+
+  let dbValue = selectValue("SELECT value FROM prefs WHERE id = :param1", "value", prefId);
+  let cacheValue = cps.getPref(uri, prefName);
+
+  do_check_eq(dbValue, NEW_VALUE);
+  do_check_eq(cacheValue, CACHED_VALUE);
+  do_check_neq(cacheValue, dbValue);
+
+  do_test_pending();
+  cps.getPref(uri, prefName, function (value) {
+    do_check_eq(dbValue, NEW_VALUE);
+    do_check_eq(value, CACHED_VALUE);
+    do_check_neq(value, dbValue);
+    do_test_finished();
+  });
+}
+
+function testHasCachedPrefFunction(uri, prefName) {
+  const STARTING_VALUE = 3;
+  const NEW_VALUE = 5;
+
+  do_check_false(isCached(uri, prefName));
+
+  cps.setPref(uri, prefName, STARTING_VALUE);
+
+  let groupId = selectValue("SELECT id FROM groups WHERE name = :param1", "id", uri);
+  let settingId = selectValue("SELECT id FROM settings WHERE name = :param1", "id", prefName);
+  let prefId = selectValue("SELECT id FROM prefs WHERE groupID = :param1 AND settingID = :param2",
+                       "id", groupId, settingId);
+
+  do_check_neq(prefId, undefined);
+
+  let originalValue = selectValue("SELECT value FROM prefs WHERE id = :param1", "value", prefId);
+  do_check_eq(originalValue, STARTING_VALUE);
+
+  let stmt = cps.DBConnection.createStatement("UPDATE prefs SET value = :value WHERE id = :id");
+  stmt.params.value = NEW_VALUE;
+  stmt.params.id = prefId;
+  stmt.execute();
+
+  let newValue = selectValue("SELECT value FROM prefs WHERE id = :param1", "value", prefId);
+  do_check_eq(newValue, NEW_VALUE);
+
+  let cachedValue = cps.getPref(uri, prefName);
+  do_check_eq(cachedValue, STARTING_VALUE);
+  do_check_true(isCached(uri, prefName));
+}
+
+function testSetCaches(uri, prefName) {
+  cps.setPref(uri, prefName, 0);
+  do_check_true(isCached(uri, prefName));
+}
+
+function testRemovePrefs(uri, prefName) {
+
+  /* removePref */
+  cps.setPref("www1." + uri, prefName, 1);
+
+  do_check_eq(cps.getPref("www1." + uri, prefName), 1);
+
+  cps.removePref("www1." + uri, prefName);
+
+  do_check_false(isCached("www1." + uri, prefName));
+  do_check_false(cps.hasPref("www1." + uri, prefName));
+  do_check_neq(cps.getPref("www1." + uri, prefName), 1);
+
+  /* removeGroupedPrefs */
+  cps.setPref("www2." + uri, prefName, 2);
+  cps.setPref("www3." + uri, prefName, 3);
+
+  do_check_eq(cps.getPref("www2." + uri, prefName), 2);
+  do_check_eq(cps.getPref("www3." + uri, prefName), 3);
+
+  cps.removeGroupedPrefs();
+
+  do_check_false(isCached("www2." + uri, prefName));
+  do_check_false(isCached("www3." + uri, prefName));
+  do_check_false(cps.hasPref("www2." + uri, prefName));
+  do_check_false(cps.hasPref("www3." + uri, prefName));
+  do_check_neq(cps.getPref("www2." + uri, prefName), 2);
+  do_check_neq(cps.getPref("www3." + uri, prefName), 3);
+
+  /* removePrefsByName */
+  cps.setPref("www4." + uri, prefName, 4);
+  cps.setPref("www5." + uri, prefName, 5);
+
+  do_check_eq(cps.getPref("www4." + uri, prefName), 4);
+  do_check_eq(cps.getPref("www5." + uri, prefName), 5);
+
+  cps.removePrefsByName(prefName);
+
+  do_check_false(isCached("www4." + uri, prefName));
+  do_check_false(isCached("www5." + uri, prefName));
+  do_check_false(cps.hasPref("www4." + uri, prefName));
+  do_check_false(cps.hasPref("www5." + uri, prefName));
+  do_check_neq(cps.getPref("www4." + uri, prefName), 4);
+  do_check_neq(cps.getPref("www5." + uri, prefName), 5);
+}
+
+function testGetCaches(uri, prefName) {
+  const VALUE = 4;
+
+  let insertGroup = cps.DBConnection.createStatement("INSERT INTO groups (name) VALUES (:name)");
+  insertGroup.params.name = uri;
+  insertGroup.execute();
+  let groupId = cps.DBConnection.lastInsertRowID;
+
+  let insertSetting = cps.DBConnection.createStatement("INSERT INTO settings (name) VALUES (:name)");
+  insertSetting.params.name = prefName;
+  insertSetting.execute();
+  let settingId = cps.DBConnection.lastInsertRowID;
+
+  let insertPref = cps.DBConnection.createStatement("INSERT INTO prefs (groupID, settingID, value) " +
+                                                    "VALUES (:groupId, :settingId, :value)");
+  insertPref.params.groupId = groupId;
+  insertPref.params.settingId = settingId;
+  insertPref.params.value = VALUE;
+  insertPref.execute();
+  let prefId = cps.DBConnection.lastInsertRowID;
+
+  let dbValue = selectValue("SELECT value FROM prefs WHERE id = :param1", "value", prefId);
+
+  // First access from service should hit the DB
+  let svcValue = cps.getPref(uri, prefName);
+
+  // Second time should get the value from cache
+  let cacheValue = cps.getPref(uri, prefName);
+
+  do_check_eq(VALUE, dbValue);
+  do_check_eq(VALUE, svcValue);
+  do_check_eq(VALUE, cacheValue);
+
+  do_check_true(isCached(uri, prefName));
+}
+
+function testTypeConversions(uri, prefName) {
+  let value;
+
+  cps.setPref(uri, prefName, true);
+  value = cps.getPref(uri, prefName);
+  do_check_true(value === 1);
+
+  cps.setPref(uri, prefName, false);
+  value = cps.getPref(uri, prefName);
+  do_check_true(value === 0);
+
+  cps.setPref(uri, prefName, null);
+  value = cps.getPref(uri, prefName);
+  do_check_true(value === null);
+
+  cps.setPref(uri, prefName, undefined);
+  value = cps.getPref(uri, prefName);
+  do_check_true(value === null);
+}
+
+function testNonExistingPrefCachesAsUndefined(uri, prefName) {
+
+  do_check_false(isCached(uri, prefName));
+
+  // Cache the pref
+  let value = cps.getPref(uri, prefName);
+  do_check_true(value === undefined);
+
+  do_check_true(isCached(uri, prefName));
+
+  // Cached pref
+  value = cps.getPref(uri, prefName);
+  do_check_true(value === undefined);
+}
+
+function testCacheEviction(uri, prefName) {
+
+  cps.setPref(uri, prefName, 5);
+  do_check_eq(cps.getPref(uri, prefName), 5);
+  do_check_true(isCached(uri, prefName));
+
+  // try to evict value from cache by adding various other entries
+  const ENTRIES_TO_ADD = 200;
+  for (let i = 0; i < ENTRIES_TO_ADD; i++) {
+    let uriToAdd = "www" + i + uri;
+    cps.setPref(uriToAdd, prefName, 0);
+  }
+
+  do_check_false(isCached(uri, prefName));
+
+}
+
+function selectValue(stmt, columnName, param1, param2) {
+  let stmt = cps.DBConnection.createStatement(stmt);
+  if (param1)
+    stmt.params.param1 = param1;
+
+  if (param2)
+    stmt.params.param2 = param2;
+
+  stmt.executeStep();
+  let val = stmt.row[columnName];
+  stmt.reset();
+  stmt.finalize();
+  return val;
+}
+
+function isCached(uri, prefName) {
+  return cps.hasCachedPref(uri, prefName);
+}
--- a/toolkit/content/Services.jsm
+++ b/toolkit/content/Services.jsm
@@ -58,17 +58,17 @@ XPCOMUtils.defineLazyGetter(Services, "a
 XPCOMUtils.defineLazyGetter(Services, "dirsvc", function () {
   return Cc["@mozilla.org/file/directory_service;1"]
            .getService(Ci.nsIDirectoryService)
            .QueryInterface(Ci.nsIProperties);
 });
 
 XPCOMUtils.defineLazyServiceGetter(Services, "contentPrefs",
                                    "@mozilla.org/content-pref/service;1",
-                                   "nsIContentPrefService");
+                                   "nsIContentPrefService_MOZILLA_2_0");
 
 XPCOMUtils.defineLazyServiceGetter(Services, "wm",
                                    "@mozilla.org/appshell/window-mediator;1",
                                    "nsIWindowMediator");
 
 XPCOMUtils.defineLazyServiceGetter(Services, "obs",
                                    "@mozilla.org/observer-service;1",
                                    "nsIObserverService");