--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -159,16 +159,18 @@ var whitelist = [
{file: "chrome://mozapps/skin/plugins/pluginBlocked.png"},
// Bug 1348558
{file: "chrome://mozapps/skin/update/downloadButtons.png",
platforms: ["linux"]},
// Bug 1348559
{file: "chrome://pippki/content/resetpassword.xul"},
// Bug 1351078
{file: "resource://gre/modules/Battery.jsm"},
+ // Bug 1351070
+ {file: "resource://gre/modules/ContentPrefInstance.jsm"},
// Bug 1351079
{file: "resource://gre/modules/ISO8601DateUtils.jsm"},
// Bug 1337345
{file: "resource://gre/modules/Manifest.jsm"},
// Bug 1351097
{file: "resource://gre/modules/accessibility/AccessFu.jsm"},
// Bug 1351637
{file: "resource://gre/modules/sdk/bootstrap.js"},
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -459,18 +459,18 @@
@RESPATH@/components/PageIconProtocolHandler.js
@RESPATH@/components/PlacesCategoriesStarter.js
@RESPATH@/components/ColorAnalyzer.js
@RESPATH@/components/PageThumbsProtocol.js
@RESPATH@/components/mozProtocolHandler.js
@RESPATH@/components/mozProtocolHandler.manifest
@RESPATH@/components/nsDefaultCLH.manifest
@RESPATH@/components/nsDefaultCLH.js
-@RESPATH@/components/ContentPrefService2.manifest
-@RESPATH@/components/ContentPrefService2.js
+@RESPATH@/components/nsContentPrefService.manifest
+@RESPATH@/components/nsContentPrefService.js
@RESPATH@/components/nsContentDispatchChooser.manifest
@RESPATH@/components/nsContentDispatchChooser.js
@RESPATH@/components/nsHandlerService-json.manifest
@RESPATH@/components/nsHandlerService-json.js
@RESPATH@/components/nsHandlerService.manifest
@RESPATH@/components/nsHandlerService.js
@RESPATH@/components/nsWebHandlerApp.manifest
@RESPATH@/components/nsWebHandlerApp.js
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -81,17 +81,17 @@
// input type=file
#include "mozilla/dom/FileSystemEntry.h"
#include "mozilla/dom/FileSystem.h"
#include "mozilla/dom/File.h"
#include "mozilla/dom/FileList.h"
#include "nsIFile.h"
#include "nsDirectoryServiceDefs.h"
-#include "nsIContentPrefService2.h"
+#include "nsIContentPrefService.h"
#include "nsIMIMEService.h"
#include "nsIObserverService.h"
#include "nsIPopupWindowManager.h"
#include "nsGlobalWindow.h"
// input type=image
#include "nsImageLoadingContent.h"
#include "imgRequestProxy.h"
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -7,16 +7,17 @@
with Files("**"):
BUG_COMPONENT = ("Core", "DOM")
XPIDL_SOURCES += [
'domstubs.idl',
'nsIBrowser.idl',
'nsIBrowserDOMWindow.idl',
'nsIContentPermissionPrompt.idl',
+ 'nsIContentPrefService.idl',
'nsIContentPrefService2.idl',
'nsIContentProcess.idl',
'nsIContentURIGrouper.idl',
'nsIDOMChromeWindow.idl',
'nsIDOMClientRect.idl',
'nsIDOMClientRectList.idl',
'nsIDOMConstructor.idl',
'nsIDOMCrypto.idl',
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsIContentPrefService.idl
@@ -0,0 +1,263 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIVariant;
+interface nsIPropertyBag2;
+interface nsIContentURIGrouper;
+interface nsILoadContext;
+interface mozIStorageConnection;
+
+[scriptable, uuid(43635c53-b445-4c4e-8cc5-562697299b55)]
+interface nsIContentPrefObserver : nsISupports
+{
+ /**
+ * Called when a content pref is set to a different value.
+ *
+ * @param aGroup the group to which the pref belongs, or null
+ * if it's a global pref (applies to all sites)
+ * @param aName the name of the pref that was set
+ * @param aValue the new value of the pref
+ * @param aIsPrivate an optional flag determining whether the
+ * original context is private or not
+ */
+ void onContentPrefSet(in AString aGroup,
+ in AString aName,
+ in nsIVariant aValue,
+ [optional] in boolean aIsPrivate);
+
+ /**
+ * Called when a content pref is removed.
+ *
+ * @param aGroup the group to which the pref belongs, or null
+ * if it's a global pref (applies to all sites)
+ * @param aName the name of the pref that was removed
+ * @param aIsPrivate an optional flag determining whether the
+ * original context is private or not
+ */
+ void onContentPrefRemoved(in AString aGroup,
+ in AString aName,
+ [optional] in boolean aIsPrivate);
+};
+
+[scriptable, function, uuid(c1b3d6df-5373-4606-8494-8bcf14a7fc62)]
+interface nsIContentPrefCallback : nsISupports
+{
+ void onResult(in nsIVariant aResult);
+};
+
+/**
+ * @deprecated Please use nsIContentPrefService2 instead.
+ */
+[scriptable, uuid(e3f772f3-023f-4b32-b074-36cf0fd5d414)]
+interface nsIContentPrefService : nsISupports
+{
+ /**
+ * Get a pref.
+ *
+ * Besides the regular string, integer, boolean, etc. values, this method
+ * may return null (nsIDataType::VTYPE_EMPTY), which means the pref is set
+ * to NULL in the database, as well as undefined (nsIDataType::VTYPE_VOID),
+ * which means there is no record for this pref in the database.
+ *
+ * This method can be called from content processes in electrolysis builds.
+ * We have a whitelist of values that can be read in such a way.
+ *
+ * @param aGroup the group for which to get the pref, as an nsIURI
+ * from which the hostname will be used, a string
+ * (typically in the format of a hostname), or null
+ * to get the global pref (applies to all sites)
+ * @param aName the name of the pref to get
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search in memory or in
+ * permanent storage for it), obtained from a relevant
+ * window or channel.
+ * @param aCallback an optional nsIContentPrefCallback to receive the
+ * result. If desired, JavaScript callers can instead
+ * provide a function to call upon completion
+ *
+ * @returns the value of the pref
+ * @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
+ */
+ nsIVariant getPref(in nsIVariant aGroup, in AString aName,
+ in nsILoadContext aPrivacyContext,
+ [optional] in nsIContentPrefCallback aCallback);
+
+ /**
+ * Set a pref.
+ *
+ * This method can be called from content processes in electrolysis builds.
+ * We have a whitelist of values that can be set in such a way.
+ *
+ * @param aGroup the group for which to set the pref, as an nsIURI
+ * from which the hostname will be used, a string
+ * (typically in the format of a hostname), or null
+ * to set the global pref (applies to all sites)
+ * @param aName the name of the pref to set
+ * @param aValue the new value of the pref
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to store it in memory or in
+ * permanent storage), obtained from a relevant
+ * window or channel.
+ * @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
+ */
+ void setPref(in nsIVariant aGroup, in AString aName, in nsIVariant aValue, in nsILoadContext aPrivacyContext);
+
+ /**
+ * Check whether or not a pref exists.
+ *
+ * @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
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search in memory or in
+ * permanent storage for it), obtained from a relevant
+ * window or channel.
+ * @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, in nsILoadContext aContext);
+
+ /**
+ * 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
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search in memory or in
+ * permanent storage for it), obtained from a relevant
+ * window or channel.
+ * @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, in nsILoadContext aContext);
+
+ /**
+ * 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
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search in memory or in
+ * permanent storage for it), obtained from a relevant
+ * window or channel.
+ * @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
+ */
+ void removePref(in nsIVariant aGroup, in AString aName, in nsILoadContext aContext);
+
+ /**
+ * Remove all grouped prefs. Useful for removing references to the sites
+ * the user has visited when the user clears their private data.
+ *
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to remove prefs in memory or
+ * in permanent storage), obtained from a relevant
+ * window or channel.
+ */
+ void removeGroupedPrefs(in nsILoadContext aContext);
+
+ /**
+ * Remove all prefs with the given name.
+ *
+ * @param aName the setting name for which to remove prefs
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the prefs (ie. whether to remove prefs in memory or
+ * in permanent storage), obtained from a relevant
+ * window or channel.
+ * @throws NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
+ */
+ void removePrefsByName(in AString aName, in nsILoadContext aContext);
+
+ /**
+ * Get the prefs that apply to the given site.
+ *
+ * @param aGroup the group for which to retrieve prefs, as an nsIURI
+ * from which the hostname will be used, a string
+ * (typically in the format of a hostname), or null
+ * to get the global prefs (apply to all sites)
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search for prefs in memory
+ * or in permanent storage), obtained from a relevant
+ * window or channel.
+ *
+ * @returns a property bag of prefs
+ * @throws NS_ERROR_ILLEGAL_VALUE if aGroup is not a string, nsIURI, or null
+ */
+ nsIPropertyBag2 getPrefs(in nsIVariant aGroup, in nsILoadContext aContext);
+
+ /**
+ * Get the prefs with the given name.
+ *
+ * @param aName the setting name for which to retrieve prefs
+ * @param aPrivacyContext
+ * a context from which to determine the privacy status
+ * of the pref (ie. whether to search for prefs in memory
+ * or in permanent storage), obtained from a relevant
+ * window or channel.
+ *
+ * @returns a property bag of prefs
+ * @throws NS_ERROR_ILLEGAL_VALUE if aName is null or an empty string
+ */
+ nsIPropertyBag2 getPrefsByName(in AString aName, in nsILoadContext aContext);
+
+ /**
+ * Add an observer.
+ *
+ * @param aName the setting to observe, or null to add
+ * a generic observer that observes all settings
+ * @param aObserver the observer to add
+ */
+ void addObserver(in AString aName, in nsIContentPrefObserver aObserver);
+
+ /**
+ * Remove an observer.
+ *
+ * @param aName the setting being observed, or null to remove
+ * a generic observer that observes all settings
+ * @param aObserver the observer to remove
+ */
+ void removeObserver(in AString aName, in nsIContentPrefObserver aObserver);
+
+ /**
+ * The component that the service uses to determine the groups to which
+ * URIs belong. By default this is the "hostname grouper", which groups
+ * URIs by full hostname (a.k.a. site).
+ */
+ readonly attribute nsIContentURIGrouper grouper;
+
+ /**
+ * The database connection to the content preferences database.
+ * Useful for accessing and manipulating preferences in ways that are caller-
+ * 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;
+};
+
+%{C++
+// The contractID for the generic implementation built in to xpcom.
+#define NS_CONTENT_PREF_SERVICE_CONTRACTID "@mozilla.org/content-pref/service;1"
+%}
--- a/dom/interfaces/base/nsIContentPrefService2.idl
+++ b/dom/interfaces/base/nsIContentPrefService2.idl
@@ -1,51 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsISupports.idl"
interface nsIVariant;
+interface nsIContentPrefObserver;
interface nsIContentPrefCallback2;
interface nsILoadContext;
interface nsIContentPref;
-[scriptable, uuid(43635c53-b445-4c4e-8cc5-562697299b55)]
-interface nsIContentPrefObserver : nsISupports
-{
- /**
- * Called when a content pref is set to a different value.
- *
- * @param aGroup the group to which the pref belongs, or null
- * if it's a global pref (applies to all sites)
- * @param aName the name of the pref that was set
- * @param aValue the new value of the pref
- * @param aIsPrivate an optional flag determining whether the
- * original context is private or not
- */
- void onContentPrefSet(in AString aGroup,
- in AString aName,
- in nsIVariant aValue,
- [optional] in boolean aIsPrivate);
-
- /**
- * Called when a content pref is removed.
- *
- * @param aGroup the group to which the pref belongs, or null
- * if it's a global pref (applies to all sites)
- * @param aName the name of the pref that was removed
- * @param aIsPrivate an optional flag determining whether the
- * original context is private or not
- */
- void onContentPrefRemoved(in AString aGroup,
- in AString aName,
- [optional] in boolean aIsPrivate);
-};
-
/**
* Content Preferences
*
* Content preferences allow the application to associate arbitrary data, or
* "preferences", with specific domains, or web "content". Specifically, a
* content preference is a structure with three values: a domain with which the
* preference is associated, a name that identifies the preference within its
* domain, and a value. (See nsIContentPref below.)
@@ -429,13 +398,8 @@ interface nsIContentPrefCallback2 : nsIS
[scriptable, function, uuid(9f24948d-24b5-4b1b-b554-7dbd58c1d792)]
interface nsIContentPref : nsISupports
{
readonly attribute AString domain;
readonly attribute AString name;
readonly attribute nsIVariant value;
};
-
-%{C++
-// The contractID for the generic implementation built in to xpcom.
-#define NS_CONTENT_PREF_SERVICE_CONTRACTID "@mozilla.org/content-pref/service;1"
-%}
--- a/editor/composer/nsEditorSpellCheck.cpp
+++ b/editor/composer/nsEditorSpellCheck.cpp
@@ -14,16 +14,17 @@
#include "mozilla/mozalloc.h" // for operator delete, etc
#include "nsAString.h" // for nsAString::IsEmpty, etc
#include "nsComponentManagerUtils.h" // for do_CreateInstance
#include "nsDebug.h" // for NS_ENSURE_TRUE, etc
#include "nsDependentSubstring.h" // for Substring
#include "nsEditorSpellCheck.h"
#include "nsError.h" // for NS_ERROR_NOT_INITIALIZED, etc
#include "nsIContent.h" // for nsIContent
+#include "nsIContentPrefService.h" // for nsIContentPrefService, etc
#include "nsIContentPrefService2.h" // for nsIContentPrefService2, etc
#include "nsIDOMDocument.h" // for nsIDOMDocument
#include "nsIDOMElement.h" // for nsIDOMElement
#include "nsIDocument.h" // for nsIDocument
#include "nsIEditor.h" // for nsIEditor
#include "nsIHTMLEditor.h" // for nsIHTMLEditor
#include "nsILoadContext.h"
#include "nsISelection.h" // for nsISelection
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/ContentPrefInstance.jsm
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+this.EXPORTED_SYMBOLS = ["ContentPrefInstance"];
+
+// This is a wrapper for nsIContentPrefService that alleviates the need to pass
+// an nsILoadContext argument to every method. Pass the context to the constructor
+// instead and continue on your way in blissful ignorance.
+
+this.ContentPrefInstance = function ContentPrefInstance(aContext) {
+ this._contentPrefSvc = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService);
+ this._context = aContext;
+};
+
+ContentPrefInstance.prototype = {
+ getPref: function ContentPrefInstance_init(aName, aGroup, aCallback) {
+ return this._contentPrefSvc.getPref(aName, aGroup, this._context, aCallback);
+ },
+
+ setPref: function ContentPrefInstance_setPref(aGroup, aName, aValue, aContext) {
+ return this._contentPrefSvc.setPref(aGroup, aName, aValue,
+ aContext ? aContext : this._context);
+ },
+
+ hasPref: function ContentPrefInstance_hasPref(aGroup, aName) {
+ return this._contentPrefSvc.hasPref(aGroup, aName, this._context);
+ },
+
+ hasCachedPref: function ContentPrefInstance_hasCachedPref(aGroup, aName) {
+ return this._contentPrefSvc.hasCachedPref(aGroup, aName, this._context);
+ },
+
+ removePref: function ContentPrefInstance_removePref(aGroup, aName) {
+ return this._contentPrefSvc.removePref(aGroup, aName, this._context);
+ },
+
+ removeGroupedPrefs: function ContentPrefInstance_removeGroupedPrefs() {
+ return this._contentPrefSvc.removeGroupedPrefs(this._context);
+ },
+
+ removePrefsByName: function ContentPrefInstance_removePrefsByName(aName) {
+ return this._contentPrefSvc.removePrefsByName(aName, this._context);
+ },
+
+ getPrefs: function ContentPrefInstance_getPrefs(aGroup) {
+ return this._contentPrefSvc.getPrefs(aGroup, this._context);
+ },
+
+ getPrefsByName: function ContentPrefInstance_getPrefsByName(aName) {
+ return this._contentPrefSvc.getPrefsByName(aName, this._context);
+ },
+
+ addObserver: function ContentPrefInstance_addObserver(aName, aObserver) {
+ return this._contentPrefSvc.addObserver(aName, aObserver);
+ },
+
+ removeObserver: function ContentPrefInstance_removeObserver(aName, aObserver) {
+ return this._contentPrefSvc.removeObserver(aName, aObserver);
+ },
+
+ get grouper() {
+ return this._contentPrefSvc.grouper;
+ },
+
+ get DBConnection() {
+ return this._contentPrefSvc.DBConnection;
+ },
+
+ set loadContext(aLoadContext) {
+ this._context = aLoadContext;
+ }
+};
rename from toolkit/components/contentprefs/ContentPrefService2.js
rename to toolkit/components/contentprefs/ContentPrefService2.jsm
--- a/toolkit/components/contentprefs/ContentPrefService2.js
+++ b/toolkit/components/contentprefs/ContentPrefService2.jsm
@@ -1,119 +1,56 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
+// This file is an XPCOM component that implements nsIContentPrefService2.
+// Although it's a JSM, it's not intended to be imported by consumers like JSMs
+// are usually imported. It's only a JSM so that nsContentPrefService.js can
+// easily use it. Consumers should access this component with the usual XPCOM
+// rigmarole:
+//
+// Cc["@mozilla.org/content-pref/service;1"].
+// getService(Ci.nsIContentPrefService2);
+//
+// That contract ID actually belongs to nsContentPrefService.js, which, when
+// QI'ed to nsIContentPrefService2, returns an instance of this component.
+//
+// The plan is to eventually remove nsIContentPrefService and its
+// implementation, nsContentPrefService.js. At such time this file can stop
+// being a JSM, and the "_cps" parts that ContentPrefService2 relies on and
+// NSGetFactory and all the other XPCOM initialization goop in
+// nsContentPrefService.js can be moved here.
+//
+// See https://bugzilla.mozilla.org/show_bug.cgi?id=699859
+
+var EXPORTED_SYMBOLS = [
+ "ContentPrefService2",
+];
+
const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components;
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/ContentPrefUtils.jsm");
Cu.import("resource://gre/modules/ContentPrefStore.jsm");
-const CACHE_MAX_GROUP_ENTRIES = 100;
-
const GROUP_CLAUSE = `
SELECT id
FROM groups
WHERE name = :group OR
(:includeSubdomains AND name LIKE :pattern ESCAPE '/')
`;
-function ContentPrefService2() {
- if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
- return Cu.import("resource://gre/modules/ContentPrefServiceChild.jsm")
- .ContentPrefServiceChild;
- }
-
- // If this throws an exception, it causes the getService call to fail,
- // but the next time a consumer tries to retrieve the service, we'll try
- // to initialize the database again, which might work if the failure
- // was due to a temporary condition (like being out of disk space).
- this._dbInit();
-
- this._observerSvc.addObserver(this, "last-pb-context-exited");
-
- // Observe shutdown so we can shut down the database connection.
- this._observerSvc.addObserver(this, "xpcom-shutdown");
+function ContentPrefService2(cps) {
+ this._cps = cps;
+ this._cache = cps._cache;
+ this._pbStore = cps._privModeStorage;
}
-const cache = new ContentPrefStore();
-cache.set = function CPS_cache_set(group, name, val) {
- Object.getPrototypeOf(this).set.apply(this, arguments);
- let groupCount = this._groups.size;
- if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
- // Clean half of the entries
- for (let [group, name, ] of this) {
- this.remove(group, name);
- groupCount--;
- if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
- break;
- }
- }
-};
-
-const privModeStorage = new ContentPrefStore();
-
ContentPrefService2.prototype = {
- // XPCOM Plumbing
-
- classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
-
- // Convenience Getters
-
- // Observer Service
- __observerSvc: null,
- get _observerSvc() {
- if (!this.__observerSvc)
- this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- return this.__observerSvc;
- },
-
- // Preferences Service
- __prefSvc: null,
- get _prefSvc() {
- if (!this.__prefSvc)
- this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
- getService(Ci.nsIPrefBranch);
- return this.__prefSvc;
- },
-
-
- // Destruction
-
- _destroy: function ContentPrefService__destroy() {
- this._observerSvc.removeObserver(this, "xpcom-shutdown");
- this._observerSvc.removeObserver(this, "last-pb-context-exited");
-
- this.destroy();
-
- this._dbConnection.asyncClose(() => {
- Services.obs.notifyObservers(null, "content-prefs-db-closed");
- });
-
- // Delete references to XPCOM components to make sure we don't leak them
- // (although we haven't observed leakage in tests). Also delete references
- // in _observers and _genericObservers to avoid cycles with those that
- // refer to us and don't remove themselves from those observer pools.
- delete this._observers;
- delete this._genericObservers;
- delete this.__grouper;
- delete this.__observerSvc;
- delete this.__prefSvc;
- },
-
-
- // in-memory cache and private-browsing stores
-
- _cache: cache,
- _pbStore: privModeStorage,
-
- // nsIContentPrefService
getByName: function CPS2_getByName(name, context, callback) {
checkNameArg(name);
checkCallbackArg(callback, true);
// Some prefs may be in both the database and the private browsing store.
// Notify the caller of such prefs only once, using the values from private
// browsing.
@@ -312,17 +249,17 @@ ContentPrefService2.prototype = {
checkNameArg(name);
checkValueArg(value);
checkCallbackArg(callback, false);
if (context && context.usePrivateBrowsing) {
this._pbStore.set(group, name, value);
this._schedule(function() {
cbHandleCompletion(callback, Ci.nsIContentPrefCallback2.COMPLETE_OK);
- this._notifyPrefSet(group, name, value, context.usePrivateBrowsing);
+ this._cps._notifyPrefSet(group, name, value, context.usePrivateBrowsing);
});
return;
}
// Invalidate the cached value so consumers accessing the cache between now
// and when the operation finishes don't get old data.
this._cache.remove(group, name);
@@ -384,17 +321,17 @@ ContentPrefService2.prototype = {
stmts.push(stmt);
this._execStmts(stmts, {
onDone: function onDone(reason, ok) {
if (ok)
this._cache.setWithCast(group, name, value);
cbHandleCompletion(callback, reason);
if (ok)
- this._notifyPrefSet(group, name, value, context && context.usePrivateBrowsing);
+ this._cps._notifyPrefSet(group, name, value, context && context.usePrivateBrowsing);
},
onError: function onError(nsresult) {
cbHandleError(callback, nsresult);
}
});
},
removeByDomainAndName: function CPS2_removeByDomainAndName(group, name,
@@ -464,17 +401,17 @@ ContentPrefService2.prototype = {
prefs.set(sgroup, name, undefined);
this._pbStore.remove(sgroup, name);
}
}
}
cbHandleCompletion(callback, reason);
if (ok) {
for (let [sgroup, , ] of prefs) {
- this._notifyPrefRemoved(sgroup, name, isPrivate);
+ this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
}
}
},
onError: function onError(nsresult) {
cbHandleError(callback, nsresult);
}
});
},
@@ -577,17 +514,17 @@ ContentPrefService2.prototype = {
prefs.set(sgroup, sname, undefined);
this._pbStore.remove(sgroup, sname);
}
}
}
cbHandleCompletion(callback, reason);
if (ok) {
for (let [sgroup, sname, ] of prefs) {
- this._notifyPrefRemoved(sgroup, sname, isPrivate);
+ this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
}
}
},
onError: function onError(nsresult) {
cbHandleError(callback, nsresult);
}
});
},
@@ -643,17 +580,17 @@ ContentPrefService2.prototype = {
prefs.set(sgroup, sname, undefined);
}
}
this._pbStore.removeAllGroups();
}
cbHandleCompletion(callback, reason);
if (ok) {
for (let [sgroup, sname, ] of prefs) {
- this._notifyPrefRemoved(sgroup, sname, isPrivate);
+ this._cps._notifyPrefRemoved(sgroup, sname, isPrivate);
}
}
},
onError: function onError(nsresult) {
cbHandleError(callback, nsresult);
}
});
},
@@ -732,17 +669,17 @@ ContentPrefService2.prototype = {
prefs.set(sgroup, name, undefined);
this._pbStore.remove(sgroup, name);
}
}
}
cbHandleCompletion(callback, reason);
if (ok) {
for (let [sgroup, , ] of prefs) {
- this._notifyPrefRemoved(sgroup, name, isPrivate);
+ this._cps._notifyPrefRemoved(sgroup, name, isPrivate);
}
}
},
onError: function onError(nsresult) {
cbHandleError(callback, nsresult);
}
});
},
@@ -762,17 +699,17 @@ ContentPrefService2.prototype = {
*
* @param sql The SQL query string.
* @return The cached, possibly new, statement.
*/
_stmt: function CPS2__stmt(sql) {
if (!this._statements)
this._statements = {};
if (!this._statements[sql])
- this._statements[sql] = this._dbConnection.createAsyncStatement(sql);
+ this._statements[sql] = this._cps._dbConnection.createAsyncStatement(sql);
return this._statements[sql];
},
/**
* Executes some async statements.
*
* @param stmts An array of mozIStorageAsyncStatements.
* @param callbacks An object with the following methods:
@@ -786,17 +723,17 @@ ContentPrefService2.prototype = {
* didGetRow: True if onRow was ever called.
* onError(nsresult) (optional)
* Called on error.
* nsresult: The error code.
*/
_execStmts: function CPS2__execStmts(stmts, callbacks) {
let self = this;
let gotRow = false;
- this._dbConnection.executeAsync(stmts, stmts.length, {
+ this._cps._dbConnection.executeAsync(stmts, stmts.length, {
handleResult: function handleResult(results) {
try {
let row = null;
while ((row = results.getNextRow())) {
gotRow = true;
if (callbacks.onRow)
callbacks.onRow.call(self, row);
}
@@ -821,24 +758,16 @@ ContentPrefService2.prototype = {
callbacks.onError.call(self, Cr.NS_ERROR_FAILURE);
} catch (err) {
Cu.reportError(err);
}
}
});
},
- __grouper: null,
- get _grouper() {
- if (!this.__grouper)
- this.__grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
- getService(Ci.nsIContentURIGrouper);
- return this.__grouper;
- },
-
/**
* Parses the domain (the "group", to use the database's term) from the given
* string.
*
* @param groupStr Assumed to be either a string or falsey.
* @return If groupStr is a valid URL string, returns the domain of
* that URL. If groupStr is some other nonempty string,
* returns groupStr itself. Otherwise returns null.
@@ -846,378 +775,85 @@ ContentPrefService2.prototype = {
_parseGroup: function CPS2__parseGroup(groupStr) {
if (!groupStr)
return null;
try {
var groupURI = Services.io.newURI(groupStr);
} catch (err) {
return groupStr;
}
- return this._grouper.group(groupURI);
+ return this._cps._grouper.group(groupURI);
},
_schedule: function CPS2__schedule(fn) {
Services.tm.dispatchToMainThread(fn.bind(this));
},
- // A hash of arrays of observers, indexed by setting name.
- _observers: {},
-
- // An array of generic observers, which observe all settings.
- _genericObservers: [],
-
- addObserverForName: function CPS2_addObserverForName(aName, aObserver) {
- var observers;
- if (aName) {
- if (!this._observers[aName])
- this._observers[aName] = [];
- observers = this._observers[aName];
- } else
- observers = this._genericObservers;
-
- if (observers.indexOf(aObserver) == -1)
- observers.push(aObserver);
- },
-
- removeObserverForName: function CPS2_removeObserverForName(aName, aObserver) {
- var observers;
- if (aName) {
- if (!this._observers[aName])
- return;
- observers = this._observers[aName];
- } else
- observers = this._genericObservers;
-
- if (observers.indexOf(aObserver) != -1)
- observers.splice(observers.indexOf(aObserver), 1);
+ addObserverForName: function CPS2_addObserverForName(name, observer) {
+ this._cps._addObserver(name, observer);
},
- /**
- * Construct a list of observers to notify about a change to some setting,
- * putting setting-specific observers before before generic ones, so observers
- * that initialize individual settings (like the page style controller)
- * execute before observers that display multiple settings and depend on them
- * being initialized first (like the content prefs sidebar).
- */
- _getObservers: function ContentPrefService__getObservers(aName) {
- var observers = [];
-
- if (aName && this._observers[aName])
- observers = observers.concat(this._observers[aName]);
- observers = observers.concat(this._genericObservers);
-
- return observers;
- },
-
- /**
- * Notify all observers about the removal of a preference.
- */
- _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName, aIsPrivate) {
- for (var observer of this._getObservers(aName)) {
- try {
- observer.onContentPrefRemoved(aGroup, aName, aIsPrivate);
- } catch (ex) {
- Cu.reportError(ex);
- }
- }
- },
-
- /**
- * Notify all observers about a preference change.
- */
- _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue, aIsPrivate) {
- for (var observer of this._getObservers(aName)) {
- try {
- observer.onContentPrefSet(aGroup, aName, aValue, aIsPrivate);
- } catch (ex) {
- Cu.reportError(ex);
- }
- }
+ removeObserverForName: function CPS2_removeObserverForName(name, observer) {
+ this._cps._removeObserver(name, observer);
},
extractDomain: function CPS2_extractDomain(str) {
return this._parseGroup(str);
},
/**
* Tests use this as a backchannel by calling it directly.
*
* @param subj This value depends on topic.
* @param topic The backchannel "method" name.
* @param data This value depends on topic.
*/
observe: function CPS2_observe(subj, topic, data) {
switch (topic) {
- case "xpcom-shutdown":
- this._destroy();
- break;
- case "last-pb-context-exited":
- this._pbStore.removeAll();
- break;
case "test:reset":
let fn = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
this._reset(fn);
break;
case "test:db":
let obj = subj.QueryInterface(Ci.xpcIJSWeakReference).get();
- obj.value = this._dbConnection;
+ obj.value = this._cps._dbConnection;
break;
}
},
/**
* Removes all state from the service. Used by tests.
*
* @param callback A function that will be called when done.
*/
_reset: function CPS2__reset(callback) {
this._pbStore.removeAll();
this._cache.removeAll();
- this._observers = {};
- this._genericObservers = [];
+ let cps = this._cps;
+ cps._observers = {};
+ cps._genericObservers = [];
let tables = ["prefs", "groups", "settings"];
let stmts = tables.map(t => this._stmt(`DELETE FROM ${t}`));
this._execStmts(stmts, { onDone: () => callback() });
},
QueryInterface: function CPS2_QueryInterface(iid) {
let supportedIIDs = [
Ci.nsIContentPrefService2,
Ci.nsIObserver,
Ci.nsISupports,
];
if (supportedIIDs.some(i => iid.equals(i)))
return this;
+ if (iid.equals(Ci.nsIContentPrefService))
+ return this._cps;
throw Cr.NS_ERROR_NO_INTERFACE;
},
-
-
- // Database Creation & Access
-
- _dbVersion: 4,
-
- _dbSchema: {
- tables: {
- groups: "id INTEGER PRIMARY KEY, \
- name TEXT NOT NULL",
-
- settings: "id INTEGER PRIMARY KEY, \
- name TEXT NOT NULL",
-
- prefs: "id INTEGER PRIMARY KEY, \
- groupID INTEGER REFERENCES groups(id), \
- settingID INTEGER NOT NULL REFERENCES settings(id), \
- value BLOB, \
- timestamp INTEGER NOT NULL DEFAULT 0" // Storage in seconds, API in ms. 0 for migrated values.
- },
- indices: {
- groups_idx: {
- table: "groups",
- columns: ["name"]
- },
- settings_idx: {
- table: "settings",
- columns: ["name"]
- },
- prefs_idx: {
- table: "prefs",
- columns: ["timestamp", "groupID", "settingID"]
- }
- }
- },
-
- _dbConnection: null,
-
- // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
- // specific migration methods) must be careful not to call any method
- // of the service that assumes the database connection has already been
- // initialized, since it won't be initialized until at the end of _dbInit.
-
- _dbInit: function ContentPrefService__dbInit() {
- var dirService = Cc["@mozilla.org/file/directory_service;1"].
- getService(Ci.nsIProperties);
- var dbFile = dirService.get("ProfD", Ci.nsIFile);
- dbFile.append("content-prefs.sqlite");
-
- var dbService = Cc["@mozilla.org/storage/service;1"].
- getService(Ci.mozIStorageService);
-
- var dbConnection;
-
- if (!dbFile.exists())
- dbConnection = this._dbCreate(dbService, dbFile);
- else {
- try {
- dbConnection = dbService.openDatabase(dbFile);
- } catch (e) {
- // If the connection isn't ready after we open the database, that means
- // the database has been corrupted, so we back it up and then recreate it.
- if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
- throw e;
- dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
- dbConnection);
- }
-
- // Get the version of the schema in the file.
- var version = dbConnection.schemaVersion;
-
- // Try to migrate the schema in the database to the current schema used by
- // the service. If migration fails, back up the database and recreate it.
- if (version != this._dbVersion) {
- try {
- this._dbMigrate(dbConnection, version, this._dbVersion);
- } catch (ex) {
- Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
- dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
- }
- }
- }
-
- // Turn off disk synchronization checking to reduce disk churn and speed up
- // operations when prefs are changed rapidly (such as when a user repeatedly
- // changes the value of the browser zoom setting for a site).
- //
- // Note: this could cause database corruption if the OS crashes or machine
- // loses power before the data gets written to disk, but this is considered
- // a reasonable risk for the not-so-critical data stored in this database.
- //
- // If you really don't want to take this risk, however, just set the
- // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
- // (FULL synchronization), in which case mozStorageConnection::Initialize
- // will use that value, and we won't override it here.
- if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
- dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
-
- this._dbConnection = dbConnection;
- },
-
- _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
- var dbConnection = aDBService.openDatabase(aDBFile);
-
- try {
- this._dbCreateSchema(dbConnection);
- dbConnection.schemaVersion = this._dbVersion;
- } catch (ex) {
- // If we failed to create the database (perhaps because the disk ran out
- // of space), then remove the database file so we don't leave it in some
- // half-created state from which we won't know how to recover.
- dbConnection.close();
- aDBFile.remove(false);
- throw ex;
- }
-
- return dbConnection;
- },
-
- _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
- this._dbCreateTables(aDBConnection);
- this._dbCreateIndices(aDBConnection);
- },
-
- _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
- for (let name in this._dbSchema.tables)
- aDBConnection.createTable(name, this._dbSchema.tables[name]);
- },
-
- _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
- for (let name in this._dbSchema.indices) {
- let index = this._dbSchema.indices[name];
- let statement = `
- CREATE INDEX IF NOT EXISTS ${name} ON ${index.table}
- (${index.columns.join(", ")})
- `;
- aDBConnection.executeSimpleSQL(statement);
- }
- },
-
- _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
- aDBFile,
- aDBConnection) {
- aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
-
- // Close the database, ignoring the "already closed" exception, if any.
- // It'll be open if we're here because of a migration failure but closed
- // if we're here because of database corruption.
- try { aDBConnection.close() } catch (ex) {}
-
- aDBFile.remove(false);
-
- let dbConnection = this._dbCreate(aDBService, aDBFile);
-
- return dbConnection;
- },
-
- _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
- /**
- * Migrations should follow the template rules in bug 1074817 comment 3 which are:
- * 1. Migration should be incremental and non-breaking.
- * 2. It should be idempotent because one can downgrade an upgrade again.
- * On downgrade:
- * 1. Decrement schema version so that upgrade runs the migrations again.
- */
- aDBConnection.beginTransaction();
-
- try {
- /**
- * If the schema version is 0, that means it was never set, which means
- * the database was somehow created without the schema being applied, perhaps
- * because the system ran out of disk space (although we check for this
- * in _createDB) or because some other code created the database file without
- * applying the schema. In any case, recover by simply reapplying the schema.
- */
- if (aOldVersion == 0) {
- this._dbCreateSchema(aDBConnection);
- } else {
- for (let i = aOldVersion; i < aNewVersion; i++) {
- let migrationName = "_dbMigrate" + i + "To" + (i + 1);
- if (typeof this[migrationName] != "function") {
- throw ("no migrator function from version " + aOldVersion + " to version " + aNewVersion);
- }
- this[migrationName](aDBConnection);
- }
- }
- aDBConnection.schemaVersion = aNewVersion;
- aDBConnection.commitTransaction();
- } catch (ex) {
- aDBConnection.rollbackTransaction();
- throw ex;
- }
- },
-
- _dbMigrate1To2: function ContentPrefService___dbMigrate1To2(aDBConnection) {
- aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
- aDBConnection.createTable("groups", this._dbSchema.tables.groups);
- aDBConnection.executeSimpleSQL(`
- INSERT INTO groups (id, name)
- SELECT id, name FROM groupsOld
- `);
-
- aDBConnection.executeSimpleSQL("DROP TABLE groupers");
- aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
- },
-
- _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
- this._dbCreateIndices(aDBConnection);
- },
-
- _dbMigrate3To4: function ContentPrefService__dbMigrate3To4(aDBConnection) {
- // Add timestamp column if it does not exist yet. This operation is idempotent.
- try {
- let stmt = aDBConnection.createStatement("SELECT timestamp FROM prefs");
- stmt.finalize();
- } catch (e) {
- aDBConnection.executeSimpleSQL("ALTER TABLE prefs ADD COLUMN timestamp INTEGER NOT NULL DEFAULT 0");
- }
-
- // To modify prefs_idx drop it and create again.
- aDBConnection.executeSimpleSQL("DROP INDEX IF EXISTS prefs_idx");
- this._dbCreateIndices(aDBConnection);
- },
};
function checkGroupArg(group) {
if (!group || typeof(group) != "string")
throw invalidArg("domain must be nonempty string.");
}
function checkNameArg(name) {
@@ -1235,61 +871,8 @@ function checkCallbackArg(callback, requ
throw invalidArg("callback must be an nsIContentPrefCallback2.");
if (!callback && required)
throw invalidArg("callback must be given.");
}
function invalidArg(msg) {
return Components.Exception(msg, Cr.NS_ERROR_INVALID_ARG);
}
-
-
-function HostnameGrouper() {}
-
-HostnameGrouper.prototype = {
- // XPCOM Plumbing
-
- classID: Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
-
- // nsIContentURIGrouper
-
- group: function HostnameGrouper_group(aURI) {
- var group;
-
- try {
- // Accessing the host property of the URI will throw an exception
- // if the URI is of a type that doesn't have a host property.
- // Otherwise, we manually throw an exception if the host is empty,
- // since the effect is the same (we can't derive a group from it).
-
- group = aURI.host;
- if (!group)
- throw ("can't derive group from host; no host in URI");
- } catch (ex) {
- // If we don't have a host, then use the entire URI (minus the query,
- // reference, and hash, if possible) as the group. This means that URIs
- // like about:mozilla and about:blank will be considered separate groups,
- // but at least they'll be grouped somehow.
-
- // This also means that each individual file: URL will be considered
- // its own group. This seems suboptimal, but so does treating the entire
- // file: URL space as a single group (especially if folks start setting
- // group-specific capabilities prefs).
-
- // XXX Is there something better we can do here?
-
- try {
- var url = aURI.QueryInterface(Ci.nsIURL);
- group = aURI.prePath + url.filePath;
- } catch (ex) {
- group = aURI.spec;
- }
- }
-
- return group;
- }
-};
-
-// XPCOM Plumbing
-
-var components = [ContentPrefService2, HostnameGrouper];
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
--- a/toolkit/components/contentprefs/moz.build
+++ b/toolkit/components/contentprefs/moz.build
@@ -1,28 +1,31 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
XPCSHELL_TESTS_MANIFESTS += [
+ 'tests/unit/xpcshell.ini',
'tests/unit_cps2/xpcshell.ini',
]
MOCHITEST_MANIFESTS += [
'tests/mochitest/mochitest.ini'
]
EXTRA_COMPONENTS += [
- 'ContentPrefService2.js',
- 'ContentPrefService2.manifest',
+ 'nsContentPrefService.js',
+ 'nsContentPrefService.manifest',
]
EXTRA_JS_MODULES += [
+ 'ContentPrefInstance.jsm',
+ 'ContentPrefService2.jsm',
'ContentPrefServiceChild.jsm',
'ContentPrefServiceParent.jsm',
'ContentPrefStore.jsm',
'ContentPrefUtils.jsm',
]
with Files('**'):
BUG_COMPONENT = ('Toolkit', 'Preferences')
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/nsContentPrefService.js
@@ -0,0 +1,1309 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+const CACHE_MAX_GROUP_ENTRIES = 100;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+function ContentPrefService() {
+ if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_CONTENT) {
+ return Cu.import("resource://gre/modules/ContentPrefServiceChild.jsm")
+ .ContentPrefServiceChild;
+ }
+
+ // If this throws an exception, it causes the getService call to fail,
+ // but the next time a consumer tries to retrieve the service, we'll try
+ // to initialize the database again, which might work if the failure
+ // was due to a temporary condition (like being out of disk space).
+ this._dbInit();
+
+ this._observerSvc.addObserver(this, "last-pb-context-exited");
+
+ // Observe shutdown so we can shut down the database connection.
+ this._observerSvc.addObserver(this, "xpcom-shutdown");
+}
+
+Cu.import("resource://gre/modules/ContentPrefStore.jsm");
+const cache = new ContentPrefStore();
+cache.set = function CPS_cache_set(group, name, val) {
+ Object.getPrototypeOf(this).set.apply(this, arguments);
+ let groupCount = this._groups.size;
+ if (groupCount >= CACHE_MAX_GROUP_ENTRIES) {
+ // Clean half of the entries
+ for (let [group, name, ] of this) {
+ this.remove(group, name);
+ groupCount--;
+ if (groupCount < CACHE_MAX_GROUP_ENTRIES / 2)
+ break;
+ }
+ }
+};
+
+const privModeStorage = new ContentPrefStore();
+
+ContentPrefService.prototype = {
+ // XPCOM Plumbing
+
+ classID: Components.ID("{e3f772f3-023f-4b32-b074-36cf0fd5d414}"),
+
+ QueryInterface: function CPS_QueryInterface(iid) {
+ let supportedIIDs = [
+ Ci.nsIContentPrefService,
+ Ci.nsISupports,
+ ];
+ if (supportedIIDs.some(i => iid.equals(i)))
+ return this;
+ if (iid.equals(Ci.nsIContentPrefService2)) {
+ if (!this._contentPrefService2) {
+ let s = {};
+ Cu.import("resource://gre/modules/ContentPrefService2.jsm", s);
+ this._contentPrefService2 = new s.ContentPrefService2(this);
+ }
+ return this._contentPrefService2;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ // Convenience Getters
+
+ // Observer Service
+ __observerSvc: null,
+ get _observerSvc() {
+ if (!this.__observerSvc)
+ this.__observerSvc = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ return this.__observerSvc;
+ },
+
+ // Console Service
+ __consoleSvc: null,
+ get _consoleSvc() {
+ if (!this.__consoleSvc)
+ this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService);
+ return this.__consoleSvc;
+ },
+
+ // Preferences Service
+ __prefSvc: null,
+ get _prefSvc() {
+ if (!this.__prefSvc)
+ this.__prefSvc = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ return this.__prefSvc;
+ },
+
+
+ // Destruction
+
+ _destroy: function ContentPrefService__destroy() {
+ this._observerSvc.removeObserver(this, "xpcom-shutdown");
+ this._observerSvc.removeObserver(this, "last-pb-context-exited");
+
+ // Finalize statements which may have been used asynchronously.
+ // FIXME(696499): put them in an object cache like other components.
+ if (this.__stmtSelectPrefID) {
+ this.__stmtSelectPrefID.finalize();
+ this.__stmtSelectPrefID = null;
+ }
+ if (this.__stmtSelectGlobalPrefID) {
+ this.__stmtSelectGlobalPrefID.finalize();
+ this.__stmtSelectGlobalPrefID = null;
+ }
+ if (this.__stmtInsertPref) {
+ this.__stmtInsertPref.finalize();
+ this.__stmtInsertPref = null;
+ }
+ if (this.__stmtInsertGroup) {
+ this.__stmtInsertGroup.finalize();
+ this.__stmtInsertGroup = null;
+ }
+ if (this.__stmtInsertSetting) {
+ this.__stmtInsertSetting.finalize();
+ this.__stmtInsertSetting = null;
+ }
+ if (this.__stmtSelectGroupID) {
+ this.__stmtSelectGroupID.finalize();
+ this.__stmtSelectGroupID = null;
+ }
+ if (this.__stmtSelectSettingID) {
+ this.__stmtSelectSettingID.finalize();
+ this.__stmtSelectSettingID = null;
+ }
+ if (this.__stmtSelectPref) {
+ this.__stmtSelectPref.finalize();
+ this.__stmtSelectPref = null;
+ }
+ if (this.__stmtSelectGlobalPref) {
+ this.__stmtSelectGlobalPref.finalize();
+ this.__stmtSelectGlobalPref = null;
+ }
+ if (this.__stmtSelectPrefsByName) {
+ this.__stmtSelectPrefsByName.finalize();
+ this.__stmtSelectPrefsByName = null;
+ }
+ if (this.__stmtDeleteSettingIfUnused) {
+ this.__stmtDeleteSettingIfUnused.finalize();
+ this.__stmtDeleteSettingIfUnused = null;
+ }
+ if (this.__stmtSelectPrefs) {
+ this.__stmtSelectPrefs.finalize();
+ this.__stmtSelectPrefs = null;
+ }
+ if (this.__stmtDeleteGroupIfUnused) {
+ this.__stmtDeleteGroupIfUnused.finalize();
+ this.__stmtDeleteGroupIfUnused = null;
+ }
+ if (this.__stmtDeletePref) {
+ this.__stmtDeletePref.finalize();
+ this.__stmtDeletePref = null;
+ }
+ if (this.__stmtUpdatePref) {
+ this.__stmtUpdatePref.finalize();
+ this.__stmtUpdatePref = null;
+ }
+
+ if (this._contentPrefService2)
+ this._contentPrefService2.destroy();
+
+ this._dbConnection.asyncClose(() => {
+ Services.obs.notifyObservers(null, "content-prefs-db-closed");
+ });
+
+ // Delete references to XPCOM components to make sure we don't leak them
+ // (although we haven't observed leakage in tests). Also delete references
+ // in _observers and _genericObservers to avoid cycles with those that
+ // refer to us and don't remove themselves from those observer pools.
+ delete this._observers;
+ delete this._genericObservers;
+ delete this.__consoleSvc;
+ delete this.__grouper;
+ delete this.__observerSvc;
+ delete this.__prefSvc;
+ },
+
+
+ // nsIObserver
+
+ observe: function ContentPrefService_observe(subject, topic, data) {
+ switch (topic) {
+ case "xpcom-shutdown":
+ this._destroy();
+ break;
+ case "last-pb-context-exited":
+ this._privModeStorage.removeAll();
+ break;
+ }
+ },
+
+
+ // in-memory cache and private-browsing stores
+
+ _cache: cache,
+ _privModeStorage: privModeStorage,
+
+ // nsIContentPrefService
+
+ getPref: function ContentPrefService_getPref(aGroup, aName, aContext, aCallback) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ if (this._privModeStorage.has(group, aName)) {
+ let value = this._privModeStorage.get(group, aName);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+ // if we don't have a pref specific to this private mode browsing
+ // session, to try to get one from normal mode
+ }
+
+ if (group == null)
+ return this._selectGlobalPref(aName, aCallback);
+ return this._selectPref(group, aName, aCallback);
+ },
+
+ setPref: function ContentPrefService_setPref(aGroup, aName, aValue, aContext) {
+ warnDeprecated();
+
+ // If the pref is already set to the value, there's nothing more to do.
+ var currentValue = this.getPref(aGroup, aName, aContext);
+ if (typeof currentValue != "undefined") {
+ if (currentValue == aValue)
+ return;
+ }
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ this._privModeStorage.setWithCast(group, aName, aValue);
+ this._notifyPrefSet(group, aName, aValue, aContext.usePrivateBrowsing);
+ return;
+ }
+
+ var settingID = this._selectSettingID(aName) || this._insertSetting(aName);
+ var groupID, prefID;
+ if (group == null) {
+ groupID = null;
+ prefID = this._selectGlobalPrefID(settingID);
+ } else {
+ groupID = this._selectGroupID(group) || this._insertGroup(group);
+ prefID = this._selectPrefID(groupID, settingID);
+ }
+
+ // 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.setWithCast(group, aName, aValue);
+
+ this._notifyPrefSet(group, aName, aValue,
+ aContext ? aContext.usePrivateBrowsing : false);
+ },
+
+ hasPref: function ContentPrefService_hasPref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ // 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, aContext) != "undefined");
+ },
+
+ hasCachedPref: function ContentPrefService_hasCachedPref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ let group = this._parseGroupParam(aGroup);
+ let storage = aContext && aContext.usePrivateBrowsing ? this._privModeStorage : this._cache;
+ return storage.has(group, aName);
+ },
+
+ removePref: function ContentPrefService_removePref(aGroup, aName, aContext) {
+ warnDeprecated();
+
+ // If there's no old value, then there's nothing to remove.
+ if (!this.hasPref(aGroup, aName, aContext))
+ return;
+
+ var group = this._parseGroupParam(aGroup);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ this._privModeStorage.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, true);
+ return;
+ }
+
+ var settingID = this._selectSettingID(aName);
+ var groupID, prefID;
+ if (group == null) {
+ groupID = null;
+ prefID = this._selectGlobalPrefID(settingID);
+ } else {
+ groupID = this._selectGroupID(group);
+ prefID = this._selectPrefID(groupID, settingID);
+ }
+
+ this._deletePref(prefID);
+
+ // Get rid of extraneous records that are no longer being used.
+ this._deleteSettingIfUnused(settingID);
+ if (groupID)
+ this._deleteGroupIfUnused(groupID);
+
+ this._cache.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, false);
+ },
+
+ removeGroupedPrefs: function ContentPrefService_removeGroupedPrefs(aContext) {
+ warnDeprecated();
+
+ // will not delete global preferences
+ if (aContext && aContext.usePrivateBrowsing) {
+ // keep only global prefs
+ this._privModeStorage.removeAllGroups();
+ }
+ this._cache.removeAllGroups();
+ 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)
+ `);
+ this._dbConnection.commitTransaction();
+ } catch (ex) {
+ this._dbConnection.rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ removePrefsByName: function ContentPrefService_removePrefsByName(aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ for (let [group, name, ] of this._privModeStorage) {
+ if (name === aName) {
+ this._privModeStorage.remove(group, aName);
+ this._notifyPrefRemoved(group, aName, true);
+ }
+ }
+ }
+
+ var settingID = this._selectSettingID(aName);
+ if (!settingID)
+ return;
+
+ var selectGroupsStmt = this._dbCreateStatement(`
+ SELECT groups.id AS groupID, groups.name AS groupName
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ WHERE prefs.settingID = :setting
+ `);
+
+ var groupNames = [];
+ var groupIDs = [];
+ try {
+ selectGroupsStmt.params.setting = settingID;
+
+ while (selectGroupsStmt.executeStep()) {
+ groupIDs.push(selectGroupsStmt.row.groupID);
+ groupNames.push(selectGroupsStmt.row.groupName);
+ }
+ } finally {
+ selectGroupsStmt.reset();
+ }
+
+ 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.remove(groupNames[i], aName);
+ if (groupNames[i]) // ie. not null, which will be last (and i == groupIDs.length)
+ this._deleteGroupIfUnused(groupIDs[i]);
+ if (!aContext || !aContext.usePrivateBrowsing) {
+ this._notifyPrefRemoved(groupNames[i], aName, false);
+ }
+ }
+ },
+
+ getPrefs: function ContentPrefService_getPrefs(aGroup, aContext) {
+ warnDeprecated();
+
+ var group = this._parseGroupParam(aGroup);
+ if (aContext && aContext.usePrivateBrowsing) {
+ let prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let [sgroup, sname, sval] of this._privModeStorage) {
+ if (sgroup === group)
+ prefs.setProperty(sname, sval);
+ }
+ return prefs;
+ }
+
+ if (group == null)
+ return this._selectGlobalPrefs();
+ return this._selectPrefs(group);
+ },
+
+ getPrefsByName: function ContentPrefService_getPrefsByName(aName, aContext) {
+ warnDeprecated();
+
+ if (!aName)
+ throw Components.Exception("aName cannot be null or an empty string",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+
+ if (aContext && aContext.usePrivateBrowsing) {
+ let prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+ for (let [sgroup, sname, sval] of this._privModeStorage) {
+ if (sname === aName)
+ prefs.setProperty(sgroup, sval);
+ }
+ return prefs;
+ }
+
+ return this._selectPrefsByName(aName);
+ },
+
+ // A hash of arrays of observers, indexed by setting name.
+ _observers: {},
+
+ // An array of generic observers, which observe all settings.
+ _genericObservers: [],
+
+ addObserver: function ContentPrefService_addObserver(aName, aObserver) {
+ warnDeprecated();
+ this._addObserver.apply(this, arguments);
+ },
+
+ _addObserver: function ContentPrefService__addObserver(aName, aObserver) {
+ var observers;
+ if (aName) {
+ if (!this._observers[aName])
+ this._observers[aName] = [];
+ observers = this._observers[aName];
+ } else
+ observers = this._genericObservers;
+
+ if (observers.indexOf(aObserver) == -1)
+ observers.push(aObserver);
+ },
+
+ removeObserver: function ContentPrefService_removeObserver(aName, aObserver) {
+ warnDeprecated();
+ this._removeObserver.apply(this, arguments);
+ },
+
+ _removeObserver: function ContentPrefService__removeObserver(aName, aObserver) {
+ var observers;
+ if (aName) {
+ if (!this._observers[aName])
+ return;
+ observers = this._observers[aName];
+ } else
+ observers = this._genericObservers;
+
+ if (observers.indexOf(aObserver) != -1)
+ observers.splice(observers.indexOf(aObserver), 1);
+ },
+
+ /**
+ * Construct a list of observers to notify about a change to some setting,
+ * putting setting-specific observers before before generic ones, so observers
+ * that initialize individual settings (like the page style controller)
+ * execute before observers that display multiple settings and depend on them
+ * being initialized first (like the content prefs sidebar).
+ */
+ _getObservers: function ContentPrefService__getObservers(aName) {
+ var observers = [];
+
+ if (aName && this._observers[aName])
+ observers = observers.concat(this._observers[aName]);
+ observers = observers.concat(this._genericObservers);
+
+ return observers;
+ },
+
+ /**
+ * Notify all observers about the removal of a preference.
+ */
+ _notifyPrefRemoved: function ContentPrefService__notifyPrefRemoved(aGroup, aName, aIsPrivate) {
+ for (var observer of this._getObservers(aName)) {
+ try {
+ observer.onContentPrefRemoved(aGroup, aName, aIsPrivate);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ /**
+ * Notify all observers about a preference change.
+ */
+ _notifyPrefSet: function ContentPrefService__notifyPrefSet(aGroup, aName, aValue, aIsPrivate) {
+ for (var observer of this._getObservers(aName)) {
+ try {
+ observer.onContentPrefSet(aGroup, aName, aValue, aIsPrivate);
+ } catch (ex) {
+ Cu.reportError(ex);
+ }
+ }
+ },
+
+ get grouper() {
+ warnDeprecated();
+ return this._grouper;
+ },
+ __grouper: null,
+ get _grouper() {
+ if (!this.__grouper)
+ this.__grouper = Cc["@mozilla.org/content-pref/hostname-grouper;1"].
+ getService(Ci.nsIContentURIGrouper);
+ return this.__grouper;
+ },
+
+ get DBConnection() {
+ warnDeprecated();
+ return this._dbConnection;
+ },
+
+
+ // Data Retrieval & Modification
+
+ __stmtSelectPref: null,
+ get _stmtSelectPref() {
+ if (!this.__stmtSelectPref)
+ this.__stmtSelectPref = this._dbCreateStatement(`
+ SELECT prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE groups.name = :group
+ AND settings.name = :setting
+ `);
+
+ return this.__stmtSelectPref;
+ },
+
+ _scheduleCallback(func) {
+ let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
+ tm.dispatchToMainThread(func);
+ },
+
+ _selectPref: function ContentPrefService__selectPref(aGroup, aSetting, aCallback) {
+ let value = undefined;
+ if (this._cache.has(aGroup, aSetting)) {
+ value = this._cache.get(aGroup, aSetting);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+
+ try {
+ this._stmtSelectPref.params.group = aGroup;
+ this._stmtSelectPref.params.setting = aSetting;
+
+ if (aCallback) {
+ let cache = this._cache;
+ new AsyncStatement(this._stmtSelectPref).execute({onResult(aResult) {
+ cache.set(aGroup, aSetting, aResult);
+ aCallback.onResult(aResult);
+ }});
+ } else {
+ if (this._stmtSelectPref.executeStep()) {
+ value = this._stmtSelectPref.row.value;
+ }
+ this._cache.set(aGroup, aSetting, value);
+ }
+ } finally {
+ this._stmtSelectPref.reset();
+ }
+
+ return value;
+ },
+
+ __stmtSelectGlobalPref: null,
+ get _stmtSelectGlobalPref() {
+ if (!this.__stmtSelectGlobalPref)
+ this.__stmtSelectGlobalPref = this._dbCreateStatement(`
+ SELECT prefs.value AS value
+ FROM prefs
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE prefs.groupID IS NULL
+ AND settings.name = :name
+ `);
+
+ return this.__stmtSelectGlobalPref;
+ },
+
+ _selectGlobalPref: function ContentPrefService__selectGlobalPref(aName, aCallback) {
+ let value = undefined;
+ if (this._cache.has(null, aName)) {
+ value = this._cache.get(null, aName);
+ if (aCallback) {
+ this._scheduleCallback(function() { aCallback.onResult(value); });
+ return undefined;
+ }
+ return value;
+ }
+
+ try {
+ this._stmtSelectGlobalPref.params.name = aName;
+
+ if (aCallback) {
+ let cache = this._cache;
+ new AsyncStatement(this._stmtSelectGlobalPref).execute({onResult(aResult) {
+ cache.set(null, aName, aResult);
+ aCallback.onResult(aResult);
+ }});
+ } else {
+ if (this._stmtSelectGlobalPref.executeStep()) {
+ value = this._stmtSelectGlobalPref.row.value;
+ }
+ this._cache.set(null, aName, value);
+ }
+ } finally {
+ this._stmtSelectGlobalPref.reset();
+ }
+
+ return value;
+ },
+
+ __stmtSelectGroupID: null,
+ get _stmtSelectGroupID() {
+ if (!this.__stmtSelectGroupID)
+ this.__stmtSelectGroupID = this._dbCreateStatement(`
+ SELECT groups.id AS id
+ FROM groups
+ WHERE groups.name = :name
+ `);
+
+ return this.__stmtSelectGroupID;
+ },
+
+ _selectGroupID: function ContentPrefService__selectGroupID(aName) {
+ var id;
+
+ try {
+ this._stmtSelectGroupID.params.name = aName;
+
+ if (this._stmtSelectGroupID.executeStep())
+ id = this._stmtSelectGroupID.row.id;
+ } finally {
+ this._stmtSelectGroupID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertGroup: null,
+ get _stmtInsertGroup() {
+ if (!this.__stmtInsertGroup)
+ this.__stmtInsertGroup = this._dbCreateStatement(
+ "INSERT INTO groups (name) VALUES (:name)"
+ );
+
+ return this.__stmtInsertGroup;
+ },
+
+ _insertGroup: function ContentPrefService__insertGroup(aName) {
+ this._stmtInsertGroup.params.name = aName;
+ this._stmtInsertGroup.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtSelectSettingID: null,
+ get _stmtSelectSettingID() {
+ if (!this.__stmtSelectSettingID)
+ this.__stmtSelectSettingID = this._dbCreateStatement(
+ "SELECT id FROM settings WHERE name = :name"
+ );
+
+ return this.__stmtSelectSettingID;
+ },
+
+ _selectSettingID: function ContentPrefService__selectSettingID(aName) {
+ var id;
+
+ try {
+ this._stmtSelectSettingID.params.name = aName;
+
+ if (this._stmtSelectSettingID.executeStep())
+ id = this._stmtSelectSettingID.row.id;
+ } finally {
+ this._stmtSelectSettingID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertSetting: null,
+ get _stmtInsertSetting() {
+ if (!this.__stmtInsertSetting)
+ this.__stmtInsertSetting = this._dbCreateStatement(
+ "INSERT INTO settings (name) VALUES (:name)"
+ );
+
+ return this.__stmtInsertSetting;
+ },
+
+ _insertSetting: function ContentPrefService__insertSetting(aName) {
+ this._stmtInsertSetting.params.name = aName;
+ this._stmtInsertSetting.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtSelectPrefID: null,
+ get _stmtSelectPrefID() {
+ if (!this.__stmtSelectPrefID)
+ this.__stmtSelectPrefID = this._dbCreateStatement(
+ "SELECT id FROM prefs WHERE groupID = :groupID AND settingID = :settingID"
+ );
+
+ return this.__stmtSelectPrefID;
+ },
+
+ _selectPrefID: function ContentPrefService__selectPrefID(aGroupID, aSettingID) {
+ var id;
+
+ try {
+ this._stmtSelectPrefID.params.groupID = aGroupID;
+ this._stmtSelectPrefID.params.settingID = aSettingID;
+
+ if (this._stmtSelectPrefID.executeStep())
+ id = this._stmtSelectPrefID.row.id;
+ } finally {
+ this._stmtSelectPrefID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtSelectGlobalPrefID: null,
+ get _stmtSelectGlobalPrefID() {
+ if (!this.__stmtSelectGlobalPrefID)
+ this.__stmtSelectGlobalPrefID = this._dbCreateStatement(
+ "SELECT id FROM prefs WHERE groupID IS NULL AND settingID = :settingID"
+ );
+
+ return this.__stmtSelectGlobalPrefID;
+ },
+
+ _selectGlobalPrefID: function ContentPrefService__selectGlobalPrefID(aSettingID) {
+ var id;
+
+ try {
+ this._stmtSelectGlobalPrefID.params.settingID = aSettingID;
+
+ if (this._stmtSelectGlobalPrefID.executeStep())
+ id = this._stmtSelectGlobalPrefID.row.id;
+ } finally {
+ this._stmtSelectGlobalPrefID.reset();
+ }
+
+ return id;
+ },
+
+ __stmtInsertPref: null,
+ get _stmtInsertPref() {
+ if (!this.__stmtInsertPref)
+ this.__stmtInsertPref = this._dbCreateStatement(`
+ INSERT INTO prefs (groupID, settingID, value)
+ VALUES (:groupID, :settingID, :value)
+ `);
+
+ return this.__stmtInsertPref;
+ },
+
+ _insertPref: function ContentPrefService__insertPref(aGroupID, aSettingID, aValue) {
+ this._stmtInsertPref.params.groupID = aGroupID;
+ this._stmtInsertPref.params.settingID = aSettingID;
+ this._stmtInsertPref.params.value = aValue;
+ this._stmtInsertPref.execute();
+ return this._dbConnection.lastInsertRowID;
+ },
+
+ __stmtUpdatePref: null,
+ get _stmtUpdatePref() {
+ if (!this.__stmtUpdatePref)
+ this.__stmtUpdatePref = this._dbCreateStatement(
+ "UPDATE prefs SET value = :value WHERE id = :id"
+ );
+
+ return this.__stmtUpdatePref;
+ },
+
+ _updatePref: function ContentPrefService__updatePref(aPrefID, aValue) {
+ this._stmtUpdatePref.params.id = aPrefID;
+ this._stmtUpdatePref.params.value = aValue;
+ this._stmtUpdatePref.execute();
+ },
+
+ __stmtDeletePref: null,
+ get _stmtDeletePref() {
+ if (!this.__stmtDeletePref)
+ this.__stmtDeletePref = this._dbCreateStatement(
+ "DELETE FROM prefs WHERE id = :id"
+ );
+
+ return this.__stmtDeletePref;
+ },
+
+ _deletePref: function ContentPrefService__deletePref(aPrefID) {
+ this._stmtDeletePref.params.id = aPrefID;
+ this._stmtDeletePref.execute();
+ },
+
+ __stmtDeleteSettingIfUnused: null,
+ get _stmtDeleteSettingIfUnused() {
+ if (!this.__stmtDeleteSettingIfUnused)
+ this.__stmtDeleteSettingIfUnused = this._dbCreateStatement(`
+ DELETE FROM settings WHERE id = :id
+ AND id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `);
+
+ return this.__stmtDeleteSettingIfUnused;
+ },
+
+ _deleteSettingIfUnused: function ContentPrefService__deleteSettingIfUnused(aSettingID) {
+ this._stmtDeleteSettingIfUnused.params.id = aSettingID;
+ this._stmtDeleteSettingIfUnused.execute();
+ },
+
+ __stmtDeleteGroupIfUnused: null,
+ get _stmtDeleteGroupIfUnused() {
+ if (!this.__stmtDeleteGroupIfUnused)
+ this.__stmtDeleteGroupIfUnused = this._dbCreateStatement(`
+ DELETE FROM groups WHERE id = :id
+ AND id NOT IN (SELECT DISTINCT groupID FROM prefs)
+ `);
+
+ return this.__stmtDeleteGroupIfUnused;
+ },
+
+ _deleteGroupIfUnused: function ContentPrefService__deleteGroupIfUnused(aGroupID) {
+ this._stmtDeleteGroupIfUnused.params.id = aGroupID;
+ this._stmtDeleteGroupIfUnused.execute();
+ },
+
+ __stmtSelectPrefs: null,
+ get _stmtSelectPrefs() {
+ if (!this.__stmtSelectPrefs)
+ this.__stmtSelectPrefs = this._dbCreateStatement(`
+ SELECT settings.name AS name, prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE groups.name = :group
+ `);
+
+ return this.__stmtSelectPrefs;
+ },
+
+ _selectPrefs: function ContentPrefService__selectPrefs(aGroup) {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ this._stmtSelectPrefs.params.group = aGroup;
+
+ while (this._stmtSelectPrefs.executeStep())
+ prefs.setProperty(this._stmtSelectPrefs.row.name,
+ this._stmtSelectPrefs.row.value);
+ } finally {
+ this._stmtSelectPrefs.reset();
+ }
+
+ return prefs;
+ },
+
+ __stmtSelectGlobalPrefs: null,
+ get _stmtSelectGlobalPrefs() {
+ if (!this.__stmtSelectGlobalPrefs)
+ this.__stmtSelectGlobalPrefs = this._dbCreateStatement(`
+ SELECT settings.name AS name, prefs.value AS value
+ FROM prefs
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE prefs.groupID IS NULL
+ `);
+
+ return this.__stmtSelectGlobalPrefs;
+ },
+
+ _selectGlobalPrefs: function ContentPrefService__selectGlobalPrefs() {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ while (this._stmtSelectGlobalPrefs.executeStep())
+ prefs.setProperty(this._stmtSelectGlobalPrefs.row.name,
+ this._stmtSelectGlobalPrefs.row.value);
+ } finally {
+ this._stmtSelectGlobalPrefs.reset();
+ }
+
+ return prefs;
+ },
+
+ __stmtSelectPrefsByName: null,
+ get _stmtSelectPrefsByName() {
+ if (!this.__stmtSelectPrefsByName)
+ this.__stmtSelectPrefsByName = this._dbCreateStatement(`
+ SELECT groups.name AS groupName, prefs.value AS value
+ FROM prefs
+ JOIN groups ON prefs.groupID = groups.id
+ JOIN settings ON prefs.settingID = settings.id
+ WHERE settings.name = :setting
+ `);
+
+ return this.__stmtSelectPrefsByName;
+ },
+
+ _selectPrefsByName: function ContentPrefService__selectPrefsByName(aName) {
+ var prefs = Cc["@mozilla.org/hash-property-bag;1"].
+ createInstance(Ci.nsIWritablePropertyBag);
+
+ try {
+ this._stmtSelectPrefsByName.params.setting = aName;
+
+ while (this._stmtSelectPrefsByName.executeStep())
+ prefs.setProperty(this._stmtSelectPrefsByName.row.groupName,
+ this._stmtSelectPrefsByName.row.value);
+ } finally {
+ this._stmtSelectPrefsByName.reset();
+ }
+
+ var global = this._selectGlobalPref(aName);
+ if (typeof global != "undefined") {
+ prefs.setProperty(null, global);
+ }
+
+ return prefs;
+ },
+
+
+ // Database Creation & Access
+
+ _dbVersion: 4,
+
+ _dbSchema: {
+ tables: {
+ groups: "id INTEGER PRIMARY KEY, \
+ name TEXT NOT NULL",
+
+ settings: "id INTEGER PRIMARY KEY, \
+ name TEXT NOT NULL",
+
+ prefs: "id INTEGER PRIMARY KEY, \
+ groupID INTEGER REFERENCES groups(id), \
+ settingID INTEGER NOT NULL REFERENCES settings(id), \
+ value BLOB, \
+ timestamp INTEGER NOT NULL DEFAULT 0" // Storage in seconds, API in ms. 0 for migrated values.
+ },
+ indices: {
+ groups_idx: {
+ table: "groups",
+ columns: ["name"]
+ },
+ settings_idx: {
+ table: "settings",
+ columns: ["name"]
+ },
+ prefs_idx: {
+ table: "prefs",
+ columns: ["timestamp", "groupID", "settingID"]
+ }
+ }
+ },
+
+ _dbConnection: null,
+
+ _dbCreateStatement: function ContentPrefService__dbCreateStatement(aSQLString) {
+ try {
+ var statement = this._dbConnection.createStatement(aSQLString);
+ } catch (ex) {
+ Cu.reportError("error creating statement " + aSQLString + ": " +
+ this._dbConnection.lastError + " - " +
+ this._dbConnection.lastErrorString);
+ throw ex;
+ }
+
+ return statement;
+ },
+
+ // _dbInit and the methods it calls (_dbCreate, _dbMigrate, and version-
+ // specific migration methods) must be careful not to call any method
+ // of the service that assumes the database connection has already been
+ // initialized, since it won't be initialized until at the end of _dbInit.
+
+ _dbInit: function ContentPrefService__dbInit() {
+ var dirService = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ var dbFile = dirService.get("ProfD", Ci.nsIFile);
+ dbFile.append("content-prefs.sqlite");
+
+ var dbService = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+
+ var dbConnection;
+
+ if (!dbFile.exists())
+ dbConnection = this._dbCreate(dbService, dbFile);
+ else {
+ try {
+ dbConnection = dbService.openDatabase(dbFile);
+ } catch (e) {
+ // If the connection isn't ready after we open the database, that means
+ // the database has been corrupted, so we back it up and then recreate it.
+ if (e.result != Cr.NS_ERROR_FILE_CORRUPTED)
+ throw e;
+ dbConnection = this._dbBackUpAndRecreate(dbService, dbFile,
+ dbConnection);
+ }
+
+ // Get the version of the schema in the file.
+ var version = dbConnection.schemaVersion;
+
+ // Try to migrate the schema in the database to the current schema used by
+ // the service. If migration fails, back up the database and recreate it.
+ if (version != this._dbVersion) {
+ try {
+ this._dbMigrate(dbConnection, version, this._dbVersion);
+ } catch (ex) {
+ Cu.reportError("error migrating DB: " + ex + "; backing up and recreating");
+ dbConnection = this._dbBackUpAndRecreate(dbService, dbFile, dbConnection);
+ }
+ }
+ }
+
+ // Turn off disk synchronization checking to reduce disk churn and speed up
+ // operations when prefs are changed rapidly (such as when a user repeatedly
+ // changes the value of the browser zoom setting for a site).
+ //
+ // Note: this could cause database corruption if the OS crashes or machine
+ // loses power before the data gets written to disk, but this is considered
+ // a reasonable risk for the not-so-critical data stored in this database.
+ //
+ // If you really don't want to take this risk, however, just set the
+ // toolkit.storage.synchronous pref to 1 (NORMAL synchronization) or 2
+ // (FULL synchronization), in which case mozStorageConnection::Initialize
+ // will use that value, and we won't override it here.
+ if (!this._prefSvc.prefHasUserValue("toolkit.storage.synchronous"))
+ dbConnection.executeSimpleSQL("PRAGMA synchronous = OFF");
+
+ this._dbConnection = dbConnection;
+ },
+
+ _dbCreate: function ContentPrefService__dbCreate(aDBService, aDBFile) {
+ var dbConnection = aDBService.openDatabase(aDBFile);
+
+ try {
+ this._dbCreateSchema(dbConnection);
+ dbConnection.schemaVersion = this._dbVersion;
+ } catch (ex) {
+ // If we failed to create the database (perhaps because the disk ran out
+ // of space), then remove the database file so we don't leave it in some
+ // half-created state from which we won't know how to recover.
+ dbConnection.close();
+ aDBFile.remove(false);
+ throw ex;
+ }
+
+ return dbConnection;
+ },
+
+ _dbCreateSchema: function ContentPrefService__dbCreateSchema(aDBConnection) {
+ this._dbCreateTables(aDBConnection);
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _dbCreateTables: function ContentPrefService__dbCreateTables(aDBConnection) {
+ for (let name in this._dbSchema.tables)
+ aDBConnection.createTable(name, this._dbSchema.tables[name]);
+ },
+
+ _dbCreateIndices: function ContentPrefService__dbCreateIndices(aDBConnection) {
+ for (let name in this._dbSchema.indices) {
+ let index = this._dbSchema.indices[name];
+ let statement = `
+ CREATE INDEX IF NOT EXISTS ${name} ON ${index.table}
+ (${index.columns.join(", ")})
+ `;
+ aDBConnection.executeSimpleSQL(statement);
+ }
+ },
+
+ _dbBackUpAndRecreate: function ContentPrefService__dbBackUpAndRecreate(aDBService,
+ aDBFile,
+ aDBConnection) {
+ aDBService.backupDatabaseFile(aDBFile, "content-prefs.sqlite.corrupt");
+
+ // Close the database, ignoring the "already closed" exception, if any.
+ // It'll be open if we're here because of a migration failure but closed
+ // if we're here because of database corruption.
+ try { aDBConnection.close() } catch (ex) {}
+
+ aDBFile.remove(false);
+
+ let dbConnection = this._dbCreate(aDBService, aDBFile);
+
+ return dbConnection;
+ },
+
+ _dbMigrate: function ContentPrefService__dbMigrate(aDBConnection, aOldVersion, aNewVersion) {
+ /**
+ * Migrations should follow the template rules in bug 1074817 comment 3 which are:
+ * 1. Migration should be incremental and non-breaking.
+ * 2. It should be idempotent because one can downgrade an upgrade again.
+ * On downgrade:
+ * 1. Decrement schema version so that upgrade runs the migrations again.
+ */
+ aDBConnection.beginTransaction();
+
+ try {
+ /**
+ * If the schema version is 0, that means it was never set, which means
+ * the database was somehow created without the schema being applied, perhaps
+ * because the system ran out of disk space (although we check for this
+ * in _createDB) or because some other code created the database file without
+ * applying the schema. In any case, recover by simply reapplying the schema.
+ */
+ if (aOldVersion == 0) {
+ this._dbCreateSchema(aDBConnection);
+ } else {
+ for (let i = aOldVersion; i < aNewVersion; i++) {
+ let migrationName = "_dbMigrate" + i + "To" + (i + 1);
+ if (typeof this[migrationName] != "function") {
+ throw ("no migrator function from version " + aOldVersion + " to version " + aNewVersion);
+ }
+ this[migrationName](aDBConnection);
+ }
+ }
+ aDBConnection.schemaVersion = aNewVersion;
+ aDBConnection.commitTransaction();
+ } catch (ex) {
+ aDBConnection.rollbackTransaction();
+ throw ex;
+ }
+ },
+
+ _dbMigrate1To2: function ContentPrefService___dbMigrate1To2(aDBConnection) {
+ aDBConnection.executeSimpleSQL("ALTER TABLE groups RENAME TO groupsOld");
+ aDBConnection.createTable("groups", this._dbSchema.tables.groups);
+ aDBConnection.executeSimpleSQL(`
+ INSERT INTO groups (id, name)
+ SELECT id, name FROM groupsOld
+ `);
+
+ aDBConnection.executeSimpleSQL("DROP TABLE groupers");
+ aDBConnection.executeSimpleSQL("DROP TABLE groupsOld");
+ },
+
+ _dbMigrate2To3: function ContentPrefService__dbMigrate2To3(aDBConnection) {
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _dbMigrate3To4: function ContentPrefService__dbMigrate3To4(aDBConnection) {
+ // Add timestamp column if it does not exist yet. This operation is idempotent.
+ try {
+ let stmt = aDBConnection.createStatement("SELECT timestamp FROM prefs");
+ stmt.finalize();
+ } catch (e) {
+ aDBConnection.executeSimpleSQL("ALTER TABLE prefs ADD COLUMN timestamp INTEGER NOT NULL DEFAULT 0");
+ }
+
+ // To modify prefs_idx drop it and create again.
+ aDBConnection.executeSimpleSQL("DROP INDEX IF EXISTS prefs_idx");
+ this._dbCreateIndices(aDBConnection);
+ },
+
+ _parseGroupParam: function ContentPrefService__parseGroupParam(aGroup) {
+ if (aGroup == null)
+ return null;
+ if (aGroup.constructor.name == "String")
+ return aGroup.toString();
+ if (aGroup instanceof Ci.nsIURI)
+ return this.grouper.group(aGroup);
+
+ throw Components.Exception("aGroup is not a string, nsIURI or null",
+ Cr.NS_ERROR_ILLEGAL_VALUE);
+ },
+};
+
+function warnDeprecated() {
+ let Deprecated = Cu.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ Deprecated.warning("nsIContentPrefService is deprecated. Please use nsIContentPrefService2 instead.",
+ "https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/nsIContentPrefService2",
+ Components.stack.caller);
+}
+
+
+function HostnameGrouper() {}
+
+HostnameGrouper.prototype = {
+ // XPCOM Plumbing
+
+ classID: Components.ID("{8df290ae-dcaa-4c11-98a5-2429a4dc97bb}"),
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentURIGrouper]),
+
+ // nsIContentURIGrouper
+
+ group: function HostnameGrouper_group(aURI) {
+ var group;
+
+ try {
+ // Accessing the host property of the URI will throw an exception
+ // if the URI is of a type that doesn't have a host property.
+ // Otherwise, we manually throw an exception if the host is empty,
+ // since the effect is the same (we can't derive a group from it).
+
+ group = aURI.host;
+ if (!group)
+ throw ("can't derive group from host; no host in URI");
+ } catch (ex) {
+ // If we don't have a host, then use the entire URI (minus the query,
+ // reference, and hash, if possible) as the group. This means that URIs
+ // like about:mozilla and about:blank will be considered separate groups,
+ // but at least they'll be grouped somehow.
+
+ // This also means that each individual file: URL will be considered
+ // its own group. This seems suboptimal, but so does treating the entire
+ // file: URL space as a single group (especially if folks start setting
+ // group-specific capabilities prefs).
+
+ // XXX Is there something better we can do here?
+
+ try {
+ var url = aURI.QueryInterface(Ci.nsIURL);
+ group = aURI.prePath + url.filePath;
+ } catch (ex) {
+ group = aURI.spec;
+ }
+ }
+
+ return group;
+ }
+};
+
+function AsyncStatement(aStatement) {
+ this.stmt = aStatement;
+}
+
+AsyncStatement.prototype = {
+ execute: function AsyncStmt_execute(aCallback) {
+ let stmt = this.stmt;
+ stmt.executeAsync({
+ _callback: aCallback,
+ _hadResult: false,
+ handleResult(aResult) {
+ this._hadResult = true;
+ if (this._callback) {
+ let row = aResult.getNextRow();
+ this._callback.onResult(row.getResultByName("value"));
+ }
+ },
+ handleCompletion(aReason) {
+ if (!this._hadResult && this._callback &&
+ aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED)
+ this._callback.onResult(undefined);
+ },
+ handleError(aError) {}
+ });
+ }
+};
+
+// XPCOM Plumbing
+
+var components = [ContentPrefService, HostnameGrouper];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
rename from toolkit/components/contentprefs/ContentPrefService2.manifest
rename to toolkit/components/contentprefs/nsContentPrefService.manifest
--- a/toolkit/components/contentprefs/ContentPrefService2.manifest
+++ b/toolkit/components/contentprefs/nsContentPrefService.manifest
@@ -1,5 +1,5 @@
-component {e3f772f3-023f-4b32-b074-36cf0fd5d414} ContentPrefService2.js
+component {e3f772f3-023f-4b32-b074-36cf0fd5d414} nsContentPrefService.js
contract @mozilla.org/content-pref/service;1 {e3f772f3-023f-4b32-b074-36cf0fd5d414}
-component {8df290ae-dcaa-4c11-98a5-2429a4dc97bb} ContentPrefService2.js
+component {8df290ae-dcaa-4c11-98a5-2429a4dc97bb} nsContentPrefService.js
contract @mozilla.org/content-pref/hostname-grouper;1 {8df290ae-dcaa-4c11-98a5-2429a4dc97bb}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/.eslintrc.js
@@ -0,0 +1,7 @@
+"use strict";
+
+module.exports = {
+ "extends": [
+ "plugin:mozilla/xpcshell-test"
+ ]
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/head_contentPrefs.js
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Inspired by the Places infrastructure in head_bookmarks.js
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/ContentPrefInstance.jsm");
+Cu.import("resource://testing-common/TestUtils.jsm");
+
+const CONTENT_PREFS_DB_FILENAME = "content-prefs.sqlite";
+const CONTENT_PREFS_BACKUP_DB_FILENAME = "content-prefs.sqlite.corrupt";
+
+var ContentPrefTest = {
+ // Convenience Getters
+
+ __dirSvc: null,
+ get _dirSvc() {
+ if (!this.__dirSvc)
+ this.__dirSvc = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties);
+ return this.__dirSvc;
+ },
+
+ __consoleSvc: null,
+ get _consoleSvc() {
+ if (!this.__consoleSvc)
+ this.__consoleSvc = Cc["@mozilla.org/consoleservice;1"].
+ getService(Ci.nsIConsoleService);
+ return this.__consoleSvc;
+ },
+
+ __ioSvc: null,
+ get _ioSvc() {
+ if (!this.__ioSvc)
+ this.__ioSvc = Cc["@mozilla.org/network/io-service;1"].
+ getService(Ci.nsIIOService);
+ return this.__ioSvc;
+ },
+
+
+ // nsISupports
+
+ interfaces: [Ci.nsIDirectoryServiceProvider, Ci.nsISupports],
+
+ QueryInterface: function ContentPrefTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+
+ // nsIDirectoryServiceProvider
+
+ getFile: function ContentPrefTest_getFile(property, persistent) {
+ persistent.value = true;
+
+ if (property == "ProfD")
+ return this._dirSvc.get("CurProcD", Ci.nsIFile);
+
+ // This causes extraneous errors to show up in the log when the directory
+ // service asks us first for CurProcD and MozBinD. I wish there was a way
+ // to suppress those errors.
+ throw Cr.NS_ERROR_FAILURE;
+ },
+
+
+ // Utilities
+
+ getURI: function ContentPrefTest_getURI(spec) {
+ return this._ioSvc.newURI(spec);
+ },
+
+ /**
+ * Get the profile directory.
+ */
+ getProfileDir: function ContentPrefTest_getProfileDir() {
+ // do_get_profile can be only called from a parent process
+ if (runningInParent) {
+ return do_get_profile();
+ }
+ // if running in a content process, this just returns the path
+ // profile was initialized in the ipc head file
+ let env = Components.classes["@mozilla.org/process/environment;1"]
+ .getService(Components.interfaces.nsIEnvironment);
+ // the python harness sets this in the environment for us
+ let profd = env.get("XPCSHELL_TEST_PROFILE_DIR");
+ let file = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ file.initWithPath(profd);
+ return file;
+ },
+
+ /**
+ * Delete the content pref service's persistent datastore. We do this before
+ * and after running tests to make sure we start from scratch each time. We
+ * also do it during the database creation, schema migration, and backup tests.
+ */
+ deleteDatabase: function ContentPrefTest_deleteDatabase() {
+ var file = this.getProfileDir();
+ file.append(CONTENT_PREFS_DB_FILENAME);
+ if (file.exists())
+ try { file.remove(false); } catch (e) { /* stupid windows box */ }
+ return file;
+ },
+
+ /**
+ * Delete the backup of the content pref service's persistent datastore.
+ * We do this during the database creation, schema migration, and backup tests.
+ */
+ deleteBackupDatabase: function ContentPrefTest_deleteBackupDatabase() {
+ var file = this.getProfileDir();
+ file.append(CONTENT_PREFS_BACKUP_DB_FILENAME);
+ if (file.exists())
+ file.remove(false);
+ return file;
+ },
+
+ /**
+ * Log a message to the console and the test log.
+ */
+ log: function ContentPrefTest_log(message) {
+ message = "*** ContentPrefTest: " + message;
+ this._consoleSvc.logStringMessage(message);
+ print(message);
+ }
+
+};
+
+let loadContext = Cc["@mozilla.org/loadcontext;1"].
+ createInstance(Ci.nsILoadContext);
+let privateLoadContext = Cc["@mozilla.org/privateloadcontext;1"].
+ createInstance(Ci.nsILoadContext);
+function enterPBMode(cps) {
+ cps.loadContext = privateLoadContext;
+}
+function exitPBMode(cps) {
+ cps.loadContext = loadContext;
+ Services.obs.notifyObservers(null, "last-pb-context-exited");
+}
+
+ContentPrefTest.deleteDatabase();
+
+do_register_cleanup(function() {
+ ContentPrefTest.deleteDatabase();
+ ContentPrefTest.__dirSvc = null;
+});
+
+function inChildProcess() {
+ var appInfo = Cc["@mozilla.org/xre/app-info;1"];
+ if (!appInfo || appInfo.getService(Ci.nsIXULRuntime).processType ==
+ Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT) {
+ return false;
+ }
+ return true;
+}
+
+// Turn on logging for the content preferences service so we can troubleshoot
+// problems with the tests. Note that we cannot do this in a child process
+// without crashing (but we don't need it anyhow)
+if (!inChildProcess()) {
+ var prefBranch = Cc["@mozilla.org/preferences-service;1"].
+ getService(Ci.nsIPrefBranch);
+ prefBranch.setBoolPref("browser.preferences.content.log", true);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_bug248970.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ ContentPrefTest.deleteDatabase();
+ var cp = new ContentPrefInstance(loadContext);
+ do_check_neq(cp, null, "Retrieving the content prefs service failed");
+
+ try {
+ const uri1 = ContentPrefTest.getURI("http://www.example.com/");
+ const uri2 = ContentPrefTest.getURI("http://www.anotherexample.com/");
+ const pref_name = "browser.content.full-zoom";
+ const zoomA = 1.5, zoomA_new = 0.8, zoomB = 1.3;
+ // save Zoom-A
+ cp.setPref(uri1, pref_name, zoomA);
+ // make sure Zoom-A is retrievable
+ do_check_eq(cp.getPref(uri1, pref_name), zoomA);
+ // enter private browsing mode
+ enterPBMode(cp);
+ // make sure Zoom-A is retrievable
+ do_check_eq(cp.getPref(uri1, pref_name), zoomA);
+ // save Zoom-B
+ cp.setPref(uri2, pref_name, zoomB);
+ // make sure Zoom-B is retrievable
+ do_check_eq(cp.getPref(uri2, pref_name), zoomB);
+ // update Zoom-A
+ cp.setPref(uri1, pref_name, zoomA_new);
+ // make sure Zoom-A has changed
+ do_check_eq(cp.getPref(uri1, pref_name), zoomA_new);
+ // exit private browsing mode
+ exitPBMode(cp);
+ // make sure Zoom-A change has not persisted
+ do_check_eq(cp.getPref(uri1, pref_name), zoomA);
+ // make sure Zoom-B change has not persisted
+ do_check_eq(cp.hasPref(uri2, pref_name), false);
+ } catch (e) {
+ do_throw("Unexpected exception: " + e);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_bug503971.js
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ var cps = new ContentPrefInstance(null);
+
+ var uri = ContentPrefTest.getURI("http://www.example.com/");
+
+ do_check_thrown(function() { cps.setPref(uri, null, 8); });
+ do_check_thrown(function() { cps.hasPref(uri, null); });
+ do_check_thrown(function() { cps.getPref(uri, null); });
+ do_check_thrown(function() { cps.removePref(uri, null); });
+ do_check_thrown(function() { cps.getPrefsByName(null); });
+ do_check_thrown(function() { cps.removePrefsByName(null); });
+
+ do_check_thrown(function() { cps.setPref(uri, "", 21); });
+ do_check_thrown(function() { cps.hasPref(uri, ""); });
+ do_check_thrown(function() { cps.getPref(uri, ""); });
+ do_check_thrown(function() { cps.removePref(uri, ""); });
+ do_check_thrown(function() { cps.getPrefsByName(""); });
+ do_check_thrown(function() { cps.removePrefsByName(""); });
+}
+
+function do_check_thrown(aCallback) {
+ var exThrown = false;
+ try {
+ aCallback();
+ do_throw("NS_ERROR_ILLEGAL_VALUE should have been thrown here");
+ } catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ exThrown = true;
+ }
+ do_check_true(exThrown);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_bug679784.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+var prefObserver = {
+ setCalledNum: 0,
+ onContentPrefSet(aGroup, aName, aValue) {
+ this.setCalledNum++;
+ },
+ removedCalledNum: 0,
+ onContentPrefRemoved(aGroup, aName) {
+ this.removedCalledNum++;
+ }
+};
+
+function run_test() {
+ var cps = new ContentPrefInstance(loadContext);
+ cps.removeGroupedPrefs();
+
+ var uri = ContentPrefTest.getURI("http://www.example.com/");
+ var group = cps.grouper.group(uri);
+
+ // first, set a pref in normal mode
+ cps.setPref(uri, "value", "foo");
+ cps.setPref(null, "value-global", "foo-global");
+
+ var num;
+ cps.addObserver("value", prefObserver);
+ cps.addObserver("value-global", prefObserver);
+
+ enterPBMode(cps);
+
+ // test setPref
+ num = prefObserver.setCalledNum;
+ cps.setPref(uri, "value", "foo-private-browsing");
+ do_check_eq(cps.hasPref(uri, "value"), true);
+ do_check_eq(cps.getPref(uri, "value"), "foo-private-browsing");
+ do_check_eq(prefObserver.setCalledNum, num + 1);
+
+ num = prefObserver.setCalledNum;
+ cps.setPref(null, "value-global", "foo-private-browsing-global");
+ do_check_eq(cps.hasPref(null, "value-global"), true);
+ do_check_eq(cps.getPref(null, "value-global"), "foo-private-browsing-global");
+ do_check_eq(prefObserver.setCalledNum, num + 1);
+
+ // test removePref
+ num = prefObserver.removedCalledNum;
+ cps.removePref(uri, "value");
+ do_check_eq(cps.hasPref(uri, "value"), true);
+ // fallback to non private mode value
+ do_check_eq(cps.getPref(uri, "value"), "foo");
+ do_check_eq(prefObserver.removedCalledNum, num + 1);
+
+ num = prefObserver.removedCalledNum;
+ cps.removePref(null, "value-global");
+ do_check_eq(cps.hasPref(null, "value-global"), true);
+ // fallback to non private mode value
+ do_check_eq(cps.getPref(null, "value-global"), "foo-global") ;
+ do_check_eq(prefObserver.removedCalledNum, num + 1);
+
+ // test removeGroupedPrefs
+ cps.setPref(uri, "value", "foo-private-browsing");
+ cps.removeGroupedPrefs();
+ do_check_eq(cps.hasPref(uri, "value"), false);
+ do_check_eq(cps.getPref(uri, "value"), undefined);
+
+ cps.setPref(null, "value-global", "foo-private-browsing-global");
+ cps.removeGroupedPrefs();
+ do_check_eq(cps.hasPref(null, "value-global"), true);
+ do_check_eq(cps.getPref(null, "value-global"), "foo-private-browsing-global");
+
+ // test removePrefsByName
+ num = prefObserver.removedCalledNum;
+ cps.setPref(uri, "value", "foo-private-browsing");
+ cps.removePrefsByName("value");
+ do_check_eq(cps.hasPref(uri, "value"), false);
+ do_check_eq(cps.getPref(uri, "value"), undefined);
+ do_check_true(prefObserver.removedCalledNum > num);
+
+ num = prefObserver.removedCalledNum;
+ cps.setPref(null, "value-global", "foo-private-browsing");
+ cps.removePrefsByName("value-global");
+ do_check_eq(cps.hasPref(null, "value-global"), false);
+ do_check_eq(cps.getPref(null, "value-global"), undefined);
+ do_check_true(prefObserver.removedCalledNum > num);
+
+ // test getPrefs
+ cps.setPref(uri, "value", "foo-private-browsing");
+ do_check_eq(cps.getPrefs(uri).getProperty("value"), "foo-private-browsing");
+
+ cps.setPref(null, "value-global", "foo-private-browsing-global");
+ do_check_eq(cps.getPrefs(null).getProperty("value-global"), "foo-private-browsing-global");
+
+ // test getPrefsByName
+ do_check_eq(cps.getPrefsByName("value").getProperty(group), "foo-private-browsing");
+ do_check_eq(cps.getPrefsByName("value-global").getProperty(null), "foo-private-browsing-global");
+
+ cps.removeObserver("value", prefObserver);
+ cps.removeObserver("value-global", prefObserver);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_contentPrefs.js
@@ -0,0 +1,462 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+add_task(async function() {
+ // Database Creation, Schema Migration, and Backup
+
+ // Note: in these tests we use createInstance instead of getService
+ // so we can instantiate the service multiple times and make it run
+ // its database initialization code each time.
+
+ function with_cps_instance(testFn) {
+ let cps = Cc["@mozilla.org/content-pref/service;1"]
+ .createInstance(Ci.nsIContentPrefService)
+ .QueryInterface(Ci.nsIObserver);
+ testFn(cps);
+ let promiseClosed = TestUtils.topicObserved("content-prefs-db-closed");
+ cps.observe(null, "xpcom-shutdown", "");
+ return promiseClosed;
+ }
+
+ // Create a new database.
+ ContentPrefTest.deleteDatabase();
+ await with_cps_instance(cps => {
+ do_check_true(cps.DBConnection.connectionReady);
+ });
+
+ // Open an existing database.
+
+ ContentPrefTest.deleteDatabase();
+ await with_cps_instance(cps => {});
+
+ // Get the service and make sure it has a ready database connection.
+ await with_cps_instance(cps => {
+ do_check_true(cps.DBConnection.connectionReady);
+ });
+
+ // Open an empty database.
+ {
+ let dbFile = ContentPrefTest.deleteDatabase();
+
+ // Create an empty database.
+ let dbService = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ let dbConnection = dbService.openDatabase(dbFile);
+ do_check_eq(dbConnection.schemaVersion, 0);
+ dbConnection.close();
+ do_check_true(dbFile.exists());
+
+ // Get the service and make sure it has created the schema.
+ await with_cps_instance(cps => {
+ do_check_neq(cps.DBConnection.schemaVersion, 0);
+ });
+ }
+
+ // Open a corrupted database.
+ {
+ let dbFile = ContentPrefTest.deleteDatabase();
+ let backupDBFile = ContentPrefTest.deleteBackupDatabase();
+
+ // Create a corrupted database.
+ let foStream = Cc["@mozilla.org/network/file-output-stream;1"].
+ createInstance(Ci.nsIFileOutputStream);
+ foStream.init(dbFile, 0x02 | 0x08 | 0x20, 0o666, 0);
+ let garbageData = "garbage that makes SQLite think the file is corrupted";
+ foStream.write(garbageData, garbageData.length);
+ foStream.close();
+
+ // Get the service and make sure it backs up and recreates the database.
+ await with_cps_instance(cps => {
+ do_check_true(backupDBFile.exists());
+ do_check_true(cps.DBConnection.connectionReady);
+ });
+ }
+
+ // Open a database with a corrupted schema.
+ {
+ let dbFile = ContentPrefTest.deleteDatabase();
+ let backupDBFile = ContentPrefTest.deleteBackupDatabase();
+
+ // Create an empty database and set the schema version to a number
+ // that will trigger a schema migration that will fail.
+ let dbService = Cc["@mozilla.org/storage/service;1"].
+ getService(Ci.mozIStorageService);
+ let dbConnection = dbService.openDatabase(dbFile);
+ dbConnection.schemaVersion = -1;
+ dbConnection.close();
+ do_check_true(dbFile.exists());
+
+ // Get the service and make sure it backs up and recreates the database.
+ await with_cps_instance(cps => {
+ do_check_true(backupDBFile.exists());
+ do_check_true(cps.DBConnection.connectionReady);
+ });
+ }
+
+
+ // Now get the content pref service for real for use by the rest of the tests.
+ let cps = new ContentPrefInstance(null);
+
+ var uri = ContentPrefTest.getURI("http://www.example.com/");
+
+ // Make sure disk synchronization checking is turned off by default.
+ var statement = cps.DBConnection.createStatement("PRAGMA synchronous");
+ try {
+ statement.executeStep();
+ do_check_eq(0, statement.getInt32(0));
+ } finally {
+ statement.finalize();
+ }
+
+ // Nonexistent Pref
+
+ do_check_eq(cps.getPref(uri, "test.nonexistent.getPref"), undefined);
+ do_check_eq(cps.setPref(uri, "test.nonexistent.setPref", 5), undefined);
+ do_check_false(cps.hasPref(uri, "test.nonexistent.hasPref"));
+ do_check_eq(cps.removePref(uri, "test.nonexistent.removePref"), undefined);
+
+
+ // Existing Pref
+
+ cps.setPref(uri, "test.existing", 5);
+
+ // getPref should return the pref value
+ do_check_eq(cps.getPref(uri, "test.existing"), 5);
+
+ // setPref should return undefined and change the value of the pref
+ do_check_eq(cps.setPref(uri, "test.existing", 6), undefined);
+ do_check_eq(cps.getPref(uri, "test.existing"), 6);
+
+ // hasPref should return true
+ do_check_true(cps.hasPref(uri, "test.existing"));
+
+ // removePref should return undefined and remove the pref
+ do_check_eq(cps.removePref(uri, "test.existing"), undefined);
+ do_check_false(cps.hasPref(uri, "test.existing"));
+
+
+ // Round-Trip Data Integrity
+
+ // Make sure pref values remain the same from setPref to getPref.
+
+ cps.setPref(uri, "test.data-integrity.integer", 5);
+ do_check_eq(cps.getPref(uri, "test.data-integrity.integer"), 5);
+
+ cps.setPref(uri, "test.data-integrity.float", 5.5);
+ do_check_eq(cps.getPref(uri, "test.data-integrity.float"), 5.5);
+
+ cps.setPref(uri, "test.data-integrity.boolean", true);
+ do_check_eq(cps.getPref(uri, "test.data-integrity.boolean"), true);
+
+ cps.setPref(uri, "test.data-integrity.string", "test");
+ do_check_eq(cps.getPref(uri, "test.data-integrity.string"), "test");
+
+ cps.setPref(uri, "test.data-integrity.null", null);
+ do_check_eq(cps.getPref(uri, "test.data-integrity.null"), null);
+
+ // XXX Test arbitrary binary data.
+
+ // Make sure hasPref and removePref work on all data types.
+
+ do_check_true(cps.hasPref(uri, "test.data-integrity.integer"));
+ do_check_true(cps.hasPref(uri, "test.data-integrity.float"));
+ do_check_true(cps.hasPref(uri, "test.data-integrity.boolean"));
+ do_check_true(cps.hasPref(uri, "test.data-integrity.string"));
+ do_check_true(cps.hasPref(uri, "test.data-integrity.null"));
+
+ do_check_eq(cps.removePref(uri, "test.data-integrity.integer"), undefined);
+ do_check_eq(cps.removePref(uri, "test.data-integrity.float"), undefined);
+ do_check_eq(cps.removePref(uri, "test.data-integrity.boolean"), undefined);
+ do_check_eq(cps.removePref(uri, "test.data-integrity.string"), undefined);
+ do_check_eq(cps.removePref(uri, "test.data-integrity.null"), undefined);
+
+ do_check_false(cps.hasPref(uri, "test.data-integrity.integer"));
+ do_check_false(cps.hasPref(uri, "test.data-integrity.float"));
+ do_check_false(cps.hasPref(uri, "test.data-integrity.boolean"));
+ do_check_false(cps.hasPref(uri, "test.data-integrity.string"));
+ do_check_false(cps.hasPref(uri, "test.data-integrity.null"));
+
+
+ // getPrefs
+
+ cps.setPref(uri, "test.getPrefs.a", 1);
+ cps.setPref(uri, "test.getPrefs.b", 2);
+ cps.setPref(uri, "test.getPrefs.c", 3);
+
+ var prefs = cps.getPrefs(uri);
+ do_check_true(prefs.hasKey("test.getPrefs.a"));
+ do_check_eq(prefs.get("test.getPrefs.a"), 1);
+ do_check_true(prefs.hasKey("test.getPrefs.b"));
+ do_check_eq(prefs.get("test.getPrefs.b"), 2);
+ do_check_true(prefs.hasKey("test.getPrefs.c"));
+ do_check_eq(prefs.get("test.getPrefs.c"), 3);
+
+
+ // Site-Specificity
+
+ {
+ // These are all different sites, and setting a pref for one of them
+ // shouldn't set it for the others.
+ let uri1 = ContentPrefTest.getURI("http://www.domain1.com/");
+ let uri2 = ContentPrefTest.getURI("http://foo.domain1.com/");
+ let uri3 = ContentPrefTest.getURI("http://domain1.com/");
+ let uri4 = ContentPrefTest.getURI("http://www.domain2.com/");
+
+ cps.setPref(uri1, "test.site-specificity.uri1", 5);
+ do_check_false(cps.hasPref(uri2, "test.site-specificity.uri1"));
+ do_check_false(cps.hasPref(uri3, "test.site-specificity.uri1"));
+ do_check_false(cps.hasPref(uri4, "test.site-specificity.uri1"));
+
+ cps.setPref(uri2, "test.site-specificity.uri2", 5);
+ do_check_false(cps.hasPref(uri1, "test.site-specificity.uri2"));
+ do_check_false(cps.hasPref(uri3, "test.site-specificity.uri2"));
+ do_check_false(cps.hasPref(uri4, "test.site-specificity.uri2"));
+
+ cps.setPref(uri3, "test.site-specificity.uri3", 5);
+ do_check_false(cps.hasPref(uri1, "test.site-specificity.uri3"));
+ do_check_false(cps.hasPref(uri2, "test.site-specificity.uri3"));
+ do_check_false(cps.hasPref(uri4, "test.site-specificity.uri3"));
+
+ cps.setPref(uri4, "test.site-specificity.uri4", 5);
+ do_check_false(cps.hasPref(uri1, "test.site-specificity.uri4"));
+ do_check_false(cps.hasPref(uri2, "test.site-specificity.uri4"));
+ do_check_false(cps.hasPref(uri3, "test.site-specificity.uri4"));
+ }
+
+ // Observers
+
+ var specificObserver = {
+ interfaces: [Ci.nsIContentPrefObserver, Ci.nsISupports],
+
+ QueryInterface: function ContentPrefTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ numTimesSetCalled: 0,
+ onContentPrefSet: function specificObserver_onContentPrefSet(group, name, value) {
+ ++this.numTimesSetCalled;
+ do_check_eq(group, "www.example.com");
+ do_check_eq(name, "test.observer.1");
+ do_check_eq(value, "test value");
+ },
+
+ numTimesRemovedCalled: 0,
+ onContentPrefRemoved: function specificObserver_onContentPrefRemoved(group, name) {
+ ++this.numTimesRemovedCalled;
+ do_check_eq(group, "www.example.com");
+ do_check_eq(name, "test.observer.1");
+ }
+
+ };
+
+ var genericObserver = {
+ interfaces: [Ci.nsIContentPrefObserver, Ci.nsISupports],
+
+ QueryInterface: function ContentPrefTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ numTimesSetCalled: 0,
+ onContentPrefSet: function genericObserver_onContentPrefSet(group, name, value, isPrivate) {
+ ++this.numTimesSetCalled;
+ do_check_eq(group, "www.example.com");
+ if (name == "test.observer.private")
+ do_check_true(isPrivate);
+ else if (name == "test.observer.normal")
+ do_check_false(isPrivate);
+ else if (name != "test.observer.1" && name != "test.observer.2")
+ do_throw("genericObserver.onContentPrefSet: " +
+ "name not in (test.observer.(1|2|normal|private))");
+ do_check_eq(value, "test value");
+ },
+
+ numTimesRemovedCalled: 0,
+ onContentPrefRemoved: function genericObserver_onContentPrefRemoved(group, name, isPrivate) {
+ ++this.numTimesRemovedCalled;
+ do_check_eq(group, "www.example.com");
+ if (name == "test.observer.private")
+ do_check_true(isPrivate);
+ else if (name == "test.observer.normal")
+ do_check_false(isPrivate);
+ if (name != "test.observer.1" && name != "test.observer.2" &&
+ name != "test.observer.normal" && name != "test.observer.private") {
+ do_throw("genericObserver.onContentPrefSet: " +
+ "name not in (test.observer.(1|2|normal|private))");
+ }
+ }
+
+ };
+
+ // Make sure we can add observers, observers get notified about changes,
+ // specific observers only get notified about changes to the specific setting,
+ // and generic observers get notified about changes to all settings.
+ cps.addObserver("test.observer.1", specificObserver);
+ cps.addObserver(null, genericObserver);
+ cps.setPref(uri, "test.observer.1", "test value");
+ cps.setPref(uri, "test.observer.2", "test value");
+ cps.removePref(uri, "test.observer.1");
+ cps.removePref(uri, "test.observer.2");
+ do_check_eq(specificObserver.numTimesSetCalled, 1);
+ do_check_eq(genericObserver.numTimesSetCalled, 2);
+ do_check_eq(specificObserver.numTimesRemovedCalled, 1);
+ do_check_eq(genericObserver.numTimesRemovedCalled, 2);
+
+ // Make sure information about private context is properly
+ // retrieved by the observer.
+ cps.setPref(uri, "test.observer.private", "test value", privateLoadContext);
+ cps.setPref(uri, "test.observer.normal", "test value", loadContext);
+ cps.removePref(uri, "test.observer.private");
+ cps.removePref(uri, "test.observer.normal");
+
+ // Make sure we can remove observers and they don't get notified
+ // about changes anymore.
+ cps.removeObserver("test.observer.1", specificObserver);
+ cps.removeObserver(null, genericObserver);
+ cps.setPref(uri, "test.observer.1", "test value");
+ cps.removePref(uri, "test.observer.1", "test value");
+ do_check_eq(specificObserver.numTimesSetCalled, 1);
+ do_check_eq(genericObserver.numTimesSetCalled, 4);
+ do_check_eq(specificObserver.numTimesRemovedCalled, 1);
+ do_check_eq(genericObserver.numTimesRemovedCalled, 3);
+
+
+ // Get/Remove Prefs By Name
+
+ {
+ var anObserver = {
+ interfaces: [Ci.nsIContentPrefObserver, Ci.nsISupports],
+
+ QueryInterface: function ContentPrefTest_QueryInterface(iid) {
+ if (!this.interfaces.some( function(v) { return iid.equals(v) } ))
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ return this;
+ },
+
+ onContentPrefSet: function anObserver_onContentPrefSet(group, name, value) {
+ },
+
+ expectedDomains: [],
+ numTimesRemovedCalled: 0,
+ onContentPrefRemoved: function anObserver_onContentPrefRemoved(group, name) {
+ ++this.numTimesRemovedCalled;
+
+ // remove the domain from the list of expected domains
+ var index = this.expectedDomains.indexOf(group);
+ do_check_true(index >= 0);
+ this.expectedDomains.splice(index, 1);
+ }
+ };
+
+ let uri1 = ContentPrefTest.getURI("http://www.domain1.com/");
+ let uri2 = ContentPrefTest.getURI("http://foo.domain1.com/");
+ let uri3 = ContentPrefTest.getURI("http://domain1.com/");
+ let uri4 = ContentPrefTest.getURI("http://www.domain2.com/");
+
+ cps.setPref(uri1, "test.byname.1", 1);
+ cps.setPref(uri1, "test.byname.2", 2);
+ cps.setPref(uri2, "test.byname.1", 4);
+ cps.setPref(uri3, "test.byname.3", 8);
+ cps.setPref(uri4, "test.byname.1", 16);
+ cps.setPref(null, "test.byname.1", 32);
+ cps.setPref(null, "test.byname.2", false);
+
+ function enumerateAndCheck(testName, expectedSum, expectedDomains) {
+ var prefsByName = cps.getPrefsByName(testName);
+ var enumerator = prefsByName.enumerator;
+ var sum = 0;
+ while (enumerator.hasMoreElements()) {
+ var property = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
+ sum += parseInt(property.value);
+
+ // remove the domain from the list of expected domains
+ var index = expectedDomains.indexOf(property.name);
+ do_check_true(index >= 0);
+ expectedDomains.splice(index, 1);
+ }
+ do_check_eq(sum, expectedSum);
+ // check all domains have been removed from the array
+ do_check_eq(expectedDomains.length, 0);
+ }
+
+ enumerateAndCheck("test.byname.1", 53,
+ ["foo.domain1.com", null, "www.domain1.com", "www.domain2.com"]);
+ enumerateAndCheck("test.byname.2", 2, ["www.domain1.com", null]);
+ enumerateAndCheck("test.byname.3", 8, ["domain1.com"]);
+
+ cps.addObserver("test.byname.1", anObserver);
+ anObserver.expectedDomains = ["foo.domain1.com", null, "www.domain1.com", "www.domain2.com"];
+
+ cps.removePrefsByName("test.byname.1");
+ do_check_false(cps.hasPref(uri1, "test.byname.1"));
+ do_check_false(cps.hasPref(uri2, "test.byname.1"));
+ do_check_false(cps.hasPref(uri3, "test.byname.1"));
+ do_check_false(cps.hasPref(uri4, "test.byname.1"));
+ do_check_false(cps.hasPref(null, "test.byname.1"));
+ do_check_true(cps.hasPref(uri1, "test.byname.2"));
+ do_check_true(cps.hasPref(uri3, "test.byname.3"));
+
+ do_check_eq(anObserver.numTimesRemovedCalled, 4);
+ do_check_eq(anObserver.expectedDomains.length, 0);
+
+ cps.removeObserver("test.byname.1", anObserver);
+
+ // Clean up after ourselves
+ cps.removePref(uri1, "test.byname.2");
+ cps.removePref(uri3, "test.byname.3");
+ cps.removePref(null, "test.byname.2");
+ }
+
+
+ // Clear Private Data Pref Removal
+
+ {
+ let uri1 = ContentPrefTest.getURI("http://www.domain1.com/");
+ let uri2 = ContentPrefTest.getURI("http://www.domain2.com/");
+ let uri3 = ContentPrefTest.getURI("http://www.domain3.com/");
+
+ let dbConnection = cps.DBConnection;
+
+ let prefCount = dbConnection.createStatement("SELECT COUNT(*) AS count FROM prefs");
+
+ let groupCount = dbConnection.createStatement("SELECT COUNT(*) AS count FROM groups");
+
+ // Add some prefs for multiple domains.
+ cps.setPref(uri1, "test.removeAllGroups", 1);
+ cps.setPref(uri2, "test.removeAllGroups", 2);
+ cps.setPref(uri3, "test.removeAllGroups", 3);
+
+ // Add a global pref.
+ cps.setPref(null, "test.removeAllGroups", 1);
+
+ // Make sure there are some prefs and groups in the database.
+ prefCount.executeStep();
+ do_check_true(prefCount.row.count > 0);
+ prefCount.reset();
+ groupCount.executeStep();
+ do_check_true(groupCount.row.count > 0);
+ groupCount.reset();
+
+ // Remove all prefs and groups from the database using the same routine
+ // the Clear Private Data dialog uses.
+ cps.removeGroupedPrefs();
+
+ // Make sure there are no longer any groups in the database and the only pref
+ // is the global one.
+ prefCount.executeStep();
+ do_check_true(prefCount.row.count == 1);
+ prefCount.reset();
+ groupCount.executeStep();
+ do_check_true(groupCount.row.count == 0);
+ groupCount.reset();
+ let globalPref = dbConnection.createStatement("SELECT groupID FROM prefs");
+ globalPref.executeStep();
+ do_check_true(globalPref.row.groupID == null);
+ globalPref.reset();
+ }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_contentPrefsCache.js
@@ -0,0 +1,244 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var cps = new ContentPrefInstance(null);
+
+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) {
+ 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);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_getPrefAsync.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+var cps = new ContentPrefInstance(null);
+var uri = ContentPrefTest.getURI("http://www.example.com/");
+
+function run_test() {
+ do_test_pending();
+
+ cps.setPref(uri, "asynctest", "pie");
+ do_check_eq(cps.getPref(uri, "asynctest"), "pie");
+
+ cps.getPref(uri, "asynctest", function(aValue) {
+ do_check_eq(aValue, "pie");
+ testCallbackObj();
+ });
+}
+
+function testCallbackObj() {
+ cps.getPref(uri, "asynctest", {
+ onResult(aValue) {
+ do_check_eq(aValue, "pie");
+ cps.removePref(uri, "asynctest");
+ testNoResult();
+ }
+ });
+}
+
+function testNoResult() {
+ cps.getPref(uri, "asynctest", function(aValue) {
+ do_check_eq(aValue, undefined);
+ do_test_finished();
+ });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_stringGroups.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+
+ var cps = new ContentPrefInstance(null);
+
+ // Make sure disk synchronization checking is turned off by default.
+ var statement = cps.DBConnection.createStatement("PRAGMA synchronous");
+ statement.executeStep();
+ do_check_eq(0, statement.getInt32(0));
+
+ // These are the different types of aGroup arguments we'll test.
+ var anObject = {"foo": "bar"}; // a simple object
+ var uri = ContentPrefTest.getURI("http://www.example.com/"); // nsIURI
+ var stringURI = "www.example.com"; // typeof = "string"
+
+ // Test wants to check for a String object
+ // eslint-disable-next-line no-new-wrappers
+ var stringObjectURI = new String("www.example.com"); // typeof = "object"
+
+ // First check that all the methods work or don't work.
+ function simple_test_methods(aGroup, shouldThrow) {
+ var prefName = "test.pref.0";
+ var prefValue = Math.floor(Math.random() * 100);
+
+ if (shouldThrow) {
+ do_check_thrown(function() { cps.getPref(aGroup, prefName); });
+ do_check_thrown(function() { cps.setPref(aGroup, prefName, prefValue); });
+ do_check_thrown(function() { cps.hasPref(aGroup, prefName); });
+ do_check_thrown(function() { cps.removePref(aGroup, prefName); });
+ do_check_thrown(function() { cps.getPrefs(aGroup); });
+ } else {
+ do_check_eq(cps.setPref(aGroup, prefName, prefValue), undefined);
+ do_check_true(cps.hasPref(aGroup, prefName));
+ do_check_eq(cps.getPref(aGroup, prefName), prefValue);
+ do_check_eq(cps.removePref(aGroup, prefName), undefined);
+ do_check_false(cps.hasPref(aGroup, prefName));
+ }
+ }
+
+ simple_test_methods(cps, true); // arbitrary nsISupports object, should throw too
+ simple_test_methods(anObject, true);
+ simple_test_methods(uri, false);
+ simple_test_methods(stringURI, false);
+ simple_test_methods(stringObjectURI, false);
+
+ // Now we'll check that each argument produces the same result.
+ function complex_test_methods(aGroup) {
+ var prefName = "test.pref.1";
+ var prefValue = Math.floor(Math.random() * 100);
+
+ do_check_eq(cps.setPref(aGroup, prefName, prefValue), undefined);
+
+ do_check_true(cps.hasPref(uri, prefName));
+ do_check_true(cps.hasPref(stringURI, prefName));
+ do_check_true(cps.hasPref(stringObjectURI, prefName));
+
+ do_check_eq(cps.getPref(uri, prefName), prefValue);
+ do_check_eq(cps.getPref(stringURI, prefName), prefValue);
+ do_check_eq(cps.getPref(stringObjectURI, prefName), prefValue);
+
+ do_check_eq(cps.removePref(aGroup, prefName), undefined);
+
+ do_check_false(cps.hasPref(uri, prefName));
+ do_check_false(cps.hasPref(stringURI, prefName));
+ do_check_false(cps.hasPref(stringObjectURI, prefName));
+ }
+
+ complex_test_methods(uri);
+ complex_test_methods(stringURI);
+ complex_test_methods(stringObjectURI);
+
+ // test getPrefs returns the same prefs
+ do_check_eq(cps.setPref(stringObjectURI, "test.5", 5), undefined);
+ do_check_eq(cps.setPref(stringURI, "test.2", 2), undefined);
+ do_check_eq(cps.setPref(uri, "test.1", 1), undefined);
+
+ enumerateAndCheck(cps.getPrefs(uri), 8, ["test.1", "test.2", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringURI), 8, ["test.1", "test.2", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringObjectURI), 8, ["test.1", "test.2", "test.5"]);
+
+ do_check_eq(cps.setPref(uri, "test.4", 4), undefined);
+ do_check_eq(cps.setPref(stringObjectURI, "test.0", 0), undefined);
+
+ enumerateAndCheck(cps.getPrefs(uri), 12, ["test.0", "test.1", "test.2", "test.4", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringURI), 12, ["test.0", "test.1", "test.2", "test.4", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringObjectURI), 12, ["test.0", "test.1", "test.2", "test.4", "test.5"]);
+
+ do_check_eq(cps.setPref(stringURI, "test.3", 3), undefined);
+
+ enumerateAndCheck(cps.getPrefs(uri), 15, ["test.0", "test.1", "test.2", "test.3", "test.4", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringURI), 15, ["test.0", "test.1", "test.2", "test.3", "test.4", "test.5"]);
+ enumerateAndCheck(cps.getPrefs(stringObjectURI), 15, ["test.0", "test.1", "test.2", "test.3", "test.4", "test.5"]);
+}
+
+function do_check_thrown(aCallback) {
+ var exThrown = false;
+ try {
+ aCallback();
+ do_throw("NS_ERROR_ILLEGAL_VALUE should have been thrown here");
+ } catch (e) {
+ do_check_eq(e.result, Cr.NS_ERROR_ILLEGAL_VALUE);
+ exThrown = true;
+ }
+ do_check_true(exThrown);
+}
+
+function enumerateAndCheck(prefs, expectedSum, expectedNames) {
+ var enumerator = prefs.enumerator;
+ var sum = 0;
+ while (enumerator.hasMoreElements()) {
+ var property = enumerator.getNext().QueryInterface(Components.interfaces.nsIProperty);
+ sum += parseInt(property.value);
+
+ // remove the pref name from the list of expected names
+ var index = expectedNames.indexOf(property.name);
+ do_check_true(index >= 0);
+ expectedNames.splice(index, 1);
+ }
+ do_check_eq(sum, expectedSum);
+ // check all pref names have been removed from the array
+ do_check_eq(expectedNames.length, 0);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/test_unusedGroupsAndSettings.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var cps = new ContentPrefInstance(null);
+
+function run_test() {
+ var uri1 = ContentPrefTest.getURI("http://www.domain1.com/");
+ var uri2 = ContentPrefTest.getURI("http://foo.domain1.com/");
+ var uri3 = ContentPrefTest.getURI("http://domain1.com/");
+ var uri4 = ContentPrefTest.getURI("http://www.domain2.com/");
+
+ cps.setPref(uri1, "one", 1);
+ cps.setPref(uri1, "two", 2);
+ cps.setPref(uri2, "one", 4);
+ cps.setPref(uri3, "three", 8);
+ cps.setPref(uri4, "two", 16);
+
+ cps.removePref(uri3, "three"); // uri3 should be removed now
+ checkForUnusedGroups();
+ checkForUnusedSettings();
+
+ cps.removePrefsByName("two"); // uri4 should be removed now
+ checkForUnusedGroups();
+ checkForUnusedSettings();
+
+ cps.removeGroupedPrefs();
+ checkForUnusedGroups();
+ checkForUnusedSettings();
+}
+
+function checkForUnusedGroups() {
+ var stmt = cps.DBConnection.createStatement(`
+ SELECT COUNT(*) AS count FROM groups
+ WHERE id NOT IN (SELECT DISTINCT groupID FROM prefs)
+ `);
+ stmt.executeStep();
+ do_check_eq(0, stmt.row.count);
+ stmt.reset();
+ stmt.finalize();
+}
+
+function checkForUnusedSettings() {
+ var stmt = cps.DBConnection.createStatement(`
+ SELECT COUNT(*) AS count FROM settings
+ WHERE id NOT IN (SELECT DISTINCT settingID FROM prefs)
+ `);
+ stmt.executeStep();
+ do_check_eq(0, stmt.row.count);
+ stmt.reset();
+ stmt.finalize();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit/xpcshell.ini
@@ -0,0 +1,11 @@
+[DEFAULT]
+head = head_contentPrefs.js
+
+[test_bug248970.js]
+[test_bug503971.js]
+[test_bug679784.js]
+[test_contentPrefs.js]
+[test_contentPrefsCache.js]
+[test_getPrefAsync.js]
+[test_stringGroups.js]
+[test_unusedGroupsAndSettings.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/contentprefs/tests/unit_cps2/test_service.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function run_test() {
+ let serv = Cc["@mozilla.org/content-pref/service;1"].
+ getService(Ci.nsIContentPrefService2);
+ do_check_eq(serv.QueryInterface(Ci.nsIContentPrefService2), serv);
+ do_check_eq(serv.QueryInterface(Ci.nsISupports), serv);
+ let val = serv.QueryInterface(Ci.nsIContentPrefService);
+ do_check_true(val instanceof Ci.nsIContentPrefService);
+}
--- a/toolkit/components/contentprefs/tests/unit_cps2/xpcshell.ini
+++ b/toolkit/components/contentprefs/tests/unit_cps2/xpcshell.ini
@@ -1,13 +1,14 @@
[DEFAULT]
head = head.js
skip-if = toolkit == 'android'
support-files = AsyncRunner.jsm
+[test_service.js]
[test_setGet.js]
[test_getSubdomains.js]
[test_remove.js]
[test_removeByDomain.js]
[test_removeAllDomains.js]
[test_removeByName.js]
[test_getCached.js]
[test_getCachedSubdomains.js]
--- a/toolkit/modules/Services.jsm
+++ b/toolkit/modules/Services.jsm
@@ -67,16 +67,17 @@ XPCOMUtils.defineLazyGetter(Services, "i
});
var initTable = {
appShell: ["@mozilla.org/appshell/appShellService;1", "nsIAppShellService"],
cache: ["@mozilla.org/network/cache-service;1", "nsICacheService"],
cache2: ["@mozilla.org/netwerk/cache-storage-service;1", "nsICacheStorageService"],
cpmm: ["@mozilla.org/childprocessmessagemanager;1", "nsIMessageSender"],
console: ["@mozilla.org/consoleservice;1", "nsIConsoleService"],
+ contentPrefs: ["@mozilla.org/content-pref/service;1", "nsIContentPrefService"],
cookies: ["@mozilla.org/cookiemanager;1", "nsICookieManager2"],
downloads: ["@mozilla.org/download-manager;1", "nsIDownloadManager"],
droppedLinkHandler: ["@mozilla.org/content/dropped-link-handler;1", "nsIDroppedLinkHandler"],
els: ["@mozilla.org/eventlistenerservice;1", "nsIEventListenerService"],
eTLD: ["@mozilla.org/network/effective-tld-service;1", "nsIEffectiveTLDService"],
intl: ["@mozilla.org/mozintl;1", "mozIMozIntl"],
locale: ["@mozilla.org/intl/localeservice;1", "mozILocaleService"],
logins: ["@mozilla.org/login-manager;1", "nsILoginManager"],
--- a/toolkit/modules/tests/xpcshell/test_Services.js
+++ b/toolkit/modules/tests/xpcshell/test_Services.js
@@ -28,16 +28,17 @@ function run_test() {
checkService("appShell", Ci.nsIAppShellService);
checkService("appinfo", Ci.nsIXULRuntime);
checkService("blocklist", Ci.nsIBlocklistService);
checkService("cache", Ci.nsICacheService);
checkService("cache2", Ci.nsICacheStorageService);
checkService("clipboard", Ci.nsIClipboard);
checkService("console", Ci.nsIConsoleService);
+ checkService("contentPrefs", Ci.nsIContentPrefService);
checkService("cookies", Ci.nsICookieManager2);
checkService("dirsvc", Ci.nsIDirectoryService);
checkService("dirsvc", Ci.nsIProperties);
checkService("DOMRequest", Ci.nsIDOMRequestService);
checkService("domStorageManager", Ci.nsIDOMStorageManager);
checkService("downloads", Ci.nsIDownloadManager);
checkService("droppedLinkHandler", Ci.nsIDroppedLinkHandler);
checkService("eTLD", Ci.nsIEffectiveTLDService);
--- a/toolkit/mozapps/downloads/DownloadLastDir.jsm
+++ b/toolkit/mozapps/downloads/DownloadLastDir.jsm
@@ -108,16 +108,35 @@ DownloadLastDir.prototype = {
return this.fakeContext.usePrivateBrowsing;
},
// compat shims
get file() { return this._getLastFile(); },
set file(val) { this.setFile(null, val); },
cleanupPrivateFile() {
gDownloadLastDirFile = null;
},
+ // This function is now deprecated as it uses the sync nsIContentPrefService
+ // interface. New consumers should use the getFileAsync function.
+ getFile(aURI) {
+ let Deprecated = Components.utils.import("resource://gre/modules/Deprecated.jsm", {}).Deprecated;
+ Deprecated.warning("DownloadLastDir.getFile is deprecated. Please use getFileAsync instead.",
+ "https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/DownloadLastDir.jsm",
+ Components.stack.caller);
+
+ if (aURI && isContentPrefEnabled()) {
+ let lastDir = Services.contentPrefs.getPref(aURI, LAST_DIR_PREF, this.fakeContext);
+ if (lastDir) {
+ var lastDirFile = Components.classes["@mozilla.org/file/local;1"]
+ .createInstance(Components.interfaces.nsIFile);
+ lastDirFile.initWithPath(lastDir);
+ return lastDirFile;
+ }
+ }
+ return this._getLastFile();
+ },
_getLastFile() {
if (gDownloadLastDirFile && !gDownloadLastDirFile.exists())
gDownloadLastDirFile = null;
if (this.isPrivate()) {
if (!gDownloadLastDirFile)
gDownloadLastDirFile = readLastDirPref();