diff options
author | Matt A. Tobin <email@mattatobin.com> | 2021-11-19 08:23:01 -0500 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2021-11-19 08:23:01 -0500 |
commit | 410d228eaa229067de8bb3a359e0ca80a4dd6066 (patch) | |
tree | a5b29d0e6df6da569f6c84a8b155405714f4597e /components/preferences | |
parent | 0caddc34f86c28aeeafb1d967872eca008db1974 (diff) | |
download | aura-central-410d228eaa229067de8bb3a359e0ca80a4dd6066.tar.gz |
Issue %3005 - Move modules/libpref to components/preferences
- greprefs is now in system/
Diffstat (limited to 'components/preferences')
-rw-r--r-- | components/preferences/moz.build | 26 | ||||
-rw-r--r-- | components/preferences/public/nsIPrefBranch.idl | 425 | ||||
-rw-r--r-- | components/preferences/public/nsIPrefBranch2.idl | 16 | ||||
-rw-r--r-- | components/preferences/public/nsIPrefBranchInternal.idl | 17 | ||||
-rw-r--r-- | components/preferences/public/nsIPrefLocalizedString.idl | 64 | ||||
-rw-r--r-- | components/preferences/public/nsIPrefService.idl | 160 | ||||
-rw-r--r-- | components/preferences/public/nsIRelativeFilePref.idl | 69 | ||||
-rw-r--r-- | components/preferences/src/Preferences.cpp | 1988 | ||||
-rw-r--r-- | components/preferences/src/Preferences.h | 408 | ||||
-rw-r--r-- | components/preferences/src/nsPrefBranch.cpp | 943 | ||||
-rw-r--r-- | components/preferences/src/nsPrefBranch.h | 269 | ||||
-rw-r--r-- | components/preferences/src/nsPrefsFactory.cpp | 54 | ||||
-rw-r--r-- | components/preferences/src/prefapi.cpp | 1005 | ||||
-rw-r--r-- | components/preferences/src/prefapi.h | 258 | ||||
-rw-r--r-- | components/preferences/src/prefapi_private_data.h | 40 | ||||
-rw-r--r-- | components/preferences/src/prefread.cpp | 657 | ||||
-rw-r--r-- | components/preferences/src/prefread.h | 118 |
17 files changed, 6517 insertions, 0 deletions
diff --git a/components/preferences/moz.build b/components/preferences/moz.build index 635fa39c9..4bd147384 100644 --- a/components/preferences/moz.build +++ b/components/preferences/moz.build @@ -3,4 +3,30 @@ # 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('/ipc/chromium/chromium-config.mozbuild') + +DEFINES['OS_ARCH'] = CONFIG['OS_ARCH'] +DEFINES['MOZ_WIDGET_TOOLKIT'] = CONFIG['MOZ_WIDGET_TOOLKIT'] + +XPIDL_SOURCES += [ + 'public/nsIPrefBranch.idl', + 'public/nsIPrefBranch2.idl', + 'public/nsIPrefBranchInternal.idl', + 'public/nsIPrefLocalizedString.idl', + 'public/nsIPrefService.idl', + 'public/nsIRelativeFilePref.idl', +] + +EXPORTS.mozilla += ['src/Preferences.h'] + +SOURCES += [ + 'src/nsPrefBranch.cpp', + 'src/nsPrefsFactory.cpp', + 'src/prefapi.cpp', + 'src/Preferences.cpp', + 'src/prefread.cpp', +] + +XPIDL_MODULE = 'pref' +FINAL_LIBRARY = 'xul' JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file diff --git a/components/preferences/public/nsIPrefBranch.idl b/components/preferences/public/nsIPrefBranch.idl new file mode 100644 index 000000000..900806b42 --- /dev/null +++ b/components/preferences/public/nsIPrefBranch.idl @@ -0,0 +1,425 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsIObserver; + +/** + * The nsIPrefBranch interface is used to manipulate the preferences data. This + * object may be obtained from the preferences service (nsIPrefService) and + * used to get and set default and/or user preferences across the application. + * + * This object is created with a "root" value which describes the base point in + * the preferences "tree" from which this "branch" stems. Preferences are + * accessed off of this root by using just the final portion of the preference. + * For example, if this object is created with the root "browser.startup.", + * the preferences "browser.startup.page", "browser.startup.homepage", + * and "browser.startup.homepage_override" can be accessed by simply passing + * "page", "homepage", or "homepage_override" to the various Get/Set methods. + * + * @see nsIPrefService + */ + +[scriptable, uuid(55d25e49-793f-4727-a69f-de8b15f4b985)] +interface nsIPrefBranch : nsISupports +{ + + /** + * Values describing the basic preference types. + * + * @see getPrefType + */ + const long PREF_INVALID = 0; + const long PREF_STRING = 32; + const long PREF_INT = 64; + const long PREF_BOOL = 128; + + /** + * Called to get the root on which this branch is based, such as + * "browser.startup." + */ + readonly attribute string root; + + /** + * Called to determine the type of a specific preference. + * + * @param aPrefName The preference to get the type of. + * + * @return long A value representing the type of the preference. This + * value will be PREF_STRING, PREF_INT, or PREF_BOOL. + */ + long getPrefType(in string aPrefName); + + /** + * Called to get the state of an individual boolean preference. + * + * @param aPrefName The boolean preference to get the state of. + * @param aDefaultValue The value to return if the preference is not set. + * + * @return boolean The value of the requested boolean preference. + * + * @see setBoolPref + */ + [optional_argc,binaryname(GetBoolPrefWithDefault)] + boolean getBoolPref(in string aPrefName, [optional] in boolean aDefaultValue); + [noscript,binaryname(GetBoolPref)] + boolean getBoolPrefXPCOM(in string aPrefName); + + /** + * Called to set the state of an individual boolean preference. + * + * @param aPrefName The boolean preference to set the state of. + * @param aValue The boolean value to set the preference to. + * + * @throws Error if setting failed or the preference has a default + value of a type other than boolean. + * + * @see getBoolPref + */ + void setBoolPref(in string aPrefName, in boolean aValue); + + /** + * Called to get the state of an individual floating-point preference. + * "Floating point" preferences are really string preferences that + * are converted to floating point numbers. + * + * @param aPrefName The floating point preference to get the state of. + * @param aDefaultValue The value to return if the preference is not set. + * + * @return float The value of the requested floating point preference. + * + * @see setCharPref + */ + [optional_argc,binaryname(GetFloatPrefWithDefault)] + float getFloatPref(in string aPrefName, [optional] in float aDefaultValue); + [noscript,binaryname(GetFloatPref)] + float getFloatPrefXPCOM(in string aPrefName); + + /** + * Called to get the state of an individual string preference. + * + * @param aPrefName The string preference to retrieve. + * @param aDefaultValue The string to return if the preference is not set. + * + * @return string The value of the requested string preference. + * + * @see setCharPref + */ + [optional_argc,binaryname(GetCharPrefWithDefault)] + string getCharPref(in string aPrefName, [optional] in string aDefaultValue); + [noscript,binaryname(GetCharPref)] + string getCharPrefXPCOM(in string aPrefName); + + /** + * Called to set the state of an individual string preference. + * + * @param aPrefName The string preference to set. + * @param aValue The string value to set the preference to. + * + * @throws Error if setting failed or the preference has a default + value of a type other than string. + * + * @see getCharPref + */ + void setCharPref(in string aPrefName, in string aValue); + + /** + * Called to get the state of an individual integer preference. + * + * @param aPrefName The integer preference to get the value of. + * @param aDefaultValue The value to return if the preference is not set. + * + * @return long The value of the requested integer preference. + * + * @see setIntPref + */ + [optional_argc,binaryname(GetIntPrefWithDefault)] + long getIntPref(in string aPrefName, [optional] in long aDefaultValue); + [noscript,binaryname(GetIntPref)] + long getIntPrefXPCOM(in string aPrefName); + + /** + * Called to set the state of an individual integer preference. + * + * @param aPrefName The integer preference to set the value of. + * @param aValue The integer value to set the preference to. + * + * @throws Error if setting failed or the preference has a default + value of a type other than integer. + * + * @see getIntPref + */ + void setIntPref(in string aPrefName, in long aValue); + + /** + * Called to get the state of an individual complex preference. A complex + * preference is a preference which represents an XPCOM object that can not + * be easily represented using a standard boolean, integer or string value. + * + * @param aPrefName The complex preference to get the value of. + * @param aType The XPCOM interface that this complex preference + * represents. Interfaces currently supported are: + * - nsIFile + * - nsISupportsString (UniChar) + * - nsIPrefLocalizedString (Localized UniChar) + * @param aValue The XPCOM object into which to the complex preference + * value should be retrieved. + * + * @throws Error The value does not exist or is the wrong type. + * + * @see setComplexValue + */ + void getComplexValue(in string aPrefName, in nsIIDRef aType, + [iid_is(aType), retval] out nsQIResult aValue); + + /** + * Called to set the state of an individual complex preference. A complex + * preference is a preference which represents an XPCOM object that can not + * be easily represented using a standard boolean, integer or string value. + * + * @param aPrefName The complex preference to set the value of. + * @param aType The XPCOM interface that this complex preference + * represents. Interfaces currently supported are: + * - nsIFile + * - nsISupportsString (UniChar) + * - nsIPrefLocalizedString (Localized UniChar) + * @param aValue The XPCOM object from which to set the complex preference + * value. + * + * @throws Error if setting failed or the value is the wrong type. + * + * @see getComplexValue + */ + void setComplexValue(in string aPrefName, in nsIIDRef aType, in nsISupports aValue); + + /** + * Called to clear a user set value from a specific preference. This will, in + * effect, reset the value to the default value. If no default value exists + * the preference will cease to exist. + * + * @param aPrefName The preference to be cleared. + * + * @note + * This method does nothing if this object is a default branch. + */ + void clearUserPref(in string aPrefName); + + /** + * Called to lock a specific preference. Locking a preference will cause the + * preference service to always return the default value regardless of + * whether there is a user set value or not. + * + * @param aPrefName The preference to be locked. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on the default branch. + * + * @throws Error The preference does not exist or an error occurred. + * + * @see unlockPref + */ + void lockPref(in string aPrefName); + + /** + * Called to check if a specific preference has a user value associated to + * it. + * + * @param aPrefName The preference to be tested. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on the user branch. + * + * @note + * If a preference was manually set to a value that equals the default value, + * then the preference no longer has a user set value, i.e. it is + * considered reset to its default value. + * In particular, this method will return false for such a preference and + * the preference will not be saved to a file by nsIPrefService.savePrefFile. + * + * @return boolean true The preference has a user set value. + * false The preference only has a default value. + */ + boolean prefHasUserValue(in string aPrefName); + + /** + * Called to check if a specific preference is locked. If a preference is + * locked calling its Get method will always return the default value. + * + * @param aPrefName The preference to be tested. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on the default branch. + * + * @return boolean true The preference is locked. + * false The preference is not locked. + * + * @see lockPref + * @see unlockPref + */ + boolean prefIsLocked(in string aPrefName); + + /** + * Called to unlock a specific preference. Unlocking a previously locked + * preference allows the preference service to once again return the user set + * value of the preference. + * + * @param aPrefName The preference to be unlocked. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on the default branch. + * + * @throws Error The preference does not exist or an error occurred. + * + * @see lockPref + */ + void unlockPref(in string aPrefName); + + + /** + * Called to remove all of the preferences referenced by this branch. + * + * @param aStartingAt The point on the branch at which to start the deleting + * preferences. Pass in "" to remove all preferences + * referenced by this branch. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on both. + * + * @throws Error The preference(s) do not exist or an error occurred. + */ + void deleteBranch(in string aStartingAt); + + /** + * Returns an array of strings representing the child preferences of the + * root of this branch. + * + * @param aStartingAt The point on the branch at which to start enumerating + * the child preferences. Pass in "" to enumerate all + * preferences referenced by this branch. + * @param aCount Receives the number of elements in the array. + * @param aChildArray Receives the array of child preferences. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on both. + * + * @throws Error The preference(s) do not exist or an error occurred. + */ + void getChildList(in string aStartingAt, + [optional] out unsigned long aCount, + [array, size_is(aCount), retval] out string aChildArray); + + /** + * Called to reset all of the preferences referenced by this branch to their + * default values. + * + * @param aStartingAt The point on the branch at which to start the resetting + * preferences to their default values. Pass in "" to + * reset all preferences referenced by this branch. + * + * @note + * This method can be called on either a default or user branch but, in + * effect, always operates on the user branch. + * + * @throws Error The preference(s) do not exist or an error occurred. + */ + void resetBranch(in string aStartingAt); + + /** + * Add a preference change observer. On preference changes, the following + * arguments will be passed to the nsIObserver.observe() method: + * aSubject - The nsIPrefBranch object (this) + * aTopic - The string defined by NS_PREFBRANCH_PREFCHANGE_TOPIC_ID + * aData - The name of the preference which has changed, relative to + * the |root| of the aSubject branch. + * + * aSubject.get*Pref(aData) will get the new value of the modified + * preference. For example, if your observer is registered with + * addObserver("bar.", ...) on a branch with root "foo.", modifying + * the preference "foo.bar.baz" will trigger the observer, and aData + * parameter will be "bar.baz". + * + * @param aDomain The preference on which to listen for changes. This can + * be the name of an entire branch to observe. + * e.g. Holding the "root" prefbranch and calling + * addObserver("foo.bar.", ...) will observe changes to + * foo.bar.baz and foo.bar.bzip + * @param aObserver The object to be notified if the preference changes. + * @param aHoldWeak true Hold a weak reference to |aObserver|. The object + * must implement the nsISupportsWeakReference + * interface or this will fail. + * false Hold a strong reference to |aObserver|. + * + * @note + * Registering as a preference observer can open an object to potential + * cyclical references which will cause memory leaks. These cycles generally + * occur because an object both registers itself as an observer (causing the + * branch to hold a reference to the observer) and holds a reference to the + * branch object for the purpose of getting/setting preference values. There + * are 3 approaches which have been implemented in an attempt to avoid these + * situations. + * 1) The nsPrefBranch object supports nsISupportsWeakReference. Any consumer + * may hold a weak reference to it instead of a strong one. + * 2) The nsPrefBranch object listens for xpcom-shutdown and frees all of the + * objects currently in its observer list. This ensures that long lived + * objects (services for example) will be freed correctly. + * 3) The observer can request to be held as a weak reference when it is + * registered. This insures that shorter lived objects (say one tied to an + * open window) will not fall into the cyclical reference trap. + * + * @note + * The list of registered observers may be changed during the dispatch of + * nsPref:changed notification. However, the observers are not guaranteed + * to be notified in any particular order, so you can't be sure whether the + * added/removed observer will be called during the notification when it + * is added/removed. + * + * @note + * It is possible to change preferences during the notification. + * + * @note + * It is not safe to change observers during this callback in Gecko + * releases before 1.9. If you want a safe way to remove a pref observer, + * please use an nsITimer. + * + * @see nsIObserver + * @see removeObserver + */ + void addObserver(in string aDomain, in nsIObserver aObserver, + in boolean aHoldWeak); + + /** + * Remove a preference change observer. + * + * @param aDomain The preference which is being observed for changes. + * @param aObserver An observer previously registered with addObserver(). + * + * @note + * Note that you must call removeObserver() on the same nsIPrefBranch + * instance on which you called addObserver() in order to remove aObserver; + * otherwise, the observer will not be removed. + * + * @see nsIObserver + * @see addObserver + */ + void removeObserver(in string aDomain, in nsIObserver aObserver); +}; + + +%{C++ + +#define NS_PREFBRANCH_CONTRACTID "@mozilla.org/preferencesbranch;1" +/** + * Notification sent when a preference changes. + */ +#define NS_PREFBRANCH_PREFCHANGE_TOPIC_ID "nsPref:changed" + +%} diff --git a/components/preferences/public/nsIPrefBranch2.idl b/components/preferences/public/nsIPrefBranch2.idl new file mode 100644 index 000000000..f1087c92d --- /dev/null +++ b/components/preferences/public/nsIPrefBranch2.idl @@ -0,0 +1,16 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsIPrefBranch.idl" + +/** + * An empty interface to provide backwards compatibility for existing code. + * + * @see nsIPrefBranch + */ +[scriptable, uuid(8892016d-07f7-4530-b5c1-d73dfcde4a1c)] +interface nsIPrefBranch2 : nsIPrefBranch +{ +}; diff --git a/components/preferences/public/nsIPrefBranchInternal.idl b/components/preferences/public/nsIPrefBranchInternal.idl new file mode 100644 index 000000000..476e6a59f --- /dev/null +++ b/components/preferences/public/nsIPrefBranchInternal.idl @@ -0,0 +1,17 @@ +/* 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 "nsIPrefBranch2.idl" + +/** + * An empty interface to provide backwards compatibility for existing code that + * bsmedberg didn't want to break all at once. Don't use me! + * + * @status NON-FROZEN interface WHICH WILL PROBABLY GO AWAY. + */ + +[scriptable, uuid(355bd1e9-248a-438b-809d-e0db1b287882)] +interface nsIPrefBranchInternal : nsIPrefBranch2 +{ +}; diff --git a/components/preferences/public/nsIPrefLocalizedString.idl b/components/preferences/public/nsIPrefLocalizedString.idl new file mode 100644 index 000000000..f99d60f6f --- /dev/null +++ b/components/preferences/public/nsIPrefLocalizedString.idl @@ -0,0 +1,64 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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" + +/** + * The nsIPrefLocalizedString interface is simply a wrapper interface for + * nsISupportsString so the preferences service can have a unique identifier + * to distinguish between requests for normal wide strings (nsISupportsString) + * and "localized" wide strings, which get their default values from properites + * files. + * + * @see nsIPrefBranch + * @see nsISupportsString + */ + +[scriptable, uuid(ae419e24-1dd1-11b2-b39a-d3e5e7073802)] +interface nsIPrefLocalizedString : nsISupports +{ + /** + * Provides access to string data stored in this property. + * + * @throws Error An error occurred. + */ + attribute wstring data; + + /** + * Used to retrieve the contents of this object into a wide string. + * + * @return wstring The string containing the data stored within this object. + */ + wstring toString(); + + /** + * Used to set the contents of this object. + * + * @param length The length of the string. This value should not include + * space for the null terminator, nor should it account for the + * size of a character. It should only be the number of + * characters for which there is space in the string. + * @param data The string data to be stored. + * + * @note + * This makes a copy of the string argument passed in. + */ + void setDataWithLength(in unsigned long length, + [size_is(length)] in wstring data); +}; + +%{C++ + +#define NS_PREFLOCALIZEDSTRING_CID \ + { /* {064d9cee-1dd2-11b2-83e3-d25ab0193c26} */ \ + 0x064d9cee, \ + 0x1dd2, \ + 0x11b2, \ + { 0x83, 0xe3, 0xd2, 0x5a, 0xb0, 0x19, 0x3c, 0x26 } \ + } + +#define NS_PREFLOCALIZEDSTRING_CONTRACTID "@mozilla.org/pref-localizedstring;1" + +%} diff --git a/components/preferences/public/nsIPrefService.idl b/components/preferences/public/nsIPrefService.idl new file mode 100644 index 000000000..0db401996 --- /dev/null +++ b/components/preferences/public/nsIPrefService.idl @@ -0,0 +1,160 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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" +#include "nsIPrefBranch.idl" + +%{C++ +struct PrefTuple; +#include "nsTArrayForwardDeclare.h" +%} + +[ptr] native nsPreferencesArrayPtr(nsTArray<PrefTuple>); +[ptr] native nsPreferencePtr(PrefTuple); +[ptr] native nsPreferencePtrConst(const PrefTuple); + +interface nsIFile; + +/** + * The nsIPrefService interface is the main entry point into the back end + * preferences management library. The preference service is directly + * responsible for the management of the preferences files and also facilitates + * access to the preference branch object which allows the direct manipulation + * of the preferences themselves. + * + * @see nsIPrefBranch + */ + +[scriptable, uuid(1f84fd56-3956-40df-b86a-1ea01402ee96)] +interface nsIPrefService : nsISupports +{ + /** + * Called to read in the preferences specified in a user preference file. + * + * @param aFile The file to be read. + * + * @note + * If nullptr is passed in for the aFile parameter the default preferences + * file(s) [prefs.js, user.js] will be read and processed. + * + * @throws Error File failed to read or contained invalid data. + * + * @see savePrefFile + * @see nsIFile + */ + void readUserPrefs(in nsIFile aFile); + + /** + * Called to completely flush and re-initialize the preferences system. + * + * @throws Error The preference service failed to restart correctly. + */ + void resetPrefs(); + + /** + * Called to reset all preferences with user set values back to the + * application default values. + */ + void resetUserPrefs(); + + /** + * Called to write current preferences state to a file. + * + * @param aFile The file to be written. + * + * @note + * If nullptr is passed in for the aFile parameter the preference data is + * written out to the current preferences file (usually prefs.js.) + * + * @throws Error File failed to write. + * + * @see readUserPrefs + * @see nsIFile + */ + void savePrefFile(in nsIFile aFile); + + /** + * Call to get a Preferences "Branch" which accesses user preference data. + * Using a Set method on this object will always create or set a user + * preference value. When using a Get method a user set value will be + * returned if one exists, otherwise a default value will be returned. + * + * @param aPrefRoot The preference "root" on which to base this "branch". + * For example, if the root "browser.startup." is used, the + * branch will be able to easily access the preferences + * "browser.startup.page", "browser.startup.homepage", or + * "browser.startup.homepage_override" by simply requesting + * "page", "homepage", or "homepage_override". nullptr or "" + * may be used to access to the entire preference "tree". + * + * @return nsIPrefBranch The object representing the requested branch. + * + * @see getDefaultBranch + */ + nsIPrefBranch getBranch(in string aPrefRoot); + + /** + * Call to get a Preferences "Branch" which accesses only the default + * preference data. Using a Set method on this object will always create or + * set a default preference value. When using a Get method a default value + * will always be returned. + * + * @param aPrefRoot The preference "root" on which to base this "branch". + * For example, if the root "browser.startup." is used, the + * branch will be able to easily access the preferences + * "browser.startup.page", "browser.startup.homepage", or + * "browser.startup.homepage_override" by simply requesting + * "page", "homepage", or "homepage_override". nullptr or "" + * may be used to access to the entire preference "tree". + * + * @note + * Few consumers will want to create default branch objects. Many of the + * branch methods do nothing on a default branch because the operations only + * make sense when applied to user set preferences. + * + * @return nsIPrefBranch The object representing the requested default branch. + * + * @see getBranch + */ + nsIPrefBranch getDefaultBranch(in string aPrefRoot); + + /** + * The preference service is 'dirty' if there are changes to user preferences + * that have not been written to disk + */ + readonly attribute boolean dirty; +}; + +%{C++ + +#define NS_PREFSERVICE_CID \ + { /* {1cd91b88-1dd2-11b2-92e1-ed22ed298000} */ \ + 0x91ca2441, \ + 0x050f, \ + 0x4f7c, \ + { 0x9d, 0xf8, 0x75, 0xb4, 0x0e, 0xa4, 0x01, 0x56 } \ + } + +#define NS_PREFSERVICE_CONTRACTID "@mozilla.org/preferences-service;1" + +/** + * Notification sent before reading the default user preferences files. + */ +#define NS_PREFSERVICE_READ_TOPIC_ID "prefservice:before-read-userprefs" + +/** + * Notification sent when resetPrefs has been called, but before the actual + * reset process occurs. + */ +#define NS_PREFSERVICE_RESET_TOPIC_ID "prefservice:before-reset" + +/** + * Notification sent when after reading app-provided default + * preferences, but before user profile override defaults or extension + * defaults are loaded. + */ +#define NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID "prefservice:after-app-defaults" + +%} diff --git a/components/preferences/public/nsIRelativeFilePref.idl b/components/preferences/public/nsIRelativeFilePref.idl new file mode 100644 index 000000000..4b86e3755 --- /dev/null +++ b/components/preferences/public/nsIRelativeFilePref.idl @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 nsIFile; + +/** + * The nsIRelativeFilePref interface is a wrapper for an nsIFile and + * and a directory service key. When used as a pref value, it stores a + * relative path to the file from the location pointed to by the directory + * service key. The path has the same syntax across all platforms. + * + * @see nsIPrefBranch::getComplexValue + * @see nsIPrefBranch::setComplexValue + * + */ + +[scriptable, uuid(2f977d4e-5485-11d4-87e2-0010a4e75ef2)] +interface nsIRelativeFilePref : nsISupports +{ + /** + * file + * + * The file whose location is stored or retrieved. + */ + attribute nsIFile file; + + /** + * relativeToKey + * + * A directory service key for the directory + * from which the file path is relative. + */ + attribute ACString relativeToKey; + +}; + +%{C++ + +#define NS_RELATIVEFILEPREF_CID \ + { /* {2f977d4f-5485-11d4-87e2-0010a4e75ef2} */ \ + 0x2f977d4f, \ + 0x5485, \ + 0x11d4, \ + { 0x87, 0xe2, 0x00, 0x10, 0xa4, 0xe7, 0x5e, 0xf2 } \ + } + +#define NS_RELATIVEFILEPREF_CONTRACTID "@mozilla.org/pref-relativefile;1" + +#include "nsComponentManagerUtils.h" + +inline nsresult +NS_NewRelativeFilePref(nsIFile* aFile, const nsACString& relativeToKey, nsIRelativeFilePref** result) +{ + nsresult rv; + nsCOMPtr<nsIRelativeFilePref> local(do_CreateInstance(NS_RELATIVEFILEPREF_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + (void)local->SetFile(aFile); + (void)local->SetRelativeToKey(relativeToKey); + + *result = local; + NS_ADDREF(*result); + return NS_OK; +} + +%} diff --git a/components/preferences/src/Preferences.cpp b/components/preferences/src/Preferences.cpp new file mode 100644 index 000000000..5f17125da --- /dev/null +++ b/components/preferences/src/Preferences.cpp @@ -0,0 +1,1988 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/MemoryReporting.h" +#include "mozilla/dom/ContentChild.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/UniquePtrExtensions.h" + +#include "nsXULAppAPI.h" + +#include "mozilla/Preferences.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDataHashtable.h" +#include "nsDirectoryServiceDefs.h" +#include "nsICategoryManager.h" +#include "nsCategoryManagerUtils.h" +#include "nsNetUtil.h" +#include "nsIFile.h" +#include "nsIInputStream.h" +#include "nsIObserverService.h" +#include "nsIOutputStream.h" +#include "nsISafeOutputStream.h" +#include "nsISimpleEnumerator.h" +#include "nsIStringEnumerator.h" +#include "nsIZipReader.h" +#include "nsPrefBranch.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsCOMArray.h" +#include "nsXPCOMCID.h" +#include "nsAutoPtr.h" +#include "nsPrintfCString.h" + +#include "nsQuickSort.h" +#include "PLDHashTable.h" + +#include "prefapi.h" +#include "prefread.h" +#include "prefapi_private_data.h" + +#include "mozilla/Omnijar.h" +#include "nsZipArchive.h" + +#include "nsTArray.h" +#include "nsRefPtrHashtable.h" +#include "nsIMemoryReporter.h" +#include "nsThreadUtils.h" + +#ifdef DEBUG +#define ENSURE_MAIN_PROCESS(message, pref) do { \ + if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ + nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref); \ + NS_WARNING(msg.get()); \ + return NS_ERROR_NOT_AVAILABLE; \ + } \ +} while (0); +#else +#define ENSURE_MAIN_PROCESS(message, pref) \ + if (MOZ_UNLIKELY(!XRE_IsParentProcess())) { \ + return NS_ERROR_NOT_AVAILABLE; \ + } +#endif + +class PrefCallback; + +namespace mozilla { + +// Definitions +#define INITIAL_PREF_FILES 10 +static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID); + +void +Preferences::DirtyCallback() +{ + if (gHashTable && sPreferences && !sPreferences->mDirty) { + sPreferences->mDirty = true; + } +} + +// Prototypes +static nsresult openPrefFile(nsIFile* aFile); +static nsresult pref_InitInitialObjects(void); +static nsresult pref_LoadPrefsInDirList(const char *listId); +static nsresult ReadExtensionPrefs(nsIFile *aFile); + +static const char kChannelPref[] = "app.update.channel"; + +static const char kPrefFileHeader[] = + "# Mozilla User Preferences" + NS_LINEBREAK + NS_LINEBREAK + "/* Do not edit this file." + NS_LINEBREAK + " *" + NS_LINEBREAK + " * If you make changes to this file while the application is running," + NS_LINEBREAK + " * the changes will be overwritten when the application exits." + NS_LINEBREAK + " *" + NS_LINEBREAK + " * To make a manual change to preferences, you can visit the URL about:config" + NS_LINEBREAK + " */" + NS_LINEBREAK + NS_LINEBREAK; + +Preferences* Preferences::sPreferences = nullptr; +nsIPrefBranch* Preferences::sRootBranch = nullptr; +nsIPrefBranch* Preferences::sDefaultRootBranch = nullptr; +bool Preferences::sShutdown = false; + +class ValueObserverHashKey : public PLDHashEntryHdr { +public: + typedef ValueObserverHashKey* KeyType; + typedef const ValueObserverHashKey* KeyTypePointer; + + static const ValueObserverHashKey* KeyToPointer(ValueObserverHashKey *aKey) + { + return aKey; + } + + static PLDHashNumber HashKey(const ValueObserverHashKey *aKey) + { + PLDHashNumber hash = HashString(aKey->mPrefName); + hash = AddToHash(hash, aKey->mMatchKind); + return AddToHash(hash, aKey->mCallback); + } + + ValueObserverHashKey(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind) : + mPrefName(aPref), mCallback(aCallback), mMatchKind(aMatchKind) { } + + explicit ValueObserverHashKey(const ValueObserverHashKey *aOther) : + mPrefName(aOther->mPrefName), + mCallback(aOther->mCallback), + mMatchKind(aOther->mMatchKind) + { } + + bool KeyEquals(const ValueObserverHashKey *aOther) const + { + return mCallback == aOther->mCallback && + mPrefName == aOther->mPrefName && + mMatchKind == aOther->mMatchKind; + } + + ValueObserverHashKey *GetKey() const + { + return const_cast<ValueObserverHashKey*>(this); + } + + enum { ALLOW_MEMMOVE = true }; + + nsCString mPrefName; + PrefChangedFunc mCallback; + Preferences::MatchKind mMatchKind; +}; + +class ValueObserver final : public nsIObserver, + public ValueObserverHashKey +{ + ~ValueObserver() { + Preferences::RemoveObserver(this, mPrefName.get()); + } + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + ValueObserver(const char *aPref, PrefChangedFunc aCallback, Preferences::MatchKind aMatchKind) + : ValueObserverHashKey(aPref, aCallback, aMatchKind) { } + + void AppendClosure(void *aClosure) { + mClosures.AppendElement(aClosure); + } + + void RemoveClosure(void *aClosure) { + mClosures.RemoveElement(aClosure); + } + + bool HasNoClosures() { + return mClosures.Length() == 0; + } + + nsTArray<void*> mClosures; +}; + +NS_IMPL_ISUPPORTS(ValueObserver, nsIObserver) + +NS_IMETHODIMP +ValueObserver::Observe(nsISupports *aSubject, + const char *aTopic, + const char16_t *aData) +{ + NS_ASSERTION(!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID), + "invalid topic"); + NS_ConvertUTF16toUTF8 data(aData); + if (mMatchKind == Preferences::ExactMatch && !mPrefName.EqualsASCII(data.get())) { + return NS_OK; + } + for (uint32_t i = 0; i < mClosures.Length(); i++) { + mCallback(data.get(), mClosures.ElementAt(i)); + } + + return NS_OK; +} + +struct CacheData { + void* cacheLocation; + union { + bool defaultValueBool; + int32_t defaultValueInt; + uint32_t defaultValueUint; + float defaultValueFloat; + }; +}; + +static nsTArray<nsAutoPtr<CacheData> >* gCacheData = nullptr; +static nsRefPtrHashtable<ValueObserverHashKey, + ValueObserver>* gObserverTable = nullptr; + +#ifdef DEBUG +static bool +HaveExistingCacheFor(void* aPtr) +{ + MOZ_ASSERT(NS_IsMainThread()); + if (gCacheData) { + for (size_t i = 0, count = gCacheData->Length(); i < count; ++i) { + if ((*gCacheData)[i]->cacheLocation == aPtr) { + return true; + } + } + } + return false; +} + +static void +AssertNotAlreadyCached(const char* aPrefType, + const char* aPref, + void* aPtr) +{ + if (HaveExistingCacheFor(aPtr)) { + fprintf_stderr(stderr, + "Attempt to add a %s pref cache for preference '%s' at address '%p'" + "was made. However, a pref was already cached at this address.\n", + aPrefType, aPref, aPtr); + MOZ_ASSERT(false, "Should not have an existing pref cache for this address"); + } +} +#endif + +static void +ReportToConsole(const char* aMessage, int aLine, bool aError) +{ + nsPrintfCString message("** Preference parsing %s (line %d) = %s **\n", + (aError ? "error" : "warning"), aLine, aMessage); + nsPrefBranch::ReportToConsole(NS_ConvertUTF8toUTF16(message.get())); +} + +// Although this is a member of Preferences, it measures sPreferences and +// several other global structures. +/* static */ int64_t +Preferences::SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf) +{ + NS_ENSURE_TRUE(InitStaticMembers(), 0); + + size_t n = aMallocSizeOf(sPreferences); + if (gHashTable) { + // pref keys are allocated in a private arena, which we count elsewhere. + // pref stringvals are allocated out of the same private arena. + n += gHashTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + } + if (gCacheData) { + n += gCacheData->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (uint32_t i = 0, count = gCacheData->Length(); i < count; ++i) { + n += aMallocSizeOf((*gCacheData)[i]); + } + } + if (gObserverTable) { + n += gObserverTable->ShallowSizeOfIncludingThis(aMallocSizeOf); + for (auto iter = gObserverTable->Iter(); !iter.Done(); iter.Next()) { + n += iter.Key()->mPrefName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += iter.Data()->mClosures.ShallowSizeOfExcludingThis(aMallocSizeOf); + } + } + if (sRootBranch) { + n += reinterpret_cast<nsPrefBranch*>(sRootBranch)->SizeOfIncludingThis(aMallocSizeOf); + } + if (sDefaultRootBranch) { + n += reinterpret_cast<nsPrefBranch*>(sDefaultRootBranch)->SizeOfIncludingThis(aMallocSizeOf); + } + n += pref_SizeOfPrivateData(aMallocSizeOf); + return n; +} + +class PreferenceServiceReporter final : public nsIMemoryReporter +{ + ~PreferenceServiceReporter() {} + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMEMORYREPORTER + +protected: + static const uint32_t kSuspectReferentCount = 1000; +}; + +NS_IMPL_ISUPPORTS(PreferenceServiceReporter, nsIMemoryReporter) + +MOZ_DEFINE_MALLOC_SIZE_OF(PreferenceServiceMallocSizeOf) + +NS_IMETHODIMP +PreferenceServiceReporter::CollectReports( + nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize) +{ + MOZ_COLLECT_REPORT( + "explicit/preferences", KIND_HEAP, UNITS_BYTES, + Preferences::SizeOfIncludingThisAndOtherStuff(PreferenceServiceMallocSizeOf), + "Memory used by the preferences system."); + + nsPrefBranch* rootBranch = + static_cast<nsPrefBranch*>(Preferences::GetRootBranch()); + if (!rootBranch) { + return NS_OK; + } + + size_t numStrong = 0; + size_t numWeakAlive = 0; + size_t numWeakDead = 0; + nsTArray<nsCString> suspectPreferences; + // Count of the number of referents for each preference. + nsDataHashtable<nsCStringHashKey, uint32_t> prefCounter; + + for (auto iter = rootBranch->mObservers.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<PrefCallback>& callback = iter.Data(); + nsPrefBranch* prefBranch = callback->GetPrefBranch(); + const char* pref = prefBranch->getPrefName(callback->GetDomain().get()); + + if (callback->IsWeak()) { + nsCOMPtr<nsIObserver> callbackRef = do_QueryReferent(callback->mWeakRef); + if (callbackRef) { + numWeakAlive++; + } else { + numWeakDead++; + } + } else { + numStrong++; + } + + nsDependentCString prefString(pref); + uint32_t oldCount = 0; + prefCounter.Get(prefString, &oldCount); + uint32_t currentCount = oldCount + 1; + prefCounter.Put(prefString, currentCount); + + // Keep track of preferences that have a suspiciously large number of + // referents (a symptom of a leak). + if (currentCount == kSuspectReferentCount) { + suspectPreferences.AppendElement(prefString); + } + } + + for (uint32_t i = 0; i < suspectPreferences.Length(); i++) { + nsCString& suspect = suspectPreferences[i]; + uint32_t totalReferentCount = 0; + prefCounter.Get(suspect, &totalReferentCount); + + nsPrintfCString suspectPath("preference-service-suspect/" + "referent(pref=%s)", suspect.get()); + + aHandleReport->Callback( + /* process = */ EmptyCString(), + suspectPath, KIND_OTHER, UNITS_COUNT, totalReferentCount, + NS_LITERAL_CSTRING( + "A preference with a suspiciously large number referents (symptom of a " + "leak)."), + aData); + } + + MOZ_COLLECT_REPORT( + "preference-service/referent/strong", KIND_OTHER, UNITS_COUNT, + numStrong, + "The number of strong referents held by the preference service."); + + MOZ_COLLECT_REPORT( + "preference-service/referent/weak/alive", KIND_OTHER, UNITS_COUNT, + numWeakAlive, + "The number of weak referents held by the preference service that are " + "still alive."); + + MOZ_COLLECT_REPORT( + "preference-service/referent/weak/dead", KIND_OTHER, UNITS_COUNT, + numWeakDead, + "The number of weak referents held by the preference service that are " + "dead."); + + return NS_OK; +} + +namespace { +class AddPreferencesMemoryReporterRunnable : public Runnable +{ + NS_IMETHOD Run() override + { + return RegisterStrongMemoryReporter(new PreferenceServiceReporter()); + } +}; +} // namespace + +// static +Preferences* +Preferences::GetInstanceForService() +{ + if (sPreferences) { + NS_ADDREF(sPreferences); + return sPreferences; + } + + NS_ENSURE_TRUE(!sShutdown, nullptr); + + sRootBranch = new nsPrefBranch("", false); + NS_ADDREF(sRootBranch); + sDefaultRootBranch = new nsPrefBranch("", true); + NS_ADDREF(sDefaultRootBranch); + + sPreferences = new Preferences(); + NS_ADDREF(sPreferences); + + if (NS_FAILED(sPreferences->Init())) { + // The singleton instance will delete sRootBranch and sDefaultRootBranch. + NS_RELEASE(sPreferences); + return nullptr; + } + + gCacheData = new nsTArray<nsAutoPtr<CacheData> >(); + + gObserverTable = new nsRefPtrHashtable<ValueObserverHashKey, ValueObserver>(); + + // Preferences::GetInstanceForService() can be called from GetService(), and + // RegisterStrongMemoryReporter calls GetService(nsIMemoryReporter). To + // avoid a potential recursive GetService() call, we can't register the + // memory reporter here; instead, do it off a runnable. + RefPtr<AddPreferencesMemoryReporterRunnable> runnable = + new AddPreferencesMemoryReporterRunnable(); + NS_DispatchToMainThread(runnable); + + NS_ADDREF(sPreferences); + return sPreferences; +} + +// static +bool +Preferences::IsServiceAvailable() +{ + return !!sPreferences; +} + +// static +bool +Preferences::InitStaticMembers() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!sShutdown && !sPreferences) { + nsCOMPtr<nsIPrefService> prefService = + do_GetService(NS_PREFSERVICE_CONTRACTID); + } + + return sPreferences != nullptr; +} + +// static +void +Preferences::Shutdown() +{ + if (!sShutdown) { + sShutdown = true; // Don't create the singleton instance after here. + + // Don't set sPreferences to nullptr here. The instance may be grabbed by + // other modules. The utility methods of Preferences should be available + // until the singleton instance actually released. + if (sPreferences) { + sPreferences->Release(); + } + } +} + +//----------------------------------------------------------------------------- + +/* + * Constructor/Destructor + */ + +Preferences::Preferences() + : mDirty(false) +{ +} + +Preferences::~Preferences() +{ + NS_ASSERTION(sPreferences == this, "Isn't this the singleton instance?"); + + delete gObserverTable; + gObserverTable = nullptr; + + delete gCacheData; + gCacheData = nullptr; + + NS_RELEASE(sRootBranch); + NS_RELEASE(sDefaultRootBranch); + + sPreferences = nullptr; + + PREF_Cleanup(); +} + + +/* + * nsISupports Implementation + */ + +NS_IMPL_ADDREF(Preferences) +NS_IMPL_RELEASE(Preferences) + +NS_INTERFACE_MAP_BEGIN(Preferences) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService) + NS_INTERFACE_MAP_ENTRY(nsIPrefService) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranch) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + + +/* + * nsIPrefService Implementation + */ + +nsresult +Preferences::Init() +{ + nsresult rv; + + PREF_SetDirtyCallback(&DirtyCallback); + PREF_Init(); + + rv = pref_InitInitialObjects(); + NS_ENSURE_SUCCESS(rv, rv); + + using mozilla::dom::ContentChild; + if (XRE_IsContentProcess()) { + InfallibleTArray<PrefSetting> prefs; + ContentChild::GetSingleton()->SendReadPrefsArray(&prefs); + + // Store the array + for (uint32_t i = 0; i < prefs.Length(); ++i) { + pref_SetPref(prefs[i]); + } + return NS_OK; + } + + nsXPIDLCString lockFileName; + /* + * The following is a small hack which will allow us to only load the library + * which supports the netscape.cfg file if the preference is defined. We + * test for the existence of the pref, set in the all.js (mozilla) or + * all-ns.js (netscape 6), and if it exists we startup the pref config + * category which will do the rest. + */ + + rv = PREF_CopyCharPref("general.config.filename", getter_Copies(lockFileName), false); + if (NS_SUCCEEDED(rv)) + NS_CreateServicesFromCategory("pref-config-startup", + static_cast<nsISupports *>(static_cast<void *>(this)), + "pref-config-startup"); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + rv = observerService->AddObserver(this, "profile-before-change", true); + + observerService->AddObserver(this, "load-extension-defaults", true); + observerService->AddObserver(this, "suspend_process_notification", true); + + return(rv); +} + +// static +nsresult +Preferences::ResetAndReadUserPrefs() +{ + sPreferences->ResetUserPrefs(); + return sPreferences->ReadUserPrefs(nullptr); +} + +NS_IMETHODIMP +Preferences::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *someData) +{ + if (XRE_IsContentProcess()) + return NS_ERROR_NOT_AVAILABLE; + + nsresult rv = NS_OK; + + if (!nsCRT::strcmp(aTopic, "profile-before-change")) { + rv = SavePrefFile(nullptr); + } else if (!strcmp(aTopic, "load-extension-defaults")) { + pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST); + } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) { + // Reload the default prefs from file. + pref_InitInitialObjects(); + } else if (!nsCRT::strcmp(aTopic, "suspend_process_notification")) { + // Our process is being suspended. The OS may wake our process later, + // or it may kill the process. In case our process is going to be killed + // from the suspended state, we save preferences before suspending. + rv = SavePrefFile(nullptr); + } + return rv; +} + + +NS_IMETHODIMP +Preferences::ReadUserPrefs(nsIFile *aFile) +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot load prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv; + + if (nullptr == aFile) { + rv = UseDefaultPrefFile(); + // A user pref file is optional. + // Ignore all errors related to it, so we retain 'rv' value :-| + (void) UseUserPrefFile(); + + NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID); + } else { + rv = ReadAndOwnUserPrefFile(aFile); + } + + return rv; +} + +NS_IMETHODIMP +Preferences::ResetPrefs() +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot reset prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID); + PREF_CleanupPrefs(); + + PREF_Init(); + + return pref_InitInitialObjects(); +} + +NS_IMETHODIMP +Preferences::ResetUserPrefs() +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot reset user prefs from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + PREF_ClearAllUserPrefs(); + return NS_OK; +} + +NS_IMETHODIMP +Preferences::SavePrefFile(nsIFile *aFile) +{ + if (XRE_IsContentProcess()) { + NS_ERROR("cannot save pref file from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + return SavePrefFileInternal(aFile); +} + +static nsresult +ReadExtensionPrefs(nsIFile *aFile) +{ + nsresult rv; + nsCOMPtr<nsIZipReader> reader = do_CreateInstance(kZipReaderCID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = reader->Open(aFile); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIUTF8StringEnumerator> files; + rv = reader->FindEntries(nsDependentCString("defaults/preferences/*.(J|j)(S|s)$"), + getter_AddRefs(files)); + NS_ENSURE_SUCCESS(rv, rv); + + char buffer[4096]; + + bool more; + while (NS_SUCCEEDED(rv = files->HasMore(&more)) && more) { + nsAutoCString entry; + rv = files->GetNext(entry); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIInputStream> stream; + rv = reader->GetInputStream(entry, getter_AddRefs(stream)); + NS_ENSURE_SUCCESS(rv, rv); + + uint64_t avail; + uint32_t read; + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + while (NS_SUCCEEDED(rv = stream->Available(&avail)) && avail) { + rv = stream->Read(buffer, 4096, &read); + if (NS_FAILED(rv)) { + NS_WARNING("Pref stream read failed"); + break; + } + + PREF_ParseBuf(&ps, buffer, read); + } + PREF_FinalizeParseState(&ps); + } + return rv; +} + +void +Preferences::SetPreference(const PrefSetting& aPref) +{ + pref_SetPref(aPref); +} + +void +Preferences::GetPreference(PrefSetting* aPref) +{ + PrefHashEntry *entry = pref_HashTableLookup(aPref->name().get()); + if (!entry) + return; + + if (pref_EntryHasAdvisablySizedValues(entry)) { + pref_GetPrefFromEntry(entry, aPref); + } +} + +void +Preferences::GetPreferences(InfallibleTArray<PrefSetting>* aPrefs) +{ + aPrefs->SetCapacity(gHashTable->Capacity()); + for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PrefHashEntry*>(iter.Get()); + + if (!pref_EntryHasAdvisablySizedValues(entry)) { + continue; + } + + dom::PrefSetting *pref = aPrefs->AppendElement(); + pref_GetPrefFromEntry(entry, pref); + } +} + +NS_IMETHODIMP +Preferences::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval) +{ + nsresult rv; + + if ((nullptr != aPrefRoot) && (*aPrefRoot != '\0')) { + // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) + RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, false); + prefBranch.forget(_retval); + rv = NS_OK; + } else { + // special case caching the default root + nsCOMPtr<nsIPrefBranch> root(sRootBranch); + root.forget(_retval); + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +Preferences::GetDefaultBranch(const char *aPrefRoot, nsIPrefBranch **_retval) +{ + if (!aPrefRoot || !aPrefRoot[0]) { + nsCOMPtr<nsIPrefBranch> root(sDefaultRootBranch); + root.forget(_retval); + return NS_OK; + } + + // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) + RefPtr<nsPrefBranch> prefBranch = new nsPrefBranch(aPrefRoot, true); + if (!prefBranch) + return NS_ERROR_OUT_OF_MEMORY; + + prefBranch.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP +Preferences::GetDirty(bool *_retval) { + *_retval = mDirty; + return NS_OK; +} + +nsresult +Preferences::NotifyServiceObservers(const char *aTopic) +{ + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + nsISupports *subject = (nsISupports *)((nsIPrefService *)this); + observerService->NotifyObservers(subject, aTopic, nullptr); + + return NS_OK; +} + +nsresult +Preferences::UseDefaultPrefFile() +{ + nsCOMPtr<nsIFile> aFile; + nsresult rv = NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(aFile)); + + if (NS_SUCCEEDED(rv)) { + rv = ReadAndOwnUserPrefFile(aFile); + // Most likely cause of failure here is that the file didn't + // exist, so save a new one. mUserPrefReadFailed will be + // used to catch an error in actually reading the file. + if (NS_FAILED(rv)) { + if (NS_FAILED(SavePrefFileInternal(aFile))) + NS_ERROR("Failed to save new shared pref file"); + else + rv = NS_OK; + } + } + + return rv; +} + +nsresult +Preferences::UseUserPrefFile() +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> aFile; + nsDependentCString prefsDirProp(NS_APP_PREFS_50_DIR); + + rv = NS_GetSpecialDirectory(prefsDirProp.get(), getter_AddRefs(aFile)); + if (NS_SUCCEEDED(rv) && aFile) { + rv = aFile->AppendNative(NS_LITERAL_CSTRING("user.js")); + if (NS_SUCCEEDED(rv)) { + bool exists = false; + aFile->Exists(&exists); + if (exists) { + rv = openPrefFile(aFile); + } else { + rv = NS_ERROR_FILE_NOT_FOUND; + } + } + } + return rv; +} + +nsresult +Preferences::MakeBackupPrefFile(nsIFile *aFile) +{ + // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory. + // "Invalidprefs.js" is removed if it exists, prior to making the copy. + nsAutoString newFilename; + nsresult rv = aFile->GetLeafName(newFilename); + NS_ENSURE_SUCCESS(rv, rv); + newFilename.Insert(NS_LITERAL_STRING("Invalid"), 0); + nsCOMPtr<nsIFile> newFile; + rv = aFile->GetParent(getter_AddRefs(newFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = newFile->Append(newFilename); + NS_ENSURE_SUCCESS(rv, rv); + bool exists = false; + newFile->Exists(&exists); + if (exists) { + rv = newFile->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = aFile->CopyTo(nullptr, newFilename); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult +Preferences::ReadAndOwnUserPrefFile(nsIFile *aFile) +{ + NS_ENSURE_ARG(aFile); + + if (mCurrentFile == aFile) + return NS_OK; + mCurrentFile = aFile; + + nsresult rv = NS_OK; + bool exists = false; + mCurrentFile->Exists(&exists); + if (exists) { + rv = openPrefFile(mCurrentFile); + if (NS_FAILED(rv)) { + // Save a backup copy of the current (invalid) prefs file, since all prefs + // from the error line to the end of the file will be lost (bug 361102). + // TODO we should notify the user about it (bug 523725). + MakeBackupPrefFile(mCurrentFile); + } + } else { + rv = NS_ERROR_FILE_NOT_FOUND; + } + + return rv; +} + +nsresult +Preferences::SavePrefFileInternal(nsIFile *aFile) +{ + if (nullptr == aFile) { + // the mDirty flag tells us if we should write to mCurrentFile + // we only check this flag when the caller wants to write to the default + if (!mDirty) { + return NS_OK; + } + + // It's possible that we never got a prefs file. + nsresult rv = NS_OK; + if (mCurrentFile) + rv = WritePrefFile(mCurrentFile); + + return rv; + } else { + return WritePrefFile(aFile); + } +} + +nsresult +Preferences::WritePrefFile(nsIFile* aFile) +{ + nsCOMPtr<nsIOutputStream> outStreamSink; + nsCOMPtr<nsIOutputStream> outStream; + uint32_t writeAmount; + nsresult rv; + + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + // execute a "safe" save by saving through a tempfile + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), + aFile, + -1, + 0600); + if (NS_FAILED(rv)) + return rv; + rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096); + if (NS_FAILED(rv)) + return rv; + + // get the lines that we're supposed to be writing to the file + uint32_t prefCount; + UniquePtr<char*[]> valueArray = pref_savePrefs(gHashTable, &prefCount); + + /* Sort the preferences to make a readable file on disk */ + NS_QuickSort(valueArray.get(), prefCount, sizeof(char *), + pref_CompareStrings, nullptr); + + // write out the file header + outStream->Write(kPrefFileHeader, sizeof(kPrefFileHeader) - 1, &writeAmount); + + for (uint32_t valueIdx = 0; valueIdx < prefCount; valueIdx++) { + char*& pref = valueArray[valueIdx]; + MOZ_ASSERT(pref); + outStream->Write(pref, strlen(pref), &writeAmount); + outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount); + free(pref); + pref = nullptr; + } + + // tell the safe output stream to overwrite the real prefs file + // (it'll abort if there were any errors during writing) + nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(outStream); + NS_ASSERTION(safeStream, "expected a safe output stream!"); + if (safeStream) { + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save prefs file! possible data loss"); + return rv; + } + } + + mDirty = false; + return NS_OK; +} + +static nsresult openPrefFile(nsIFile* aFile) +{ + nsCOMPtr<nsIInputStream> inStr; + + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile); + if (NS_FAILED(rv)) + return rv; + + int64_t fileSize64; + rv = aFile->GetFileSize(&fileSize64); + if (NS_FAILED(rv)) + return rv; + NS_ENSURE_TRUE(fileSize64 <= UINT32_MAX, NS_ERROR_FILE_TOO_BIG); + + uint32_t fileSize = (uint32_t)fileSize64; + auto fileBuffer = MakeUniqueFallible<char[]>(fileSize); + if (fileBuffer == nullptr) + return NS_ERROR_OUT_OF_MEMORY; + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + + // Read is not guaranteed to return a buf the size of fileSize, + // but usually will. + nsresult rv2 = NS_OK; + uint32_t offset = 0; + for (;;) { + uint32_t amtRead = 0; + rv = inStr->Read(fileBuffer.get(), fileSize, &amtRead); + if (NS_FAILED(rv) || amtRead == 0) + break; + if (!PREF_ParseBuf(&ps, fileBuffer.get(), amtRead)) + rv2 = NS_ERROR_FILE_CORRUPTED; + offset += amtRead; + if (offset == fileSize) { + break; + } + } + + PREF_FinalizeParseState(&ps); + + return NS_FAILED(rv) ? rv : rv2; +} + +/* + * some stuff that gets called from Pref_Init() + */ + +static int +pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /*unused*/) +{ + nsAutoCString filename1, filename2; + aFile1->GetNativeLeafName(filename1); + aFile2->GetNativeLeafName(filename2); + + return Compare(filename2, filename1); +} + +/** + * Load default pref files from a directory. The files in the + * directory are sorted reverse-alphabetically; a set of "special file + * names" may be specified which are loaded after all the others. + */ +static nsresult +pref_LoadPrefsInDir(nsIFile* aDir, char const *const *aSpecialFiles, uint32_t aSpecialFilesCount) +{ + nsresult rv, rv2; + bool hasMoreElements; + + nsCOMPtr<nsISimpleEnumerator> dirIterator; + + // this may fail in some normal cases, such as embedders who do not use a GRE + rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); + if (NS_FAILED(rv)) { + // If the directory doesn't exist, then we have no reason to complain. We + // loaded everything (and nothing) successfully. + if (rv == NS_ERROR_FILE_NOT_FOUND || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) + rv = NS_OK; + return rv; + } + + rv = dirIterator->HasMoreElements(&hasMoreElements); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMArray<nsIFile> prefFiles(INITIAL_PREF_FILES); + nsCOMArray<nsIFile> specialFiles(aSpecialFilesCount); + nsCOMPtr<nsIFile> prefFile; + + while (hasMoreElements && NS_SUCCEEDED(rv)) { + nsAutoCString leafName; + + nsCOMPtr<nsISupports> supports; + rv = dirIterator->GetNext(getter_AddRefs(supports)); + prefFile = do_QueryInterface(supports); + if (NS_FAILED(rv)) { + break; + } + + prefFile->GetNativeLeafName(leafName); + NS_ASSERTION(!leafName.IsEmpty(), "Failure in default prefs: directory enumerator returned empty file?"); + + // Skip non-js files + if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".js"), + nsCaseInsensitiveCStringComparator())) { + bool shouldParse = true; + // separate out special files + for (uint32_t i = 0; i < aSpecialFilesCount; ++i) { + if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) { + shouldParse = false; + // special files should be process in order; we put them into + // the array by index; this can make the array sparse + specialFiles.ReplaceObjectAt(prefFile, i); + } + } + + if (shouldParse) { + prefFiles.AppendObject(prefFile); + } + } + + rv = dirIterator->HasMoreElements(&hasMoreElements); + } + + if (prefFiles.Count() + specialFiles.Count() == 0) { + NS_WARNING("No default pref files found."); + if (NS_SUCCEEDED(rv)) { + rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY; + } + return rv; + } + + prefFiles.Sort(pref_CompareFileNames, nullptr); + + uint32_t arrayCount = prefFiles.Count(); + uint32_t i; + for (i = 0; i < arrayCount; ++i) { + rv2 = openPrefFile(prefFiles[i]); + if (NS_FAILED(rv2)) { + NS_ERROR("Default pref file not parsed successfully."); + rv = rv2; + } + } + + arrayCount = specialFiles.Count(); + for (i = 0; i < arrayCount; ++i) { + // this may be a sparse array; test before parsing + nsIFile* file = specialFiles[i]; + if (file) { + rv2 = openPrefFile(file); + if (NS_FAILED(rv2)) { + NS_ERROR("Special default pref file not parsed successfully."); + rv = rv2; + } + } + } + + return rv; +} + +static nsresult pref_LoadPrefsInDirList(const char *listId) +{ + nsresult rv; + nsCOMPtr<nsIProperties> dirSvc(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsISimpleEnumerator> list; + dirSvc->Get(listId, + NS_GET_IID(nsISimpleEnumerator), + getter_AddRefs(list)); + if (!list) + return NS_OK; + + bool hasMore; + while (NS_SUCCEEDED(list->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> elem; + list->GetNext(getter_AddRefs(elem)); + if (!elem) + continue; + + nsCOMPtr<nsIFile> path = do_QueryInterface(elem); + if (!path) + continue; + + nsAutoCString leaf; + path->GetNativeLeafName(leaf); + + // Do we care if a file provided by this process fails to load? + if (Substring(leaf, leaf.Length() - 4).EqualsLiteral(".xpi")) + ReadExtensionPrefs(path); + else + pref_LoadPrefsInDir(path, nullptr, 0); + } + return NS_OK; +} + +static nsresult pref_ReadPrefFromJar(nsZipArchive* jarReader, const char *name) +{ + nsZipItemPtr<char> manifest(jarReader, name, true); + NS_ENSURE_TRUE(manifest.Buffer(), NS_ERROR_NOT_AVAILABLE); + + PrefParseState ps; + PREF_InitParseState(&ps, PREF_ReaderCallback, ReportToConsole, nullptr); + PREF_ParseBuf(&ps, manifest, manifest.Length()); + PREF_FinalizeParseState(&ps); + + return NS_OK; +} + +//---------------------------------------------------------------------------------------- +// Initialize default preference JavaScript buffers from +// appropriate TEXT resources +//---------------------------------------------------------------------------------------- +static nsresult pref_InitInitialObjects() +{ + nsresult rv; + + // In omni.jar case, we load the following prefs: + // - jar:$gre/omni.jar!/goanna.js + // - jar:$gre/omni.jar!/defaults/pref/*.js + // In non omni.jar case, we load: + // - $gre/goanna.js + // + // In both cases, we also load: + // - $gre/defaults/pref/*.js + // This is kept for bug 591866 (channel-prefs.js should not be in omni.jar) + // on $app == $gre case ; we load all files instead of channel-prefs.js only + // to have the same behaviour as $app != $gre, where this is required as + // a supported location for GRE preferences. + // + // When $app != $gre, we additionally load, in omni.jar case: + // - jar:$app/omni.jar!/defaults/preferences/*.js + // - $app/defaults/preferences/*.js + // and in non omni.jar case: + // - $app/defaults/preferences/*.js + // When $app == $gre, we additionally load, in omni.jar case: + // - jar:$gre/omni.jar!/defaults/preferences/*.js + // Thus, in omni.jar case, we always load app-specific default preferences + // from omni.jar, whether or not $app == $gre. + + nsZipFind *findPtr; + nsAutoPtr<nsZipFind> find; + nsTArray<nsCString> prefEntries; + const char *entryName; + uint16_t entryNameLen; + + RefPtr<nsZipArchive> jarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (jarReader) { + // Load jar:$gre/omni.jar!/goanna.js + rv = pref_ReadPrefFromJar(jarReader, "goanna.js"); + NS_ENSURE_SUCCESS(rv, rv); + + // Load jar:$gre/omni.jar!/defaults/pref/*.js + rv = jarReader->FindInit("defaults/pref/*.js$", &findPtr); + NS_ENSURE_SUCCESS(rv, rv); + + find = findPtr; + while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) { + prefEntries.AppendElement(Substring(entryName, entryNameLen)); + } + + prefEntries.Sort(); + for (uint32_t i = prefEntries.Length(); i--; ) { + rv = pref_ReadPrefFromJar(jarReader, prefEntries[i].get()); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing preferences."); + } + } else { + // Load $gre/goanna.js + nsCOMPtr<nsIFile> greprefsFile; + rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(greprefsFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = greprefsFile->AppendNative(NS_LITERAL_CSTRING("goanna.js")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = openPrefFile(greprefsFile); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?"); + } + + // Load $gre/defaults/pref/*.js + nsCOMPtr<nsIFile> defaultPrefDir; + + rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, getter_AddRefs(defaultPrefDir)); + NS_ENSURE_SUCCESS(rv, rv); + + /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */ + static const char* specialFiles[] = { +#if defined(XP_WIN) + "winpref.js" +#elif defined(XP_UNIX) + "unix.js" +#endif + }; + + rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles, ArrayLength(specialFiles)); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing application default preferences."); + + // Load jar:$app/omni.jar!/defaults/preferences/*.js + // or jar:$gre/omni.jar!/defaults/preferences/*.js. + RefPtr<nsZipArchive> appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::APP); + // GetReader(mozilla::Omnijar::APP) returns null when $app == $gre, in which + // case we look for app-specific default preferences in $gre. + if (!appJarReader) + appJarReader = mozilla::Omnijar::GetReader(mozilla::Omnijar::GRE); + if (appJarReader) { + rv = appJarReader->FindInit("defaults/preferences/*.js$", &findPtr); + NS_ENSURE_SUCCESS(rv, rv); + find = findPtr; + prefEntries.Clear(); + while (NS_SUCCEEDED(find->FindNext(&entryName, &entryNameLen))) { + prefEntries.AppendElement(Substring(entryName, entryNameLen)); + } + prefEntries.Sort(); + for (uint32_t i = prefEntries.Length(); i--; ) { + rv = pref_ReadPrefFromJar(appJarReader, prefEntries[i].get()); + if (NS_FAILED(rv)) + NS_WARNING("Error parsing preferences."); + } + } + + rv = pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST); + NS_ENSURE_SUCCESS(rv, rv); + + NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, + nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (!observerService) + return NS_ERROR_FAILURE; + + observerService->NotifyObservers(nullptr, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nullptr); + + return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST); +} + + +/****************************************************************************** + * + * static utilities + * + ******************************************************************************/ + +// static +nsresult +Preferences::GetBool(const char* aPref, bool* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetBoolPref(aPref, aResult, false); +} + +// static +nsresult +Preferences::GetInt(const char* aPref, int32_t* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetIntPref(aPref, aResult, false); +} + +// static +nsresult +Preferences::GetFloat(const char* aPref, float* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + *aResult = result.ToFloat(&rv); + } + + return rv; +} + +// static +nsAdoptingCString +Preferences::GetCString(const char* aPref) +{ + nsAdoptingCString result; + PREF_CopyCharPref(aPref, getter_Copies(result), false); + return result; +} + +// static +nsAdoptingString +Preferences::GetString(const char* aPref) +{ + nsAdoptingString result; + GetString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + *aResult = result; + } + return rv; +} + +// static +nsresult +Preferences::GetString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), false); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(result, *aResult); + } + return rv; +} + +// static +nsAdoptingCString +Preferences::GetLocalizedCString(const char* aPref) +{ + nsAdoptingCString result; + GetLocalizedCString(aPref, &result); + return result; +} + +// static +nsAdoptingString +Preferences::GetLocalizedString(const char* aPref) +{ + nsAdoptingString result; + GetLocalizedString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetLocalizedCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + nsAutoString result; + nsresult rv = GetLocalizedString(aPref, &result); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetLocalizedString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsCOMPtr<nsIPrefLocalizedString> prefLocalString; + nsresult rv = sRootBranch->GetComplexValue(aPref, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefLocalString)); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL"); + prefLocalString->GetData(getter_Copies(*aResult)); + } + return rv; +} + +// static +nsresult +Preferences::GetComplex(const char* aPref, const nsIID &aType, void** aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->GetComplexValue(aPref, aType, aResult); +} + +// static +nsresult +Preferences::SetCString(const char* aPref, const char* aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetCString(const char* aPref, const nsACString &aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetCString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, PromiseFlatCString(aValue).get(), false); +} + +// static +nsresult +Preferences::SetString(const char* aPref, const char16ptr_t aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false); +} + +// static +nsresult +Preferences::SetString(const char* aPref, const nsAString &aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetString from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetCharPref(aPref, NS_ConvertUTF16toUTF8(aValue).get(), false); +} + +// static +nsresult +Preferences::SetBool(const char* aPref, bool aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetBool from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetBoolPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetInt(const char* aPref, int32_t aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetInt from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_SetIntPref(aPref, aValue, false); +} + +// static +nsresult +Preferences::SetFloat(const char* aPref, float aValue) +{ + return SetCString(aPref, nsPrintfCString("%f", aValue).get()); +} + +// static +nsresult +Preferences::SetComplex(const char* aPref, const nsIID &aType, + nsISupports* aValue) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->SetComplexValue(aPref, aType, aValue); +} + +// static +nsresult +Preferences::ClearUser(const char* aPref) +{ + ENSURE_MAIN_PROCESS("Cannot ClearUser from content process:", aPref); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_ClearUserPref(aPref); +} + +// static +bool +Preferences::HasUserValue(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), false); + return PREF_HasUserPref(aPref); +} + +// static +int32_t +Preferences::GetType(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID); + int32_t result; + return NS_SUCCEEDED(sRootBranch->GetPrefType(aPref, &result)) ? + result : nsIPrefBranch::PREF_INVALID; +} + +// static +nsresult +Preferences::AddStrongObserver(nsIObserver* aObserver, + const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->AddObserver(aPref, aObserver, false); +} + +// static +nsresult +Preferences::AddWeakObserver(nsIObserver* aObserver, + const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sRootBranch->AddObserver(aPref, aObserver, true); +} + +// static +nsresult +Preferences::RemoveObserver(nsIObserver* aObserver, + const char* aPref) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + return sRootBranch->RemoveObserver(aPref, aObserver); +} + +// static +nsresult +Preferences::AddStrongObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = AddStrongObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::AddWeakObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = AddWeakObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::RemoveObservers(nsIObserver* aObserver, + const char** aPrefs) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + + for (uint32_t i = 0; aPrefs[i]; i++) { + nsresult rv = RemoveObserver(aObserver, aPrefs[i]); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +// static +nsresult +Preferences::RegisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + + ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind); + RefPtr<ValueObserver> observer; + gObserverTable->Get(&hashKey, getter_AddRefs(observer)); + if (observer) { + observer->AppendClosure(aClosure); + return NS_OK; + } + + observer = new ValueObserver(aPref, aCallback, aMatchKind); + observer->AppendClosure(aClosure); + nsresult rv = AddStrongObserver(observer, aPref); + NS_ENSURE_SUCCESS(rv, rv); + gObserverTable->Put(observer, observer); + return NS_OK; +} + +// static +nsresult +Preferences::RegisterCallbackAndCall(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + nsresult rv = RegisterCallback(aCallback, aPref, aClosure, aMatchKind); + if (NS_SUCCEEDED(rv)) { + (*aCallback)(aPref, aClosure); + } + return rv; +} + +// static +nsresult +Preferences::UnregisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure, + MatchKind aMatchKind) +{ + if (!sPreferences && sShutdown) { + return NS_OK; // Observers have been released automatically. + } + NS_ENSURE_TRUE(sPreferences, NS_ERROR_NOT_AVAILABLE); + + ValueObserverHashKey hashKey(aPref, aCallback, aMatchKind); + RefPtr<ValueObserver> observer; + gObserverTable->Get(&hashKey, getter_AddRefs(observer)); + if (!observer) { + return NS_OK; + } + + observer->RemoveClosure(aClosure); + if (observer->HasNoClosures()) { + // Delete the callback since its list of closures is empty. + gObserverTable->Remove(observer); + } + return NS_OK; +} + +static void BoolVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((bool*)cache->cacheLocation) = + Preferences::GetBool(aPref, cache->defaultValueBool); +} + +// static +nsresult +Preferences::AddBoolVarCache(bool* aCache, + const char* aPref, + bool aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("bool", aPref, aCache); +#endif + *aCache = GetBool(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueBool = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(BoolVarChanged, aPref, data, ExactMatch); +} + +static void IntVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((int32_t*)cache->cacheLocation) = + Preferences::GetInt(aPref, cache->defaultValueInt); +} + +// static +nsresult +Preferences::AddIntVarCache(int32_t* aCache, + const char* aPref, + int32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("int", aPref, aCache); +#endif + *aCache = Preferences::GetInt(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueInt = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(IntVarChanged, aPref, data, ExactMatch); +} + +static void UintVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((uint32_t*)cache->cacheLocation) = + Preferences::GetUint(aPref, cache->defaultValueUint); +} + +// static +nsresult +Preferences::AddUintVarCache(uint32_t* aCache, + const char* aPref, + uint32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("uint", aPref, aCache); +#endif + *aCache = Preferences::GetUint(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueUint = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(UintVarChanged, aPref, data, ExactMatch); +} + +template <MemoryOrdering Order> +static void AtomicUintVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((Atomic<uint32_t, Order>*)cache->cacheLocation) = + Preferences::GetUint(aPref, cache->defaultValueUint); +} + +template <MemoryOrdering Order> +// static +nsresult +Preferences::AddAtomicUintVarCache(Atomic<uint32_t, Order>* aCache, + const char* aPref, + uint32_t aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("uint", aPref, aCache); +#endif + *aCache = Preferences::GetUint(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueUint = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(AtomicUintVarChanged<Order>, aPref, data, ExactMatch); +} + +// Since the definition of this template function is not in a header file, +// we need to explicitly specify the instantiations that are required. +// Currently only the order=Relaxed variant is needed. +template +nsresult Preferences::AddAtomicUintVarCache(Atomic<uint32_t,Relaxed>*, + const char*, uint32_t); + +static void FloatVarChanged(const char* aPref, void* aClosure) +{ + CacheData* cache = static_cast<CacheData*>(aClosure); + *((float*)cache->cacheLocation) = + Preferences::GetFloat(aPref, cache->defaultValueFloat); +} + +// static +nsresult +Preferences::AddFloatVarCache(float* aCache, + const char* aPref, + float aDefault) +{ + NS_ASSERTION(aCache, "aCache must not be NULL"); +#ifdef DEBUG + AssertNotAlreadyCached("float", aPref, aCache); +#endif + *aCache = Preferences::GetFloat(aPref, aDefault); + CacheData* data = new CacheData(); + data->cacheLocation = aCache; + data->defaultValueFloat = aDefault; + gCacheData->AppendElement(data); + return RegisterCallback(FloatVarChanged, aPref, data, ExactMatch); +} + +// static +nsresult +Preferences::GetDefaultBool(const char* aPref, bool* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetBoolPref(aPref, aResult, true); +} + +// static +nsresult +Preferences::GetDefaultInt(const char* aPref, int32_t* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return PREF_GetIntPref(aPref, aResult, true); +} + +// static +nsresult +Preferences::GetDefaultCString(const char* aPref, nsACString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true); + if (NS_SUCCEEDED(rv)) { + *aResult = result; + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultString(const char* aPref, nsAString* aResult) +{ + NS_PRECONDITION(aResult, "aResult must not be NULL"); + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsAutoCString result; + nsresult rv = PREF_CopyCharPref(aPref, getter_Copies(result), true); + if (NS_SUCCEEDED(rv)) { + CopyUTF8toUTF16(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultLocalizedCString(const char* aPref, + nsACString* aResult) +{ + nsAutoString result; + nsresult rv = GetDefaultLocalizedString(aPref, &result); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(result, *aResult); + } + return rv; +} + +// static +nsresult +Preferences::GetDefaultLocalizedString(const char* aPref, + nsAString* aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + nsCOMPtr<nsIPrefLocalizedString> prefLocalString; + nsresult rv = + sDefaultRootBranch->GetComplexValue(aPref, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(prefLocalString)); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(prefLocalString, "Succeeded but the result is NULL"); + prefLocalString->GetData(getter_Copies(*aResult)); + } + return rv; +} + +// static +nsAdoptingString +Preferences::GetDefaultString(const char* aPref) +{ + nsAdoptingString result; + GetDefaultString(aPref, &result); + return result; +} + +// static +nsAdoptingCString +Preferences::GetDefaultCString(const char* aPref) +{ + nsAdoptingCString result; + PREF_CopyCharPref(aPref, getter_Copies(result), true); + return result; +} + +// static +nsAdoptingString +Preferences::GetDefaultLocalizedString(const char* aPref) +{ + nsAdoptingString result; + GetDefaultLocalizedString(aPref, &result); + return result; +} + +// static +nsAdoptingCString +Preferences::GetDefaultLocalizedCString(const char* aPref) +{ + nsAdoptingCString result; + GetDefaultLocalizedCString(aPref, &result); + return result; +} + +// static +nsresult +Preferences::GetDefaultComplex(const char* aPref, const nsIID &aType, + void** aResult) +{ + NS_ENSURE_TRUE(InitStaticMembers(), NS_ERROR_NOT_AVAILABLE); + return sDefaultRootBranch->GetComplexValue(aPref, aType, aResult); +} + +// static +int32_t +Preferences::GetDefaultType(const char* aPref) +{ + NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID); + int32_t result; + return NS_SUCCEEDED(sDefaultRootBranch->GetPrefType(aPref, &result)) ? + result : nsIPrefBranch::PREF_INVALID; +} + +} // namespace mozilla + +#undef ENSURE_MAIN_PROCESS diff --git a/components/preferences/src/Preferences.h b/components/preferences/src/Preferences.h new file mode 100644 index 000000000..255d2a8d2 --- /dev/null +++ b/components/preferences/src/Preferences.h @@ -0,0 +1,408 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_Preferences_h +#define mozilla_Preferences_h + +#ifndef MOZILLA_INTERNAL_API +#error "This header is only usable from within libxul (MOZILLA_INTERNAL_API)." +#endif + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIPrefBranchInternal.h" +#include "nsIObserver.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsWeakReference.h" +#include "mozilla/MemoryReporting.h" + +class nsIFile; +class nsAdoptingString; +class nsAdoptingCString; + +#ifndef have_PrefChangedFunc_typedef +typedef void (*PrefChangedFunc)(const char *, void *); +#define have_PrefChangedFunc_typedef +#endif + +namespace mozilla { + +namespace dom { +class PrefSetting; +} // namespace dom + +class Preferences final : public nsIPrefService, + public nsIObserver, + public nsIPrefBranchInternal, + public nsSupportsWeakReference +{ +public: + typedef mozilla::dom::PrefSetting PrefSetting; + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIPREFSERVICE + NS_FORWARD_NSIPREFBRANCH(sRootBranch->) + NS_DECL_NSIOBSERVER + + Preferences(); + + nsresult Init(); + + /** + * Returns true if the Preferences service is available, false otherwise. + */ + static bool IsServiceAvailable(); + + /** + * Reset loaded user prefs then read them + */ + static nsresult ResetAndReadUserPrefs(); + + /** + * Returns the singleton instance which is addreffed. + */ + static Preferences* GetInstanceForService(); + + /** + * Finallizes global members. + */ + static void Shutdown(); + + /** + * Returns shared pref service instance + * NOTE: not addreffed. + */ + static nsIPrefService* GetService() + { + NS_ENSURE_TRUE(InitStaticMembers(), nullptr); + return sPreferences; + } + + /** + * Returns shared pref branch instance. + * NOTE: not addreffed. + */ + static nsIPrefBranch* GetRootBranch() + { + NS_ENSURE_TRUE(InitStaticMembers(), nullptr); + return sRootBranch; + } + + /** + * Returns shared default pref branch instance. + * NOTE: not addreffed. + */ + static nsIPrefBranch* GetDefaultRootBranch() + { + NS_ENSURE_TRUE(InitStaticMembers(), nullptr); + return sDefaultRootBranch; + } + + /** + * Gets int or bool type pref value with default value if failed to get + * the pref. + */ + static bool GetBool(const char* aPref, bool aDefault = false) + { + bool result = aDefault; + GetBool(aPref, &result); + return result; + } + + static int32_t GetInt(const char* aPref, int32_t aDefault = 0) + { + int32_t result = aDefault; + GetInt(aPref, &result); + return result; + } + + static uint32_t GetUint(const char* aPref, uint32_t aDefault = 0) + { + uint32_t result = aDefault; + GetUint(aPref, &result); + return result; + } + + static float GetFloat(const char* aPref, float aDefault = 0) + { + float result = aDefault; + GetFloat(aPref, &result); + return result; + } + + /** + * Gets char type pref value directly. If failed, the get() of result + * returns nullptr. Even if succeeded but the result was empty string, the + * get() does NOT return nullptr. So, you can check whether the method + * succeeded or not by: + * + * nsAdoptingString value = Prefereces::GetString("foo.bar"); + * if (!value) { + * // failed + * } + * + * Be aware. If you wrote as: + * + * nsAutoString value = Preferences::GetString("foo.bar"); + * if (!value.get()) { + * // the condition is always FALSE!! + * } + * + * The value.get() doesn't return nullptr. You must use nsAdoptingString + * when you need to check whether it was failure or not. + */ + static nsAdoptingCString GetCString(const char* aPref); + static nsAdoptingString GetString(const char* aPref); + static nsAdoptingCString GetLocalizedCString(const char* aPref); + static nsAdoptingString GetLocalizedString(const char* aPref); + + /** + * Gets int, float, or bool type pref value with raw return value of + * nsIPrefBranch. + * + * @param aPref A pref name. + * @param aResult Must not be nullptr. The value is never modified + * when these methods fail. + */ + static nsresult GetBool(const char* aPref, bool* aResult); + static nsresult GetInt(const char* aPref, int32_t* aResult); + static nsresult GetFloat(const char* aPref, float* aResult); + static nsresult GetUint(const char* aPref, uint32_t* aResult) + { + int32_t result; + nsresult rv = GetInt(aPref, &result); + if (NS_SUCCEEDED(rv)) { + *aResult = static_cast<uint32_t>(result); + } + return rv; + } + + /** + * Gets string type pref value with raw return value of nsIPrefBranch. + * + * @param aPref A pref name. + * @param aResult Must not be nullptr. The value is never modified + * when these methods fail. + */ + static nsresult GetCString(const char* aPref, nsACString* aResult); + static nsresult GetString(const char* aPref, nsAString* aResult); + static nsresult GetLocalizedCString(const char* aPref, nsACString* aResult); + static nsresult GetLocalizedString(const char* aPref, nsAString* aResult); + + static nsresult GetComplex(const char* aPref, const nsIID &aType, + void** aResult); + + /** + * Sets various type pref values. + */ + static nsresult SetBool(const char* aPref, bool aValue); + static nsresult SetInt(const char* aPref, int32_t aValue); + static nsresult SetUint(const char* aPref, uint32_t aValue) + { + return SetInt(aPref, static_cast<int32_t>(aValue)); + } + static nsresult SetFloat(const char* aPref, float aValue); + static nsresult SetCString(const char* aPref, const char* aValue); + static nsresult SetCString(const char* aPref, const nsACString &aValue); + static nsresult SetString(const char* aPref, const char16ptr_t aValue); + static nsresult SetString(const char* aPref, const nsAString &aValue); + + static nsresult SetComplex(const char* aPref, const nsIID &aType, + nsISupports* aValue); + + /** + * Clears user set pref. + */ + static nsresult ClearUser(const char* aPref); + + /** + * Whether the pref has a user value or not. + */ + static bool HasUserValue(const char* aPref); + + /** + * Gets the type of the pref. + */ + static int32_t GetType(const char* aPref); + + /** + * Adds/Removes the observer for the root pref branch. + * The observer is referenced strongly if AddStrongObserver is used. On the + * other hand, it is referenced weakly, if AddWeakObserver is used. + * See nsIPrefBranch.idl for details. + */ + static nsresult AddStrongObserver(nsIObserver* aObserver, const char* aPref); + static nsresult AddWeakObserver(nsIObserver* aObserver, const char* aPref); + static nsresult RemoveObserver(nsIObserver* aObserver, const char* aPref); + + /** + * Adds/Removes two or more observers for the root pref branch. + * Pass to aPrefs an array of const char* whose last item is nullptr. + */ + static nsresult AddStrongObservers(nsIObserver* aObserver, + const char** aPrefs); + static nsresult AddWeakObservers(nsIObserver* aObserver, + const char** aPrefs); + static nsresult RemoveObservers(nsIObserver* aObserver, + const char** aPrefs); + + /** + * Registers/Unregisters the callback function for the aPref. + * + * Pass ExactMatch for aMatchKind to only get callbacks for + * exact matches and not prefixes. + */ + enum MatchKind { + PrefixMatch, + ExactMatch, + }; + static nsresult RegisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure = nullptr, + MatchKind aMatchKind = PrefixMatch); + static nsresult UnregisterCallback(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure = nullptr, + MatchKind aMatchKind = PrefixMatch); + // Like RegisterCallback, but also calls the callback immediately for + // initialization. + static nsresult RegisterCallbackAndCall(PrefChangedFunc aCallback, + const char* aPref, + void* aClosure = nullptr, + MatchKind aMatchKind = PrefixMatch); + + /** + * Adds the aVariable to cache table. aVariable must be a pointer for a + * static variable. The value will be modified when the pref value is + * changed but note that even if you modified it, the value isn't assigned to + * the pref. + */ + static nsresult AddBoolVarCache(bool* aVariable, + const char* aPref, + bool aDefault = false); + static nsresult AddIntVarCache(int32_t* aVariable, + const char* aPref, + int32_t aDefault = 0); + static nsresult AddUintVarCache(uint32_t* aVariable, + const char* aPref, + uint32_t aDefault = 0); + template <MemoryOrdering Order> + static nsresult AddAtomicUintVarCache(Atomic<uint32_t, Order>* aVariable, + const char* aPref, + uint32_t aDefault = 0); + static nsresult AddFloatVarCache(float* aVariable, + const char* aPref, + float aDefault = 0.0f); + + /** + * Gets the default bool, int or uint value of the pref. + * The result is raw result of nsIPrefBranch::Get*Pref(). + * If the pref could have any value, you needed to use these methods. + * If not so, you could use below methods. + */ + static nsresult GetDefaultBool(const char* aPref, bool* aResult); + static nsresult GetDefaultInt(const char* aPref, int32_t* aResult); + static nsresult GetDefaultUint(const char* aPref, uint32_t* aResult) + { + return GetDefaultInt(aPref, reinterpret_cast<int32_t*>(aResult)); + } + + /** + * Gets the default bool, int or uint value of the pref directly. + * You can set an invalid value of the pref to aFailedResult. If these + * methods failed to get the default value, they would return the + * aFailedResult value. + */ + static bool GetDefaultBool(const char* aPref, bool aFailedResult) + { + bool result; + return NS_SUCCEEDED(GetDefaultBool(aPref, &result)) ? result : + aFailedResult; + } + static int32_t GetDefaultInt(const char* aPref, int32_t aFailedResult) + { + int32_t result; + return NS_SUCCEEDED(GetDefaultInt(aPref, &result)) ? result : aFailedResult; + } + static uint32_t GetDefaultUint(const char* aPref, uint32_t aFailedResult) + { + return static_cast<uint32_t>( + GetDefaultInt(aPref, static_cast<int32_t>(aFailedResult))); + } + + /** + * Gets the default value of the char type pref. + * If the get() of the result returned nullptr, that meant the value didn't + * have default value. + * + * See the comment at definition at GetString() and GetCString() for more + * details of the result. + */ + static nsAdoptingString GetDefaultString(const char* aPref); + static nsAdoptingCString GetDefaultCString(const char* aPref); + static nsAdoptingString GetDefaultLocalizedString(const char* aPref); + static nsAdoptingCString GetDefaultLocalizedCString(const char* aPref); + + static nsresult GetDefaultCString(const char* aPref, nsACString* aResult); + static nsresult GetDefaultString(const char* aPref, nsAString* aResult); + static nsresult GetDefaultLocalizedCString(const char* aPref, + nsACString* aResult); + static nsresult GetDefaultLocalizedString(const char* aPref, + nsAString* aResult); + + static nsresult GetDefaultComplex(const char* aPref, const nsIID &aType, + void** aResult); + + /** + * Gets the type of the pref. + */ + static int32_t GetDefaultType(const char* aPref); + + // Used to synchronise preferences between chrome and content processes. + static void GetPreferences(InfallibleTArray<PrefSetting>* aPrefs); + static void GetPreference(PrefSetting* aPref); + static void SetPreference(const PrefSetting& aPref); + + static int64_t SizeOfIncludingThisAndOtherStuff(mozilla::MallocSizeOf aMallocSizeOf); + + static void DirtyCallback(); + +protected: + virtual ~Preferences(); + + nsresult NotifyServiceObservers(const char *aSubject); + /** + * Reads the default pref file or, if that failed, try to save a new one. + * + * @return NS_OK if either action succeeded, + * or the error code related to the read attempt. + */ + nsresult UseDefaultPrefFile(); + nsresult UseUserPrefFile(); + nsresult ReadAndOwnUserPrefFile(nsIFile *aFile); + nsresult ReadAndOwnSharedUserPrefFile(nsIFile *aFile); + nsresult SavePrefFileInternal(nsIFile* aFile); + nsresult WritePrefFile(nsIFile* aFile); + nsresult MakeBackupPrefFile(nsIFile *aFile); + +private: + nsCOMPtr<nsIFile> mCurrentFile; + bool mDirty; + + static Preferences* sPreferences; + static nsIPrefBranch* sRootBranch; + static nsIPrefBranch* sDefaultRootBranch; + static bool sShutdown; + + /** + * Init static members. TRUE if it succeeded. Otherwise, FALSE. + */ + static bool InitStaticMembers(); +}; + +} // namespace mozilla + +#endif // mozilla_Preferences_h diff --git a/components/preferences/src/nsPrefBranch.cpp b/components/preferences/src/nsPrefBranch.cpp new file mode 100644 index 000000000..6107e64d8 --- /dev/null +++ b/components/preferences/src/nsPrefBranch.cpp @@ -0,0 +1,943 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/dom/ContentChild.h" +#include "nsXULAppAPI.h" + +#include "nsPrefBranch.h" +#include "nsILocalFile.h" // nsILocalFile used for backwards compatibility +#include "nsIObserverService.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIDirectoryService.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsXPIDLString.h" +#include "nsPrintfCString.h" +#include "nsIStringBundle.h" +#include "prefapi.h" +#include "PLDHashTable.h" + +#include "nsCRT.h" +#include "mozilla/Services.h" + +#include "prefapi_private_data.h" + +#include "nsIConsoleService.h" + +#ifdef DEBUG +#define ENSURE_MAIN_PROCESS(message, pref) do { \ + if (GetContentChild()) { \ + nsPrintfCString msg("ENSURE_MAIN_PROCESS failed. %s %s", message, pref); \ + NS_ERROR(msg.get()); \ + return NS_ERROR_NOT_AVAILABLE; \ + } \ +} while (0); +#else +#define ENSURE_MAIN_PROCESS(message, pref) \ + if (GetContentChild()) { \ + return NS_ERROR_NOT_AVAILABLE; \ + } +#endif + +using mozilla::dom::ContentChild; + +static ContentChild* +GetContentChild() +{ + if (XRE_IsContentProcess()) { + ContentChild* cpc = ContentChild::GetSingleton(); + if (!cpc) { + NS_RUNTIMEABORT("Content Protocol is NULL! We're going to crash!"); + } + return cpc; + } + return nullptr; +} + +/* + * Constructor/Destructor + */ + +nsPrefBranch::nsPrefBranch(const char *aPrefRoot, bool aDefaultBranch) +{ + mPrefRoot = aPrefRoot; + mPrefRootLength = mPrefRoot.Length(); + mIsDefault = aDefaultBranch; + mFreeingObserverList = false; + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + ++mRefCnt; // Our refcnt must be > 0 when we call this, or we'll get deleted! + // add weak so we don't have to clean up at shutdown + observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true); + --mRefCnt; + } +} + +nsPrefBranch::~nsPrefBranch() +{ + freeObserverList(); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) + observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); +} + + +/* + * nsISupports Implementation + */ + +NS_IMPL_ADDREF(nsPrefBranch) +NS_IMPL_RELEASE(nsPrefBranch) + +NS_INTERFACE_MAP_BEGIN(nsPrefBranch) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefBranch) + NS_INTERFACE_MAP_ENTRY(nsIPrefBranch) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIPrefBranch2, !mIsDefault) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIPrefBranchInternal, !mIsDefault) + NS_INTERFACE_MAP_ENTRY(nsIObserver) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) +NS_INTERFACE_MAP_END + + +/* + * nsIPrefBranch Implementation + */ + +NS_IMETHODIMP nsPrefBranch::GetRoot(char **aRoot) +{ + NS_ENSURE_ARG_POINTER(aRoot); + mPrefRoot.Truncate(mPrefRootLength); + *aRoot = ToNewCString(mPrefRoot); + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::GetPrefType(const char *aPrefName, int32_t *_retval) +{ + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + switch (PREF_GetPrefType(pref)) { + case PrefType::String: + *_retval = PREF_STRING; + break; + case PrefType::Int: + *_retval = PREF_INT; + break; + case PrefType::Bool: + *_retval = PREF_BOOL; + break; + case PrefType::Invalid: + default: + *_retval = PREF_INVALID; + break; + } + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::GetBoolPrefWithDefault(const char *aPrefName, + bool aDefaultValue, + uint8_t _argc, bool *_retval) +{ + nsresult rv = GetBoolPref(aPrefName, _retval); + + if (NS_FAILED(rv) && _argc == 1) { + *_retval = aDefaultValue; + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::GetBoolPref(const char *aPrefName, bool *_retval) +{ + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_GetBoolPref(pref, _retval, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::SetBoolPref(const char *aPrefName, bool aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetBoolPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_SetBoolPref(pref, aValue, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::GetFloatPrefWithDefault(const char *aPrefName, + float aDefaultValue, + uint8_t _argc, float *_retval) +{ + nsresult rv = GetFloatPref(aPrefName, _retval); + + if (NS_FAILED(rv) && _argc == 1) { + *_retval = aDefaultValue; + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::GetFloatPref(const char *aPrefName, float *_retval) +{ + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + nsAutoCString stringVal; + nsresult rv = GetCharPref(pref, getter_Copies(stringVal)); + if (NS_SUCCEEDED(rv)) { + *_retval = stringVal.ToFloat(&rv); + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::GetCharPrefWithDefault(const char *aPrefName, + const char *aDefaultValue, + uint8_t _argc, char **_retval) +{ + nsresult rv = GetCharPref(aPrefName, _retval); + + if (NS_FAILED(rv) && _argc == 1) { + NS_ENSURE_ARG(aDefaultValue); + *_retval = NS_strdup(aDefaultValue); + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::GetCharPref(const char *aPrefName, char **_retval) +{ + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_CopyCharPref(pref, _retval, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::SetCharPref(const char *aPrefName, const char *aValue) +{ + nsresult rv = CheckSanityOfStringLength(aPrefName, aValue); + if (NS_FAILED(rv)) { + return rv; + } + return SetCharPrefInternal(aPrefName, aValue); +} + +nsresult nsPrefBranch::SetCharPrefInternal(const char *aPrefName, const char *aValue) + +{ + ENSURE_MAIN_PROCESS("Cannot SetCharPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + NS_ENSURE_ARG(aValue); + const char *pref = getPrefName(aPrefName); + return PREF_SetCharPref(pref, aValue, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::GetIntPrefWithDefault(const char *aPrefName, + int32_t aDefaultValue, + uint8_t _argc, int32_t *_retval) +{ + nsresult rv = GetIntPref(aPrefName, _retval); + + if (NS_FAILED(rv) && _argc == 1) { + *_retval = aDefaultValue; + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::GetIntPref(const char *aPrefName, int32_t *_retval) +{ + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_GetIntPref(pref, _retval, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::SetIntPref(const char *aPrefName, int32_t aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetIntPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_SetIntPref(pref, aValue, mIsDefault); +} + +NS_IMETHODIMP nsPrefBranch::GetComplexValue(const char *aPrefName, const nsIID & aType, void **_retval) +{ + NS_ENSURE_ARG(aPrefName); + + nsresult rv; + nsXPIDLCString utf8String; + + // we have to do this one first because it's different than all the rest + if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) { + nsCOMPtr<nsIPrefLocalizedString> theString(do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); + if (NS_FAILED(rv)) return rv; + + const char *pref = getPrefName(aPrefName); + bool bNeedDefault = false; + + if (mIsDefault) { + bNeedDefault = true; + } else { + // if there is no user (or locked) value + if (!PREF_HasUserPref(pref) && !PREF_PrefIsLocked(pref)) { + bNeedDefault = true; + } + } + + // if we need to fetch the default value, do that instead, otherwise use the + // value we pulled in at the top of this function + if (bNeedDefault) { + nsXPIDLString utf16String; + rv = GetDefaultFromPropertiesFile(pref, getter_Copies(utf16String)); + if (NS_SUCCEEDED(rv)) { + theString->SetData(utf16String.get()); + } + } else { + rv = GetCharPref(aPrefName, getter_Copies(utf8String)); + if (NS_SUCCEEDED(rv)) { + theString->SetData(NS_ConvertUTF8toUTF16(utf8String).get()); + } + } + + if (NS_SUCCEEDED(rv)) { + theString.forget(reinterpret_cast<nsIPrefLocalizedString**>(_retval)); + } + + return rv; + } + + // if we can't get the pref, there's no point in being here + rv = GetCharPref(aPrefName, getter_Copies(utf8String)); + if (NS_FAILED(rv)) { + return rv; + } + + // also check nsILocalFile, for backwards compatibility + if (aType.Equals(NS_GET_IID(nsIFile)) || aType.Equals(NS_GET_IID(nsILocalFile))) { + if (GetContentChild()) { + NS_ERROR("cannot get nsIFile pref from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIFile> file(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) { + rv = file->SetPersistentDescriptor(utf8String); + if (NS_SUCCEEDED(rv)) { + file.forget(reinterpret_cast<nsIFile**>(_retval)); + return NS_OK; + } + } + return rv; + } + + if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) { + if (GetContentChild()) { + NS_ERROR("cannot get nsIRelativeFilePref from content process"); + return NS_ERROR_NOT_AVAILABLE; + } + + nsACString::const_iterator keyBegin, strEnd; + utf8String.BeginReading(keyBegin); + utf8String.EndReading(strEnd); + + // The pref has the format: [fromKey]a/b/c + if (*keyBegin++ != '[') + return NS_ERROR_FAILURE; + nsACString::const_iterator keyEnd(keyBegin); + if (!FindCharInReadable(']', keyEnd, strEnd)) + return NS_ERROR_FAILURE; + nsAutoCString key(Substring(keyBegin, keyEnd)); + + nsCOMPtr<nsIFile> fromFile; + nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + rv = directoryService->Get(key.get(), NS_GET_IID(nsIFile), getter_AddRefs(fromFile)); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIFile> theFile; + rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(theFile)); + if (NS_FAILED(rv)) + return rv; + rv = theFile->SetRelativeDescriptor(fromFile, Substring(++keyEnd, strEnd)); + if (NS_FAILED(rv)) + return rv; + nsCOMPtr<nsIRelativeFilePref> relativePref; + rv = NS_NewRelativeFilePref(theFile, key, getter_AddRefs(relativePref)); + if (NS_FAILED(rv)) + return rv; + + relativePref.forget(reinterpret_cast<nsIRelativeFilePref**>(_retval)); + return NS_OK; + } + + if (aType.Equals(NS_GET_IID(nsISupportsString))) { + nsCOMPtr<nsISupportsString> theString(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) { + // Debugging to see why we end up with very long strings here with + // some addons, see bug 836263. + nsAutoString wdata; + if (!AppendUTF8toUTF16(utf8String, wdata, mozilla::fallible)) { + NS_RUNTIMEABORT("bug836263"); + } + theString->SetData(wdata); + theString.forget(reinterpret_cast<nsISupportsString**>(_retval)); + } + return rv; + } + + NS_WARNING("nsPrefBranch::GetComplexValue - Unsupported interface type"); + return NS_NOINTERFACE; +} + +nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const char* aValue) { + if (!aValue) { + return NS_OK; + } + return CheckSanityOfStringLength(aPrefName, strlen(aValue)); +} + +nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue) { + return CheckSanityOfStringLength(aPrefName, aValue.Length()); +} + +nsresult nsPrefBranch::CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength) { + if (aLength > MAX_PREF_LENGTH) { + return NS_ERROR_ILLEGAL_VALUE; + } + if (aLength <= MAX_ADVISABLE_PREF_LENGTH) { + return NS_OK; + } + nsresult rv; + nsCOMPtr<nsIConsoleService> console = do_GetService("@mozilla.org/consoleservice;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + nsAutoCString message(nsPrintfCString("Warning: attempting to write %d bytes to preference %s. This is bad " + "for general performance and memory usage. Such an amount of data " + "should rather be written to an external file. This preference will " + "not be sent to any content processes.", + aLength, + getPrefName(aPrefName))); + rv = console->LogStringMessage(NS_ConvertUTF8toUTF16(message).get()); + if (NS_FAILED(rv)) { + return rv; + } + return NS_OK; +} + +/*static*/ +void nsPrefBranch::ReportToConsole(const nsAString& aMessage) +{ + nsresult rv; + nsCOMPtr<nsIConsoleService> console = do_GetService("@mozilla.org/consoleservice;1", &rv); + if (NS_FAILED(rv)) { + return; + } + nsAutoString message(aMessage); + console->LogStringMessage(message.get()); +} + +NS_IMETHODIMP nsPrefBranch::SetComplexValue(const char *aPrefName, const nsIID & aType, nsISupports *aValue) +{ + ENSURE_MAIN_PROCESS("Cannot SetComplexValue from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + + nsresult rv = NS_NOINTERFACE; + + // also check nsILocalFile, for backwards compatibility + if (aType.Equals(NS_GET_IID(nsIFile)) || aType.Equals(NS_GET_IID(nsILocalFile))) { + nsCOMPtr<nsIFile> file = do_QueryInterface(aValue); + if (!file) + return NS_NOINTERFACE; + nsAutoCString descriptorString; + + rv = file->GetPersistentDescriptor(descriptorString); + if (NS_SUCCEEDED(rv)) { + rv = SetCharPrefInternal(aPrefName, descriptorString.get()); + } + return rv; + } + + if (aType.Equals(NS_GET_IID(nsIRelativeFilePref))) { + nsCOMPtr<nsIRelativeFilePref> relFilePref = do_QueryInterface(aValue); + if (!relFilePref) + return NS_NOINTERFACE; + + nsCOMPtr<nsIFile> file; + relFilePref->GetFile(getter_AddRefs(file)); + if (!file) + return NS_NOINTERFACE; + nsAutoCString relativeToKey; + (void) relFilePref->GetRelativeToKey(relativeToKey); + + nsCOMPtr<nsIFile> relativeToFile; + nsCOMPtr<nsIProperties> directoryService(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + rv = directoryService->Get(relativeToKey.get(), NS_GET_IID(nsIFile), getter_AddRefs(relativeToFile)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString relDescriptor; + rv = file->GetRelativeDescriptor(relativeToFile, relDescriptor); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString descriptorString; + descriptorString.Append('['); + descriptorString.Append(relativeToKey); + descriptorString.Append(']'); + descriptorString.Append(relDescriptor); + return SetCharPrefInternal(aPrefName, descriptorString.get()); + } + + if (aType.Equals(NS_GET_IID(nsISupportsString))) { + nsCOMPtr<nsISupportsString> theString = do_QueryInterface(aValue); + + if (theString) { + nsString wideString; + + rv = theString->GetData(wideString); + if (NS_SUCCEEDED(rv)) { + // Check sanity of string length before any lengthy conversion + rv = CheckSanityOfStringLength(aPrefName, wideString); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetCharPrefInternal(aPrefName, NS_ConvertUTF16toUTF8(wideString).get()); + } + } + return rv; + } + + if (aType.Equals(NS_GET_IID(nsIPrefLocalizedString))) { + nsCOMPtr<nsIPrefLocalizedString> theString = do_QueryInterface(aValue); + + if (theString) { + nsXPIDLString wideString; + + rv = theString->GetData(getter_Copies(wideString)); + if (NS_SUCCEEDED(rv)) { + // Check sanity of string length before any lengthy conversion + rv = CheckSanityOfStringLength(aPrefName, wideString); + if (NS_FAILED(rv)) { + return rv; + } + rv = SetCharPrefInternal(aPrefName, NS_ConvertUTF16toUTF8(wideString).get()); + } + } + return rv; + } + + NS_WARNING("nsPrefBranch::SetComplexValue - Unsupported interface type"); + return NS_NOINTERFACE; +} + +NS_IMETHODIMP nsPrefBranch::ClearUserPref(const char *aPrefName) +{ + ENSURE_MAIN_PROCESS("Cannot ClearUserPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_ClearUserPref(pref); +} + +NS_IMETHODIMP nsPrefBranch::PrefHasUserValue(const char *aPrefName, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + *_retval = PREF_HasUserPref(pref); + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::LockPref(const char *aPrefName) +{ + ENSURE_MAIN_PROCESS("Cannot LockPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_LockPref(pref, true); +} + +NS_IMETHODIMP nsPrefBranch::PrefIsLocked(const char *aPrefName, bool *_retval) +{ + ENSURE_MAIN_PROCESS("Cannot check PrefIsLocked from content process:", aPrefName); + NS_ENSURE_ARG_POINTER(_retval); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + *_retval = PREF_PrefIsLocked(pref); + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::UnlockPref(const char *aPrefName) +{ + ENSURE_MAIN_PROCESS("Cannot UnlockPref from content process:", aPrefName); + NS_ENSURE_ARG(aPrefName); + const char *pref = getPrefName(aPrefName); + return PREF_LockPref(pref, false); +} + +NS_IMETHODIMP nsPrefBranch::ResetBranch(const char *aStartingAt) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsPrefBranch::DeleteBranch(const char *aStartingAt) +{ + ENSURE_MAIN_PROCESS("Cannot DeleteBranch from content process:", aStartingAt); + NS_ENSURE_ARG(aStartingAt); + const char *pref = getPrefName(aStartingAt); + return PREF_DeleteBranch(pref); +} + +NS_IMETHODIMP nsPrefBranch::GetChildList(const char *aStartingAt, uint32_t *aCount, char ***aChildArray) +{ + char **outArray; + int32_t numPrefs; + int32_t dwIndex; + AutoTArray<nsCString, 32> prefArray; + + NS_ENSURE_ARG(aStartingAt); + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aChildArray); + + *aChildArray = nullptr; + *aCount = 0; + + // this will contain a list of all the pref name strings + // allocate on the stack for speed + + const char* parent = getPrefName(aStartingAt); + size_t parentLen = strlen(parent); + for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PrefHashEntry*>(iter.Get()); + if (strncmp(entry->key, parent, parentLen) == 0) { + prefArray.AppendElement(entry->key); + } + } + + // now that we've built up the list, run the callback on + // all the matching elements + numPrefs = prefArray.Length(); + + if (numPrefs) { + outArray = (char **)moz_xmalloc(numPrefs * sizeof(char *)); + if (!outArray) + return NS_ERROR_OUT_OF_MEMORY; + + for (dwIndex = 0; dwIndex < numPrefs; ++dwIndex) { + // we need to lop off mPrefRoot in case the user is planning to pass this + // back to us because if they do we are going to add mPrefRoot again. + const nsCString& element = prefArray[dwIndex]; + outArray[dwIndex] = (char *)nsMemory::Clone( + element.get() + mPrefRootLength, element.Length() - mPrefRootLength + 1); + + if (!outArray[dwIndex]) { + // we ran out of memory... this is annoying + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(dwIndex, outArray); + return NS_ERROR_OUT_OF_MEMORY; + } + } + *aChildArray = outArray; + } + *aCount = numPrefs; + + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::AddObserver(const char *aDomain, nsIObserver *aObserver, bool aHoldWeak) +{ + PrefCallback *pCallback; + const char *pref; + + NS_ENSURE_ARG(aDomain); + NS_ENSURE_ARG(aObserver); + + // hold a weak reference to the observer if so requested + if (aHoldWeak) { + nsCOMPtr<nsISupportsWeakReference> weakRefFactory = do_QueryInterface(aObserver); + if (!weakRefFactory) { + // the caller didn't give us a object that supports weak reference... tell them + return NS_ERROR_INVALID_ARG; + } + + // Construct a PrefCallback with a weak reference to the observer. + pCallback = new PrefCallback(aDomain, weakRefFactory, this); + + } else { + // Construct a PrefCallback with a strong reference to the observer. + pCallback = new PrefCallback(aDomain, aObserver, this); + } + + if (mObservers.Get(pCallback)) { + NS_WARNING("Ignoring duplicate observer."); + delete pCallback; + return NS_OK; + } + + mObservers.Put(pCallback, pCallback); + + // We must pass a fully qualified preference name to the callback + // aDomain == nullptr is the only possible failure, and we trapped it with + // NS_ENSURE_ARG above. + pref = getPrefName(aDomain); + PREF_RegisterCallback(pref, NotifyObserver, pCallback); + return NS_OK; +} + +NS_IMETHODIMP nsPrefBranch::RemoveObserver(const char *aDomain, nsIObserver *aObserver) +{ + NS_ENSURE_ARG(aDomain); + NS_ENSURE_ARG(aObserver); + + nsresult rv = NS_OK; + + // If we're in the middle of a call to freeObserverList, don't process this + // RemoveObserver call -- the observer in question will be removed soon, if + // it hasn't been already. + // + // It's important that we don't touch mObservers in any way -- even a Get() + // which returns null might cause the hashtable to resize itself, which will + // break the iteration in freeObserverList. + if (mFreeingObserverList) + return NS_OK; + + // Remove the relevant PrefCallback from mObservers and get an owning + // pointer to it. Unregister the callback first, and then let the owning + // pointer go out of scope and destroy the callback. + PrefCallback key(aDomain, aObserver, this); + nsAutoPtr<PrefCallback> pCallback; + mObservers.RemoveAndForget(&key, pCallback); + if (pCallback) { + // aDomain == nullptr is the only possible failure, trapped above + const char *pref = getPrefName(aDomain); + rv = PREF_UnregisterCallback(pref, NotifyObserver, pCallback); + } + + return rv; +} + +NS_IMETHODIMP nsPrefBranch::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + // watch for xpcom shutdown and free our observers to eliminate any cyclic references + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + freeObserverList(); + } + return NS_OK; +} + +/* static */ +void nsPrefBranch::NotifyObserver(const char *newpref, void *data) +{ + PrefCallback *pCallback = (PrefCallback *)data; + + nsCOMPtr<nsIObserver> observer = pCallback->GetObserver(); + if (!observer) { + // The observer has expired. Let's remove this callback. + pCallback->GetPrefBranch()->RemoveExpiredCallback(pCallback); + return; + } + + // remove any root this string may contain so as to not confuse the observer + // by passing them something other than what they passed us as a topic + uint32_t len = pCallback->GetPrefBranch()->GetRootLength(); + nsAutoCString suffix(newpref + len); + + observer->Observe(static_cast<nsIPrefBranch *>(pCallback->GetPrefBranch()), + NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, + NS_ConvertASCIItoUTF16(suffix).get()); +} + +size_t +nsPrefBranch::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) +{ + size_t n = aMallocSizeOf(this); + n += mPrefRoot.SizeOfExcludingThisIfUnshared(aMallocSizeOf); + n += mObservers.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +void nsPrefBranch::freeObserverList(void) +{ + // We need to prevent anyone from modifying mObservers while we're iterating + // over it. In particular, some clients will call RemoveObserver() when + // they're removed and destructed via the iterator; we set + // mFreeingObserverList to keep those calls from touching mObservers. + mFreeingObserverList = true; + for (auto iter = mObservers.Iter(); !iter.Done(); iter.Next()) { + nsAutoPtr<PrefCallback>& callback = iter.Data(); + nsPrefBranch *prefBranch = callback->GetPrefBranch(); + const char *pref = prefBranch->getPrefName(callback->GetDomain().get()); + PREF_UnregisterCallback(pref, nsPrefBranch::NotifyObserver, callback); + iter.Remove(); + } + mFreeingObserverList = false; +} + +void +nsPrefBranch::RemoveExpiredCallback(PrefCallback *aCallback) +{ + NS_PRECONDITION(aCallback->IsExpired(), "Callback should be expired."); + mObservers.Remove(aCallback); +} + +nsresult nsPrefBranch::GetDefaultFromPropertiesFile(const char *aPrefName, char16_t **return_buf) +{ + nsresult rv; + + // the default value contains a URL to a .properties file + + nsXPIDLCString propertyFileURL; + rv = PREF_CopyCharPref(aPrefName, getter_Copies(propertyFileURL), true); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + if (!bundleService) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle(propertyFileURL, + getter_AddRefs(bundle)); + if (NS_FAILED(rv)) + return rv; + + // string names are in unicode + nsAutoString stringId; + stringId.AssignASCII(aPrefName); + + return bundle->GetStringFromName(stringId.get(), return_buf); +} + +const char *nsPrefBranch::getPrefName(const char *aPrefName) +{ + NS_ASSERTION(aPrefName, "null pref name!"); + + // for speed, avoid strcpy if we can: + if (mPrefRoot.IsEmpty()) + return aPrefName; + + // isn't there a better way to do this? this is really kind of gross. + mPrefRoot.Truncate(mPrefRootLength); + mPrefRoot.Append(aPrefName); + return mPrefRoot.get(); +} + +//---------------------------------------------------------------------------- +// nsPrefLocalizedString +//---------------------------------------------------------------------------- + +nsPrefLocalizedString::nsPrefLocalizedString() +{ +} + +nsPrefLocalizedString::~nsPrefLocalizedString() +{ +} + + +/* + * nsISupports Implementation + */ + +NS_IMPL_ADDREF(nsPrefLocalizedString) +NS_IMPL_RELEASE(nsPrefLocalizedString) + +NS_INTERFACE_MAP_BEGIN(nsPrefLocalizedString) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefLocalizedString) + NS_INTERFACE_MAP_ENTRY(nsIPrefLocalizedString) + NS_INTERFACE_MAP_ENTRY(nsISupportsString) +NS_INTERFACE_MAP_END + +nsresult nsPrefLocalizedString::Init() +{ + nsresult rv; + mUnicodeString = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + + return rv; +} + +NS_IMETHODIMP +nsPrefLocalizedString::GetData(char16_t **_retval) +{ + nsAutoString data; + + nsresult rv = GetData(data); + if (NS_FAILED(rv)) + return rv; + + *_retval = ToNewUnicode(data); + if (!*_retval) + return NS_ERROR_OUT_OF_MEMORY; + + return NS_OK; +} + +NS_IMETHODIMP +nsPrefLocalizedString::SetData(const char16_t *aData) +{ + if (!aData) + return SetData(EmptyString()); + return SetData(nsDependentString(aData)); +} + +NS_IMETHODIMP +nsPrefLocalizedString::SetDataWithLength(uint32_t aLength, + const char16_t *aData) +{ + if (!aData) + return SetData(EmptyString()); + return SetData(Substring(aData, aLength)); +} + +//---------------------------------------------------------------------------- +// nsRelativeFilePref +//---------------------------------------------------------------------------- + +NS_IMPL_ISUPPORTS(nsRelativeFilePref, nsIRelativeFilePref) + +nsRelativeFilePref::nsRelativeFilePref() +{ +} + +nsRelativeFilePref::~nsRelativeFilePref() +{ +} + +NS_IMETHODIMP nsRelativeFilePref::GetFile(nsIFile **aFile) +{ + NS_ENSURE_ARG_POINTER(aFile); + *aFile = mFile; + NS_IF_ADDREF(*aFile); + return NS_OK; +} + +NS_IMETHODIMP nsRelativeFilePref::SetFile(nsIFile *aFile) +{ + mFile = aFile; + return NS_OK; +} + +NS_IMETHODIMP nsRelativeFilePref::GetRelativeToKey(nsACString& aRelativeToKey) +{ + aRelativeToKey.Assign(mRelativeToKey); + return NS_OK; +} + +NS_IMETHODIMP nsRelativeFilePref::SetRelativeToKey(const nsACString& aRelativeToKey) +{ + mRelativeToKey.Assign(aRelativeToKey); + return NS_OK; +} + +#undef ENSURE_MAIN_PROCESS diff --git a/components/preferences/src/nsPrefBranch.h b/components/preferences/src/nsPrefBranch.h new file mode 100644 index 000000000..37cf5c2c4 --- /dev/null +++ b/components/preferences/src/nsPrefBranch.h @@ -0,0 +1,269 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsPrefBranch_h +#define nsPrefBranch_h + +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIPrefBranch.h" +#include "nsIPrefBranchInternal.h" +#include "nsIPrefLocalizedString.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIRelativeFilePref.h" +#include "nsIFile.h" +#include "nsString.h" +#include "nsTArray.h" +#include "nsWeakReference.h" +#include "nsClassHashtable.h" +#include "nsCRT.h" +#include "nsISupportsImpl.h" +#include "mozilla/HashFunctions.h" +#include "mozilla/MemoryReporting.h" + +namespace mozilla { +class PreferenceServiceReporter; +} // namespace mozilla + +class nsPrefBranch; + +class PrefCallback : public PLDHashEntryHdr { + friend class mozilla::PreferenceServiceReporter; + + public: + typedef PrefCallback* KeyType; + typedef const PrefCallback* KeyTypePointer; + + static const PrefCallback* KeyToPointer(PrefCallback *aKey) + { + return aKey; + } + + static PLDHashNumber HashKey(const PrefCallback *aKey) + { + uint32_t hash = mozilla::HashString(aKey->mDomain); + return mozilla::AddToHash(hash, aKey->mCanonical); + } + + + public: + // Create a PrefCallback with a strong reference to its observer. + PrefCallback(const char *aDomain, nsIObserver *aObserver, + nsPrefBranch *aBranch) + : mDomain(aDomain), + mBranch(aBranch), + mWeakRef(nullptr), + mStrongRef(aObserver) + { + MOZ_COUNT_CTOR(PrefCallback); + nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver); + mCanonical = canonical; + } + + // Create a PrefCallback with a weak reference to its observer. + PrefCallback(const char *aDomain, + nsISupportsWeakReference *aObserver, + nsPrefBranch *aBranch) + : mDomain(aDomain), + mBranch(aBranch), + mWeakRef(do_GetWeakReference(aObserver)), + mStrongRef(nullptr) + { + MOZ_COUNT_CTOR(PrefCallback); + nsCOMPtr<nsISupports> canonical = do_QueryInterface(aObserver); + mCanonical = canonical; + } + + // Copy constructor needs to be explicit or the linker complains. + explicit PrefCallback(const PrefCallback *&aCopy) + : mDomain(aCopy->mDomain), + mBranch(aCopy->mBranch), + mWeakRef(aCopy->mWeakRef), + mStrongRef(aCopy->mStrongRef), + mCanonical(aCopy->mCanonical) + { + MOZ_COUNT_CTOR(PrefCallback); + } + + ~PrefCallback() + { + MOZ_COUNT_DTOR(PrefCallback); + } + + bool KeyEquals(const PrefCallback *aKey) const + { + // We want to be able to look up a weakly-referencing PrefCallback after + // its observer has died so we can remove it from the table. Once the + // callback's observer dies, its canonical pointer is stale -- in + // particular, we may have allocated a new observer in the same spot in + // memory! So we can't just compare canonical pointers to determine + // whether aKey refers to the same observer as this. + // + // Our workaround is based on the way we use this hashtable: When we ask + // the hashtable to remove a PrefCallback whose weak reference has + // expired, we use as the key for removal the same object as was inserted + // into the hashtable. Thus we can say that if one of the keys' weak + // references has expired, the two keys are equal iff they're the same + // object. + + if (IsExpired() || aKey->IsExpired()) + return this == aKey; + + if (mCanonical != aKey->mCanonical) + return false; + + return mDomain.Equals(aKey->mDomain); + } + + PrefCallback *GetKey() const + { + return const_cast<PrefCallback*>(this); + } + + // Get a reference to the callback's observer, or null if the observer was + // weakly referenced and has been destroyed. + already_AddRefed<nsIObserver> GetObserver() const + { + if (!IsWeak()) { + nsCOMPtr<nsIObserver> copy = mStrongRef; + return copy.forget(); + } + + nsCOMPtr<nsIObserver> observer = do_QueryReferent(mWeakRef); + return observer.forget(); + } + + const nsCString& GetDomain() const + { + return mDomain; + } + + nsPrefBranch* GetPrefBranch() const + { + return mBranch; + } + + // Has this callback's weak reference died? + bool IsExpired() const + { + if (!IsWeak()) + return false; + + nsCOMPtr<nsIObserver> observer(do_QueryReferent(mWeakRef)); + return !observer; + } + + enum { ALLOW_MEMMOVE = true }; + + private: + nsCString mDomain; + nsPrefBranch *mBranch; + + // Exactly one of mWeakRef and mStrongRef should be non-null. + nsWeakPtr mWeakRef; + nsCOMPtr<nsIObserver> mStrongRef; + + // We need a canonical nsISupports pointer, per bug 578392. + nsISupports *mCanonical; + + bool IsWeak() const + { + return !!mWeakRef; + } +}; + +class nsPrefBranch final : public nsIPrefBranchInternal, + public nsIObserver, + public nsSupportsWeakReference +{ + friend class mozilla::PreferenceServiceReporter; +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPREFBRANCH + NS_DECL_NSIPREFBRANCH2 + NS_DECL_NSIOBSERVER + + nsPrefBranch(const char *aPrefRoot, bool aDefaultBranch); + + int32_t GetRootLength() { return mPrefRootLength; } + + nsresult RemoveObserverFromMap(const char *aDomain, nsISupports *aObserver); + + static void NotifyObserver(const char *newpref, void *data); + + size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf); + + static void ReportToConsole(const nsAString& aMessage); + +protected: + virtual ~nsPrefBranch(); + + nsPrefBranch() /* disallow use of this constructer */ + : mPrefRootLength(0) + , mIsDefault(false) + , mFreeingObserverList(false) + {} + + nsresult GetDefaultFromPropertiesFile(const char *aPrefName, char16_t **return_buf); + // As SetCharPref, but without any check on the length of |aValue| + nsresult SetCharPrefInternal(const char *aPrefName, const char *aValue); + // Reject strings that are more than 1Mb, warn if strings are more than 16kb + nsresult CheckSanityOfStringLength(const char* aPrefName, const nsAString& aValue); + nsresult CheckSanityOfStringLength(const char* aPrefName, const char* aValue); + nsresult CheckSanityOfStringLength(const char* aPrefName, const uint32_t aLength); + void RemoveExpiredCallback(PrefCallback *aCallback); + const char *getPrefName(const char *aPrefName); + void freeObserverList(void); + +private: + int32_t mPrefRootLength; + nsCString mPrefRoot; + bool mIsDefault; + + bool mFreeingObserverList; + nsClassHashtable<PrefCallback, PrefCallback> mObservers; +}; + + +class nsPrefLocalizedString final : public nsIPrefLocalizedString, + public nsISupportsString +{ +public: + nsPrefLocalizedString(); + + NS_DECL_ISUPPORTS + NS_FORWARD_NSISUPPORTSSTRING(mUnicodeString->) + NS_FORWARD_NSISUPPORTSPRIMITIVE(mUnicodeString->) + + nsresult Init(); + +private: + virtual ~nsPrefLocalizedString(); + + NS_IMETHOD GetData(char16_t**) override; + NS_IMETHOD SetData(const char16_t* aData) override; + NS_IMETHOD SetDataWithLength(uint32_t aLength, const char16_t *aData) override; + + nsCOMPtr<nsISupportsString> mUnicodeString; +}; + + +class nsRelativeFilePref : public nsIRelativeFilePref +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRELATIVEFILEPREF + + nsRelativeFilePref(); + +private: + virtual ~nsRelativeFilePref(); + + nsCOMPtr<nsIFile> mFile; + nsCString mRelativeToKey; +}; + +#endif diff --git a/components/preferences/src/nsPrefsFactory.cpp b/components/preferences/src/nsPrefsFactory.cpp new file mode 100644 index 000000000..35a9885b9 --- /dev/null +++ b/components/preferences/src/nsPrefsFactory.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/ModuleUtils.h" +#include "mozilla/Preferences.h" +#include "nsPrefBranch.h" +#include "prefapi.h" + +using namespace mozilla; + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(Preferences, + Preferences::GetInstanceForService) +NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsPrefLocalizedString, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsRelativeFilePref) + +static NS_DEFINE_CID(kPrefServiceCID, NS_PREFSERVICE_CID); +static NS_DEFINE_CID(kPrefLocalizedStringCID, NS_PREFLOCALIZEDSTRING_CID); +static NS_DEFINE_CID(kRelativeFilePrefCID, NS_RELATIVEFILEPREF_CID); + +static mozilla::Module::CIDEntry kPrefCIDs[] = { + { &kPrefServiceCID, true, nullptr, PreferencesConstructor }, + { &kPrefLocalizedStringCID, false, nullptr, nsPrefLocalizedStringConstructor }, + { &kRelativeFilePrefCID, false, nullptr, nsRelativeFilePrefConstructor }, + { nullptr } +}; + +static mozilla::Module::ContractIDEntry kPrefContracts[] = { + { NS_PREFSERVICE_CONTRACTID, &kPrefServiceCID }, + { NS_PREFLOCALIZEDSTRING_CONTRACTID, &kPrefLocalizedStringCID }, + { NS_RELATIVEFILEPREF_CONTRACTID, &kRelativeFilePrefCID }, + // compatibility for extension that uses old service + { "@mozilla.org/preferences;1", &kPrefServiceCID }, + { nullptr } +}; + +static void +UnloadPrefsModule() +{ + Preferences::Shutdown(); +} + +static const mozilla::Module kPrefModule = { + mozilla::Module::kVersion, + kPrefCIDs, + kPrefContracts, + nullptr, + nullptr, + nullptr, + UnloadPrefsModule +}; + +NSMODULE_DEFN(nsPrefModule) = &kPrefModule; diff --git a/components/preferences/src/prefapi.cpp b/components/preferences/src/prefapi.cpp new file mode 100644 index 000000000..8aaf4077b --- /dev/null +++ b/components/preferences/src/prefapi.cpp @@ -0,0 +1,1005 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 <string> +#include <vector> + +#include "base/basictypes.h" + +#include "prefapi.h" +#include "prefapi_private_data.h" +#include "prefread.h" +#include "MainThreadUtils.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" + +#define PL_ARENA_CONST_ALIGN_MASK 3 +#include "plarena.h" + +#ifdef _WIN32 + #include "windows.h" +#endif /* _WIN32 */ + +#include "plstr.h" +#include "PLDHashTable.h" +#include "plbase64.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/dom/PContent.h" +#include "nsQuickSort.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "prlink.h" + +using namespace mozilla; + +static void +clearPrefEntry(PLDHashTable *table, PLDHashEntryHdr *entry) +{ + PrefHashEntry *pref = static_cast<PrefHashEntry *>(entry); + if (pref->prefFlags.IsTypeString()) + { + if (pref->defaultPref.stringVal) + PL_strfree(pref->defaultPref.stringVal); + if (pref->userPref.stringVal) + PL_strfree(pref->userPref.stringVal); + } + // don't need to free this as it's allocated in memory owned by + // gPrefNameArena + pref->key = nullptr; + memset(entry, 0, table->EntrySize()); +} + +static bool +matchPrefEntry(const PLDHashEntryHdr* entry, const void* key) +{ + const PrefHashEntry *prefEntry = + static_cast<const PrefHashEntry*>(entry); + + if (prefEntry->key == key) return true; + + if (!prefEntry->key || !key) return false; + + const char *otherKey = reinterpret_cast<const char*>(key); + return (strcmp(prefEntry->key, otherKey) == 0); +} + +PLDHashTable* gHashTable; +static PLArenaPool gPrefNameArena; + +static struct CallbackNode* gCallbacks = nullptr; +static bool gIsAnyPrefLocked = false; +// These are only used during the call to pref_DoCallback +static bool gCallbacksInProgress = false; +static bool gShouldCleanupDeadNodes = false; + + +static PLDHashTableOps pref_HashTableOps = { + PLDHashTable::HashStringKey, + matchPrefEntry, + PLDHashTable::MoveEntryStub, + clearPrefEntry, + nullptr, +}; + +// PR_ALIGN_OF_WORD is only defined on some platforms. ALIGN_OF_WORD has +// already been defined to PR_ALIGN_OF_WORD everywhere +#ifndef PR_ALIGN_OF_WORD +#define PR_ALIGN_OF_WORD PR_ALIGN_OF_POINTER +#endif + +// making PrefName arena 8k for nice allocation +#define PREFNAME_ARENA_SIZE 8192 + +#define WORD_ALIGN_MASK (PR_ALIGN_OF_WORD - 1) + +// sanity checking +#if (PR_ALIGN_OF_WORD & WORD_ALIGN_MASK) != 0 +#error "PR_ALIGN_OF_WORD must be a power of 2!" +#endif + +// equivalent to strdup() - does no error checking, +// we're assuming we're only called with a valid pointer +static char *ArenaStrDup(const char* str, PLArenaPool* aArena) +{ + void* mem; + uint32_t len = strlen(str); + PL_ARENA_ALLOCATE(mem, aArena, len+1); + if (mem) + memcpy(mem, str, len+1); + return static_cast<char*>(mem); +} + +static PrefsDirtyFunc gDirtyCallback = nullptr; + +inline void MakeDirtyCallback() +{ + // Right now the callback function is always set, so we don't need + // to complicate the code to cover the scenario where we set the callback + // after we've already tried to make it dirty. If this assert triggers + // we will add that code. + MOZ_ASSERT(gDirtyCallback); + if (gDirtyCallback) { + gDirtyCallback(); + } +} + +void PREF_SetDirtyCallback(PrefsDirtyFunc aFunc) +{ + gDirtyCallback = aFunc; +} + +/*---------------------------------------------------------------------------*/ + +static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type); +/* -- Privates */ +struct CallbackNode { + char* domain; + // If someone attempts to remove the node from the callback list while + // pref_DoCallback is running, |func| is set to nullptr. Such nodes will + // be removed at the end of pref_DoCallback. + PrefChangedFunc func; + void* data; + struct CallbackNode* next; +}; + +/* -- Prototypes */ +static nsresult pref_DoCallback(const char* changed_pref); + +enum { + kPrefSetDefault = 1, + kPrefForceSet = 2, + kPrefStickyDefault = 4, +}; +static nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags); + +#define PREF_HASHTABLE_INITIAL_LENGTH 1024 + +void PREF_Init() +{ + if (!gHashTable) { + gHashTable = new PLDHashTable(&pref_HashTableOps, + sizeof(PrefHashEntry), + PREF_HASHTABLE_INITIAL_LENGTH); + + PL_INIT_ARENA_POOL(&gPrefNameArena, "PrefNameArena", + PREFNAME_ARENA_SIZE); + } +} + +/* Frees the callback list. */ +void PREF_Cleanup() +{ + NS_ASSERTION(!gCallbacksInProgress, + "PREF_Cleanup was called while gCallbacksInProgress is true!"); + struct CallbackNode* node = gCallbacks; + struct CallbackNode* next_node; + + while (node) + { + next_node = node->next; + PL_strfree(node->domain); + free(node); + node = next_node; + } + gCallbacks = nullptr; + + PREF_CleanupPrefs(); +} + +/* Frees up all the objects except the callback list. */ +void PREF_CleanupPrefs() +{ + if (gHashTable) { + delete gHashTable; + gHashTable = nullptr; + PL_FinishArenaPool(&gPrefNameArena); + } +} + +// note that this appends to aResult, and does not assign! +static void str_escape(const char * original, nsAFlatCString& aResult) +{ + /* JavaScript does not allow quotes, slashes, or line terminators inside + * strings so we must escape them. ECMAScript defines four line + * terminators, but we're only worrying about \r and \n here. We currently + * feed our pref script to the JS interpreter as Latin-1 so we won't + * encounter \u2028 (line separator) or \u2029 (paragraph separator). + * + * WARNING: There are hints that we may be moving to storing prefs + * as utf8. If we ever feed them to the JS compiler as UTF8 then + * we'll have to worry about the multibyte sequences that would be + * interpreted as \u2028 and \u2029 + */ + const char *p; + + if (original == nullptr) + return; + + /* Paranoid worst case all slashes will free quickly */ + for (p=original; *p; ++p) + { + switch (*p) + { + case '\n': + aResult.AppendLiteral("\\n"); + break; + + case '\r': + aResult.AppendLiteral("\\r"); + break; + + case '\\': + aResult.AppendLiteral("\\\\"); + break; + + case '\"': + aResult.AppendLiteral("\\\""); + break; + + default: + aResult.Append(*p); + break; + } + } +} + +/* +** External calls +*/ +nsresult +PREF_SetCharPref(const char *pref_name, const char *value, bool set_default) +{ + if ((uint32_t)strlen(value) > MAX_PREF_LENGTH) { + return NS_ERROR_ILLEGAL_VALUE; + } + + PrefValue pref; + pref.stringVal = (char*)value; + + return pref_HashPref(pref_name, pref, PrefType::String, set_default ? kPrefSetDefault : 0); +} + +nsresult +PREF_SetIntPref(const char *pref_name, int32_t value, bool set_default) +{ + PrefValue pref; + pref.intVal = value; + + return pref_HashPref(pref_name, pref, PrefType::Int, set_default ? kPrefSetDefault : 0); +} + +nsresult +PREF_SetBoolPref(const char *pref_name, bool value, bool set_default) +{ + PrefValue pref; + pref.boolVal = value; + + return pref_HashPref(pref_name, pref, PrefType::Bool, set_default ? kPrefSetDefault : 0); +} + +enum WhichValue { DEFAULT_VALUE, USER_VALUE }; +static nsresult +SetPrefValue(const char* aPrefName, const dom::PrefValue& aValue, + WhichValue aWhich) +{ + bool setDefault = (aWhich == DEFAULT_VALUE); + switch (aValue.type()) { + case dom::PrefValue::TnsCString: + return PREF_SetCharPref(aPrefName, aValue.get_nsCString().get(), + setDefault); + case dom::PrefValue::Tint32_t: + return PREF_SetIntPref(aPrefName, aValue.get_int32_t(), + setDefault); + case dom::PrefValue::Tbool: + return PREF_SetBoolPref(aPrefName, aValue.get_bool(), + setDefault); + default: + MOZ_CRASH(); + } +} + +nsresult +pref_SetPref(const dom::PrefSetting& aPref) +{ + const char* prefName = aPref.name().get(); + const dom::MaybePrefValue& defaultValue = aPref.defaultValue(); + const dom::MaybePrefValue& userValue = aPref.userValue(); + + nsresult rv; + if (defaultValue.type() == dom::MaybePrefValue::TPrefValue) { + rv = SetPrefValue(prefName, defaultValue.get_PrefValue(), DEFAULT_VALUE); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (userValue.type() == dom::MaybePrefValue::TPrefValue) { + rv = SetPrefValue(prefName, userValue.get_PrefValue(), USER_VALUE); + } else { + rv = PREF_ClearUserPref(prefName); + } + + // NB: we should never try to clear a default value, that doesn't + // make sense + + return rv; +} + +UniquePtr<char*[]> +pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount) +{ + // This function allocates the entries in the savedPrefs array it returns. + // It is the callers responsibility to go through the array and free + // all of them. The aPrefCount entries will be non-null. Any end padding + // is an implementation detail and may change. + MOZ_ASSERT(aPrefCount); + auto savedPrefs = MakeUnique<char*[]>(aTable->EntryCount()); + + // This is not necessary, but leaving it in for now + memset(savedPrefs.get(), 0, aTable->EntryCount() * sizeof(char*)); + + int32_t j = 0; + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + auto pref = static_cast<PrefHashEntry*>(iter.Get()); + + nsAutoCString prefValue; + nsAutoCString prefPrefix; + prefPrefix.AssignLiteral("user_pref(\""); + + // where we're getting our pref from + PrefValue* sourcePref; + + if (pref->prefFlags.HasUserValue() && + (pref_ValueChanged(pref->defaultPref, + pref->userPref, + pref->prefFlags.GetPrefType()) || + !(pref->prefFlags.HasDefault()) || + pref->prefFlags.HasStickyDefault())) { + sourcePref = &pref->userPref; + } else { + // do not save default prefs that haven't changed + continue; + } + + // strings are in quotes! + if (pref->prefFlags.IsTypeString()) { + prefValue = '\"'; + str_escape(sourcePref->stringVal, prefValue); + prefValue += '\"'; + + } else if (pref->prefFlags.IsTypeInt()) { + prefValue.AppendInt(sourcePref->intVal); + + } else if (pref->prefFlags.IsTypeBool()) { + prefValue = (sourcePref->boolVal) ? "true" : "false"; + } + + nsAutoCString prefName; + str_escape(pref->key, prefName); + + savedPrefs[j++] = ToNewCString(prefPrefix + + prefName + + NS_LITERAL_CSTRING("\", ") + + prefValue + + NS_LITERAL_CSTRING(");")); + } + *aPrefCount = j; + + return savedPrefs; +} + +bool +pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry) +{ + if (aHashEntry->prefFlags.GetPrefType() != PrefType::String) { + return true; + } + + char* stringVal; + if (aHashEntry->prefFlags.HasDefault()) { + stringVal = aHashEntry->defaultPref.stringVal; + if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) { + return false; + } + } + + if (aHashEntry->prefFlags.HasUserValue()) { + stringVal = aHashEntry->userPref.stringVal; + if (strlen(stringVal) > MAX_ADVISABLE_PREF_LENGTH) { + return false; + } + } + + return true; +} + +static void +GetPrefValueFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref, + WhichValue aWhich) +{ + PrefValue* value; + dom::PrefValue* settingValue; + if (aWhich == USER_VALUE) { + value = &aHashEntry->userPref; + aPref->userValue() = dom::PrefValue(); + settingValue = &aPref->userValue().get_PrefValue(); + } else { + value = &aHashEntry->defaultPref; + aPref->defaultValue() = dom::PrefValue(); + settingValue = &aPref->defaultValue().get_PrefValue(); + } + + switch (aHashEntry->prefFlags.GetPrefType()) { + case PrefType::String: + *settingValue = nsDependentCString(value->stringVal); + return; + case PrefType::Int: + *settingValue = value->intVal; + return; + case PrefType::Bool: + *settingValue = !!value->boolVal; + return; + default: + MOZ_CRASH(); + } +} + +void +pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, dom::PrefSetting* aPref) +{ + aPref->name() = aHashEntry->key; + if (aHashEntry->prefFlags.HasDefault()) { + GetPrefValueFromEntry(aHashEntry, aPref, DEFAULT_VALUE); + } else { + aPref->defaultValue() = null_t(); + } + if (aHashEntry->prefFlags.HasUserValue()) { + GetPrefValueFromEntry(aHashEntry, aPref, USER_VALUE); + } else { + aPref->userValue() = null_t(); + } + + MOZ_ASSERT(aPref->defaultValue().type() == dom::MaybePrefValue::Tnull_t || + aPref->userValue().type() == dom::MaybePrefValue::Tnull_t || + (aPref->defaultValue().get_PrefValue().type() == + aPref->userValue().get_PrefValue().type())); +} + + +int +pref_CompareStrings(const void *v1, const void *v2, void *unused) +{ + char *s1 = *(char**) v1; + char *s2 = *(char**) v2; + + if (!s1) + { + if (!s2) + return 0; + else + return -1; + } + else if (!s2) + return 1; + else + return strcmp(s1, s2); +} + +bool PREF_HasUserPref(const char *pref_name) +{ + if (!gHashTable) + return false; + + PrefHashEntry *pref = pref_HashTableLookup(pref_name); + return pref && pref->prefFlags.HasUserValue(); +} + +nsresult +PREF_CopyCharPref(const char *pref_name, char ** return_buffer, bool get_default) +{ + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + char* stringVal; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + + if (pref && (pref->prefFlags.IsTypeString())) { + if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) { + stringVal = pref->defaultPref.stringVal; + } else { + stringVal = pref->userPref.stringVal; + } + + if (stringVal) { + *return_buffer = NS_strdup(stringVal); + rv = NS_OK; + } + } + return rv; +} + +nsresult PREF_GetIntPref(const char *pref_name,int32_t * return_int, bool get_default) +{ + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && (pref->prefFlags.IsTypeInt())) { + if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) { + int32_t tempInt = pref->defaultPref.intVal; + /* check to see if we even had a default */ + if (!pref->prefFlags.HasDefault()) { + return NS_ERROR_UNEXPECTED; + } + *return_int = tempInt; + } else { + *return_int = pref->userPref.intVal; + } + rv = NS_OK; + } + return rv; +} + +nsresult PREF_GetBoolPref(const char *pref_name, bool * return_value, bool get_default) +{ + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_ERROR_UNEXPECTED; + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + //NS_ASSERTION(pref, pref_name); + if (pref && (pref->prefFlags.IsTypeBool())) { + if (get_default || pref->prefFlags.IsLocked() || !pref->prefFlags.HasUserValue()) { + bool tempBool = pref->defaultPref.boolVal; + /* check to see if we even had a default */ + if (pref->prefFlags.HasDefault()) { + *return_value = tempBool; + rv = NS_OK; + } + } else { + *return_value = pref->userPref.boolVal; + rv = NS_OK; + } + } + return rv; +} + +nsresult +PREF_DeleteBranch(const char *branch_name) +{ + MOZ_ASSERT(NS_IsMainThread()); + + int len = (int)strlen(branch_name); + + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + /* The following check insures that if the branch name already has a "." + * at the end, we don't end up with a "..". This fixes an incompatibility + * between nsIPref, which needs the period added, and nsIPrefBranch which + * does not. When nsIPref goes away this function should be fixed to + * never add the period at all. + */ + nsAutoCString branch_dot(branch_name); + if ((len > 1) && branch_name[len - 1] != '.') + branch_dot += '.'; + + /* Delete a branch. Used for deleting mime types */ + const char *to_delete = branch_dot.get(); + MOZ_ASSERT(to_delete); + len = strlen(to_delete); + for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast<PrefHashEntry*>(iter.Get()); + + /* note if we're deleting "ldap" then we want to delete "ldap.xxx" + and "ldap" (if such a leaf node exists) but not "ldap_1.xxx" */ + if (PL_strncmp(entry->key, to_delete, (uint32_t) len) == 0 || + (len-1 == (int)strlen(entry->key) && + PL_strncmp(entry->key, to_delete, (uint32_t)(len-1)) == 0)) { + iter.Remove(); + } + } + + MakeDirtyCallback(); + return NS_OK; +} + + +nsresult +PREF_ClearUserPref(const char *pref_name) +{ + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && pref->prefFlags.HasUserValue()) { + pref->prefFlags.SetHasUserValue(false); + + if (!pref->prefFlags.HasDefault()) { + gHashTable->RemoveEntry(pref); + } + + pref_DoCallback(pref_name); + MakeDirtyCallback(); + } + return NS_OK; +} + +nsresult +PREF_ClearAllUserPrefs() +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + std::vector<std::string> prefStrings; + for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) { + auto pref = static_cast<PrefHashEntry*>(iter.Get()); + + if (pref->prefFlags.HasUserValue()) { + prefStrings.push_back(std::string(pref->key)); + + pref->prefFlags.SetHasUserValue(false); + if (!pref->prefFlags.HasDefault()) { + iter.Remove(); + } + } + } + + for (std::string& prefString : prefStrings) { + pref_DoCallback(prefString.c_str()); + } + + MakeDirtyCallback(); + return NS_OK; +} + +nsresult PREF_LockPref(const char *key, bool lockit) +{ + if (!gHashTable) + return NS_ERROR_NOT_INITIALIZED; + + PrefHashEntry* pref = pref_HashTableLookup(key); + if (!pref) + return NS_ERROR_UNEXPECTED; + + if (lockit) { + if (!pref->prefFlags.IsLocked()) { + pref->prefFlags.SetLocked(true); + gIsAnyPrefLocked = true; + pref_DoCallback(key); + } + } else { + if (pref->prefFlags.IsLocked()) { + pref->prefFlags.SetLocked(false); + pref_DoCallback(key); + } + } + return NS_OK; +} + +/* + * Hash table functions + */ +static bool pref_ValueChanged(PrefValue oldValue, PrefValue newValue, PrefType type) +{ + bool changed = true; + switch(type) { + case PrefType::String: + if (oldValue.stringVal && newValue.stringVal) { + changed = (strcmp(oldValue.stringVal, newValue.stringVal) != 0); + } + break; + case PrefType::Int: + changed = oldValue.intVal != newValue.intVal; + break; + case PrefType::Bool: + changed = oldValue.boolVal != newValue.boolVal; + break; + case PrefType::Invalid: + default: + changed = false; + break; + } + return changed; +} + +/* + * Overwrite the type and value of an existing preference. Caller must + * ensure that they are not changing the type of a preference that has + * a default value. + */ +static PrefTypeFlags pref_SetValue(PrefValue* existingValue, PrefTypeFlags flags, + PrefValue newValue, PrefType newType) +{ + if (flags.IsTypeString() && existingValue->stringVal) { + PL_strfree(existingValue->stringVal); + } + flags.SetPrefType(newType); + if (flags.IsTypeString()) { + MOZ_ASSERT(newValue.stringVal); + existingValue->stringVal = newValue.stringVal ? PL_strdup(newValue.stringVal) : nullptr; + } + else { + *existingValue = newValue; + } + return flags; +} + +PrefHashEntry* pref_HashTableLookup(const char *key) +{ + MOZ_ASSERT(NS_IsMainThread()); + + return static_cast<PrefHashEntry*>(gHashTable->Search(key)); +} + +nsresult pref_HashPref(const char *key, PrefValue value, PrefType type, uint32_t flags) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (!gHashTable) + return NS_ERROR_OUT_OF_MEMORY; + + auto pref = static_cast<PrefHashEntry*>(gHashTable->Add(key, fallible)); + if (!pref) + return NS_ERROR_OUT_OF_MEMORY; + + // new entry, better initialize + if (!pref->key) { + + // initialize the pref entry + pref->prefFlags.Reset().SetPrefType(type); + pref->key = ArenaStrDup(key, &gPrefNameArena); + memset(&pref->defaultPref, 0, sizeof(pref->defaultPref)); + memset(&pref->userPref, 0, sizeof(pref->userPref)); + } else if (pref->prefFlags.HasDefault() && !pref->prefFlags.IsPrefType(type)) { + NS_WARNING(nsPrintfCString("Trying to overwrite value of default pref %s with the wrong type!", key).get()); + return NS_ERROR_UNEXPECTED; + } + + bool valueChanged = false; + if (flags & kPrefSetDefault) { + if (!pref->prefFlags.IsLocked()) { + /* ?? change of semantics? */ + if (pref_ValueChanged(pref->defaultPref, value, type) || + !pref->prefFlags.HasDefault()) { + pref->prefFlags = pref_SetValue(&pref->defaultPref, pref->prefFlags, value, type).SetHasDefault(true); + if (flags & kPrefStickyDefault) { + pref->prefFlags.SetHasStickyDefault(true); + } + if (!pref->prefFlags.HasUserValue()) { + valueChanged = true; + } + } + // What if we change the default to be the same as the user value? + // Should we clear the user value? + } + } else { + /* If new value is same as the default value and it's not a "sticky" + pref, then un-set the user value. + Otherwise, set the user value only if it has changed */ + if ((pref->prefFlags.HasDefault()) && + !(pref->prefFlags.HasStickyDefault()) && + !pref_ValueChanged(pref->defaultPref, value, type) && + !(flags & kPrefForceSet)) { + if (pref->prefFlags.HasUserValue()) { + /* XXX should we free a user-set string value if there is one? */ + pref->prefFlags.SetHasUserValue(false); + if (!pref->prefFlags.IsLocked()) { + MakeDirtyCallback(); + valueChanged = true; + } + } + } else if (!pref->prefFlags.HasUserValue() || + !pref->prefFlags.IsPrefType(type) || + pref_ValueChanged(pref->userPref, value, type) ) { + pref->prefFlags = pref_SetValue(&pref->userPref, pref->prefFlags, value, type).SetHasUserValue(true); + if (!pref->prefFlags.IsLocked()) { + MakeDirtyCallback(); + valueChanged = true; + } + } + } + + if (valueChanged) { + return pref_DoCallback(key); + } + return NS_OK; +} + +size_t +pref_SizeOfPrivateData(MallocSizeOf aMallocSizeOf) +{ + size_t n = PL_SizeOfArenaPoolExcludingPool(&gPrefNameArena, aMallocSizeOf); + for (struct CallbackNode* node = gCallbacks; node; node = node->next) { + n += aMallocSizeOf(node); + n += aMallocSizeOf(node->domain); + } + return n; +} + +PrefType +PREF_GetPrefType(const char *pref_name) +{ + if (gHashTable) { + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref) { + return pref->prefFlags.GetPrefType(); + } + } + return PrefType::Invalid; +} + +/* -- */ + +bool +PREF_PrefIsLocked(const char *pref_name) +{ + bool result = false; + if (gIsAnyPrefLocked && gHashTable) { + PrefHashEntry* pref = pref_HashTableLookup(pref_name); + if (pref && pref->prefFlags.IsLocked()) { + result = true; + } + } + + return result; +} + +/* Adds a node to the beginning of the callback list. */ +void +PREF_RegisterCallback(const char *pref_node, + PrefChangedFunc callback, + void * instance_data) +{ + NS_PRECONDITION(pref_node, "pref_node must not be nullptr"); + NS_PRECONDITION(callback, "callback must not be nullptr"); + + struct CallbackNode* node = (struct CallbackNode*) malloc(sizeof(struct CallbackNode)); + if (node) + { + node->domain = PL_strdup(pref_node); + node->func = callback; + node->data = instance_data; + node->next = gCallbacks; + gCallbacks = node; + } + return; +} + +/* Removes |node| from gCallbacks list. + Returns the node after the deleted one. */ +struct CallbackNode* +pref_RemoveCallbackNode(struct CallbackNode* node, + struct CallbackNode* prev_node) +{ + NS_PRECONDITION(!prev_node || prev_node->next == node, "invalid params"); + NS_PRECONDITION(prev_node || gCallbacks == node, "invalid params"); + + NS_ASSERTION(!gCallbacksInProgress, + "modifying the callback list while gCallbacksInProgress is true"); + + struct CallbackNode* next_node = node->next; + if (prev_node) + prev_node->next = next_node; + else + gCallbacks = next_node; + PL_strfree(node->domain); + free(node); + return next_node; +} + +/* Deletes a node from the callback list or marks it for deletion. */ +nsresult +PREF_UnregisterCallback(const char *pref_node, + PrefChangedFunc callback, + void * instance_data) +{ + nsresult rv = NS_ERROR_FAILURE; + struct CallbackNode* node = gCallbacks; + struct CallbackNode* prev_node = nullptr; + + while (node != nullptr) + { + if ( node->func == callback && + node->data == instance_data && + strcmp(node->domain, pref_node) == 0) + { + if (gCallbacksInProgress) + { + // postpone the node removal until after + // gCallbacks enumeration is finished. + node->func = nullptr; + gShouldCleanupDeadNodes = true; + prev_node = node; + node = node->next; + } + else + { + node = pref_RemoveCallbackNode(node, prev_node); + } + rv = NS_OK; + } + else + { + prev_node = node; + node = node->next; + } + } + return rv; +} + +static nsresult pref_DoCallback(const char* changed_pref) +{ + nsresult rv = NS_OK; + struct CallbackNode* node; + + bool reentered = gCallbacksInProgress; + gCallbacksInProgress = true; + // Nodes must not be deleted while gCallbacksInProgress is true. + // Nodes that need to be deleted are marked for deletion by nulling + // out the |func| pointer. We release them at the end of this function + // if we haven't reentered. + + for (node = gCallbacks; node != nullptr; node = node->next) + { + if ( node->func && + PL_strncmp(changed_pref, + node->domain, + strlen(node->domain)) == 0 ) + { + (*node->func) (changed_pref, node->data); + } + } + + gCallbacksInProgress = reentered; + + if (gShouldCleanupDeadNodes && !gCallbacksInProgress) + { + struct CallbackNode* prev_node = nullptr; + node = gCallbacks; + + while (node != nullptr) + { + if (!node->func) + { + node = pref_RemoveCallbackNode(node, prev_node); + } + else + { + prev_node = node; + node = node->next; + } + } + gShouldCleanupDeadNodes = false; + } + + return rv; +} + +void PREF_ReaderCallback(void *closure, + const char *pref, + PrefValue value, + PrefType type, + bool isDefault, + bool isStickyDefault) + +{ + uint32_t flags = 0; + if (isDefault) { + flags |= kPrefSetDefault; + if (isStickyDefault) { + flags |= kPrefStickyDefault; + } + } else { + flags |= kPrefForceSet; + } + pref_HashPref(pref, value, type, flags); +} diff --git a/components/preferences/src/prefapi.h b/components/preferences/src/prefapi.h new file mode 100644 index 000000000..9ef014ada --- /dev/null +++ b/components/preferences/src/prefapi.h @@ -0,0 +1,258 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* +// <pre> +*/ +#ifndef PREFAPI_H +#define PREFAPI_H + +#include "nscore.h" +#include "PLDHashTable.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// 1 MB should be enough for everyone. +static const uint32_t MAX_PREF_LENGTH = 1 * 1024 * 1024; +// Actually, 4kb should be enough for everyone. +static const uint32_t MAX_ADVISABLE_PREF_LENGTH = 4 * 1024; + +typedef union +{ + char* stringVal; + int32_t intVal; + bool boolVal; +} PrefValue; + +/* +// <font color=blue> +// The Init function initializes the preference context and creates +// the preference hashtable. +// </font> +*/ +void PREF_Init(); + +/* +// Cleanup should be called at program exit to free the +// list of registered callbacks. +*/ +void PREF_Cleanup(); +void PREF_CleanupPrefs(); + +/* +// <font color=blue> +// Preference flags, including the native type of the preference. Changing any of these +// values will require modifying the code inside of PrefTypeFlags class. +// </font> +*/ + +enum class PrefType { + Invalid = 0, + String = 1, + Int = 2, + Bool = 3, +}; + +// Keep the type of the preference, as well as the flags guiding its behaviour. +class PrefTypeFlags +{ +public: + PrefTypeFlags() : mValue(AsInt(PrefType::Invalid)) {} + explicit PrefTypeFlags(PrefType aType) : mValue(AsInt(aType)) {} + PrefTypeFlags& Reset() { mValue = AsInt(PrefType::Invalid); return *this; } + + bool IsTypeValid() const { return !IsPrefType(PrefType::Invalid); } + bool IsTypeString() const { return IsPrefType(PrefType::String); } + bool IsTypeInt() const { return IsPrefType(PrefType::Int); } + bool IsTypeBool() const { return IsPrefType(PrefType::Bool); } + bool IsPrefType(PrefType type) const { return GetPrefType() == type; } + + PrefTypeFlags& SetPrefType(PrefType aType) { + mValue = mValue - AsInt(GetPrefType()) + AsInt(aType); + return *this; + } + PrefType GetPrefType() const { + return (PrefType)(mValue & (AsInt(PrefType::String) | + AsInt(PrefType::Int) | + AsInt(PrefType::Bool))); + } + + bool HasDefault() const { return mValue & PREF_FLAG_HAS_DEFAULT; } + PrefTypeFlags& SetHasDefault(bool aSetOrUnset) { return SetFlag(PREF_FLAG_HAS_DEFAULT, aSetOrUnset); } + + bool HasStickyDefault() const { return mValue & PREF_FLAG_STICKY_DEFAULT; } + PrefTypeFlags& SetHasStickyDefault(bool aSetOrUnset) { return SetFlag(PREF_FLAG_STICKY_DEFAULT, aSetOrUnset); } + + bool IsLocked() const { return mValue & PREF_FLAG_LOCKED; } + PrefTypeFlags& SetLocked(bool aSetOrUnset) { return SetFlag(PREF_FLAG_LOCKED, aSetOrUnset); } + + bool HasUserValue() const { return mValue & PREF_FLAG_USERSET; } + PrefTypeFlags& SetHasUserValue(bool aSetOrUnset) { return SetFlag(PREF_FLAG_USERSET, aSetOrUnset); } + +private: + static uint16_t AsInt(PrefType aType) { return (uint16_t)aType; } + + PrefTypeFlags& SetFlag(uint16_t aFlag, bool aSetOrUnset) { + mValue = aSetOrUnset ? mValue | aFlag : mValue & ~aFlag; + return *this; + } + + // Pack both the value of type (PrefType) and flags into the same int. This is why + // the flag enum starts at 4, as PrefType occupies the bottom two bits. + enum { + PREF_FLAG_LOCKED = 4, + PREF_FLAG_USERSET = 8, + PREF_FLAG_CONFIG = 16, + PREF_FLAG_REMOTE = 32, + PREF_FLAG_LILOCAL = 64, + PREF_FLAG_HAS_DEFAULT = 128, + PREF_FLAG_STICKY_DEFAULT = 256, + }; + uint16_t mValue; +}; + +struct PrefHashEntry : PLDHashEntryHdr +{ + PrefTypeFlags prefFlags; // This field goes first to minimize struct size on 64-bit. + const char *key; + PrefValue defaultPref; + PrefValue userPref; +}; + +/* +// <font color=blue> +// Set the various types of preferences. These functions take a dotted +// notation of the preference name (e.g. "browser.startup.homepage"). +// Note that this will cause the preference to be saved to the file if +// it is different from the default. In other words, these are used +// to set the _user_ preferences. +// +// If set_default is set to true however, it sets the default value. +// This will only affect the program behavior if the user does not have a value +// saved over it for the particular preference. In addition, these will never +// be saved out to disk. +// +// Each set returns PREF_VALUECHANGED if the user value changed +// (triggering a callback), or PREF_NOERROR if the value was unchanged. +// </font> +*/ +nsresult PREF_SetCharPref(const char *pref,const char* value, bool set_default = false); +nsresult PREF_SetIntPref(const char *pref,int32_t value, bool set_default = false); +nsresult PREF_SetBoolPref(const char *pref,bool value, bool set_default = false); + +bool PREF_HasUserPref(const char* pref_name); + +/* +// <font color=blue> +// Get the various types of preferences. These functions take a dotted +// notation of the preference name (e.g. "browser.startup.homepage") +// +// They also take a pointer to fill in with the return value and return an +// error value. At the moment, this is simply an int but it may +// be converted to an enum once the global error strategy is worked out. +// +// They will perform conversion if the type doesn't match what was requested. +// (if it is reasonably possible) +// </font> +*/ +nsresult PREF_GetIntPref(const char *pref, + int32_t * return_int, bool get_default); +nsresult PREF_GetBoolPref(const char *pref, bool * return_val, bool get_default); +/* +// <font color=blue> +// These functions are similar to the above "Get" version with the significant +// difference that the preference module will alloc the memory (e.g. XP_STRDUP) and +// the caller will need to be responsible for freeing it... +// </font> +*/ +nsresult PREF_CopyCharPref(const char *pref, char ** return_buf, bool get_default); +/* +// <font color=blue> +// bool function that returns whether or not the preference is locked and therefore +// cannot be changed. +// </font> +*/ +bool PREF_PrefIsLocked(const char *pref_name); + +/* +// <font color=blue> +// Function that sets whether or not the preference is locked and therefore +// cannot be changed. +// </font> +*/ +nsresult PREF_LockPref(const char *key, bool lockIt); + +PrefType PREF_GetPrefType(const char *pref_name); + +/* + * Delete a branch of the tree + */ +nsresult PREF_DeleteBranch(const char *branch_name); + +/* + * Clears the given pref (reverts it to its default value) + */ +nsresult PREF_ClearUserPref(const char *pref_name); + +/* + * Clears all user prefs + */ +nsresult PREF_ClearAllUserPrefs(); + + +/* +// <font color=blue> +// The callback function will get passed the pref_node which triggered the call +// and the void * instance_data which was passed to the register callback function. +// Return a non-zero result (nsresult) to pass an error up to the caller. +// </font> +*/ +/* Temporarily conditionally compile PrefChangedFunc typedef. +** During migration from old libpref to nsIPref we need it in +** both header files. Eventually prefapi.h will become a private +** file. The two types need to be in sync for now. Certain +** compilers were having problems with multiple definitions. +*/ +#ifndef have_PrefChangedFunc_typedef +typedef void (*PrefChangedFunc) (const char *, void *); +#define have_PrefChangedFunc_typedef +#endif + +/* +// <font color=blue> +// Register a callback. This takes a node in the preference tree and will +// call the callback function if anything below that node is modified. +// Unregister returns PREF_NOERROR if a callback was found that +// matched all the parameters; otherwise it returns PREF_ERROR. +// </font> +*/ +void PREF_RegisterCallback(const char* domain, + PrefChangedFunc callback, void* instance_data ); +nsresult PREF_UnregisterCallback(const char* domain, + PrefChangedFunc callback, void* instance_data ); + +/* + * Used by nsPrefService as the callback function of the 'pref' parser + */ +void PREF_ReaderCallback( void *closure, + const char *pref, + PrefValue value, + PrefType type, + bool isDefault, + bool isStickyDefault); + + +/* + * Callback whenever we change a preference + */ +typedef void (*PrefsDirtyFunc) (); +void PREF_SetDirtyCallback(PrefsDirtyFunc); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/components/preferences/src/prefapi_private_data.h b/components/preferences/src/prefapi_private_data.h new file mode 100644 index 000000000..f1fa68fdc --- /dev/null +++ b/components/preferences/src/prefapi_private_data.h @@ -0,0 +1,40 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* Data shared between prefapi.c and nsPref.cpp */ + +#ifndef prefapi_private_data_h +#define prefapi_private_data_h + +#include "mozilla/MemoryReporting.h" +#include "mozilla/UniquePtr.h" + +extern PLDHashTable* gHashTable; + +namespace mozilla { +namespace dom { +class PrefSetting; +} // namespace dom +} // namespace mozilla + +mozilla::UniquePtr<char*[]> +pref_savePrefs(PLDHashTable* aTable, uint32_t* aPrefCount); + +nsresult +pref_SetPref(const mozilla::dom::PrefSetting& aPref); + +int pref_CompareStrings(const void *v1, const void *v2, void* unused); +PrefHashEntry* pref_HashTableLookup(const char *key); + +bool +pref_EntryHasAdvisablySizedValues(PrefHashEntry* aHashEntry); + +void pref_GetPrefFromEntry(PrefHashEntry *aHashEntry, + mozilla::dom::PrefSetting* aPref); + +size_t +pref_SizeOfPrivateData(mozilla::MallocSizeOf aMallocSizeOf); + +#endif diff --git a/components/preferences/src/prefread.cpp b/components/preferences/src/prefread.cpp new file mode 100644 index 000000000..605dcaac6 --- /dev/null +++ b/components/preferences/src/prefread.cpp @@ -0,0 +1,657 @@ +/* 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 <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "prefread.h" +#include "nsString.h" +#include "nsUTF8Utils.h" + +#ifdef TEST_PREFREAD +#include <stdio.h> +#define NS_WARNING(_s) printf(">>> " _s "!\n") +#define NS_NOTREACHED(_s) NS_WARNING(_s) +#else +#include "nsDebug.h" // for NS_WARNING +#endif + +/* pref parser states */ +enum { + PREF_PARSE_INIT, + PREF_PARSE_MATCH_STRING, + PREF_PARSE_UNTIL_NAME, + PREF_PARSE_QUOTED_STRING, + PREF_PARSE_UNTIL_COMMA, + PREF_PARSE_UNTIL_VALUE, + PREF_PARSE_INT_VALUE, + PREF_PARSE_COMMENT_MAYBE_START, + PREF_PARSE_COMMENT_BLOCK, + PREF_PARSE_COMMENT_BLOCK_MAYBE_END, + PREF_PARSE_ESC_SEQUENCE, + PREF_PARSE_HEX_ESCAPE, + PREF_PARSE_UTF16_LOW_SURROGATE, + PREF_PARSE_UNTIL_OPEN_PAREN, + PREF_PARSE_UNTIL_CLOSE_PAREN, + PREF_PARSE_UNTIL_SEMICOLON, + PREF_PARSE_UNTIL_EOL +}; + +#define UTF16_ESC_NUM_DIGITS 4 +#define HEX_ESC_NUM_DIGITS 2 +#define BITS_PER_HEX_DIGIT 4 + +static const char kUserPref[] = "user_pref"; +static const char kPref[] = "pref"; +static const char kPrefSticky[] = "sticky_pref"; +static const char kTrue[] = "true"; +static const char kFalse[] = "false"; + +/** + * pref_GrowBuf + * + * this function will increase the size of the buffer owned + * by the given pref parse state. We currently use a simple + * doubling algorithm, but the only hard requirement is that + * it increase the buffer by at least the size of the ps->esctmp + * buffer used for escape processing (currently 6 bytes). + * + * this buffer is used to store partial pref lines. it is + * freed when the parse state is destroyed. + * + * @param ps + * parse state instance + * + * this function updates all pointers that reference an + * address within lb since realloc may relocate the buffer. + * + * @return false if insufficient memory. + */ +static bool +pref_GrowBuf(PrefParseState *ps) +{ + int bufLen, curPos, valPos; + + bufLen = ps->lbend - ps->lb; + curPos = ps->lbcur - ps->lb; + valPos = ps->vb - ps->lb; + + if (bufLen == 0) + bufLen = 128; /* default buffer size */ + else + bufLen <<= 1; /* double buffer size */ + +#ifdef TEST_PREFREAD + fprintf(stderr, ">>> realloc(%d)\n", bufLen); +#endif + + ps->lb = (char*) realloc(ps->lb, bufLen); + if (!ps->lb) + return false; + + ps->lbcur = ps->lb + curPos; + ps->lbend = ps->lb + bufLen; + ps->vb = ps->lb + valPos; + + return true; +} + +/** + * Report an error or a warning. If not specified, just dump to stderr. + */ +static void +pref_ReportParseProblem(PrefParseState& ps, const char* aMessage, int aLine, bool aError) +{ + if (ps.reporter) { + ps.reporter(aMessage, aLine, aError); + } else { + printf_stderr("**** Preference parsing %s (line %d) = %s **\n", + (aError ? "error" : "warning"), aLine, aMessage); + } +} + +/** + * pref_DoCallback + * + * this function is called when a complete pref name-value pair has + * been extracted from the input data. + * + * @param ps + * parse state instance + * + * @return false to indicate a fatal error. + */ +static bool +pref_DoCallback(PrefParseState *ps) +{ + PrefValue value; + + switch (ps->vtype) { + case PrefType::String: + value.stringVal = ps->vb; + break; + case PrefType::Int: + if ((ps->vb[0] == '-' || ps->vb[0] == '+') && ps->vb[1] == '\0') { + pref_ReportParseProblem(*ps, "invalid integer value", 0, true); + NS_WARNING("malformed integer value"); + return false; + } + value.intVal = atoi(ps->vb); + break; + case PrefType::Bool: + value.boolVal = (ps->vb == kTrue); + break; + default: + break; + } + (*ps->reader)(ps->closure, ps->lb, value, ps->vtype, ps->fdefault, + ps->fstickydefault); + return true; +} + +void +PREF_InitParseState(PrefParseState *ps, PrefReader reader, + PrefParseErrorReporter reporter, void *closure) +{ + memset(ps, 0, sizeof(*ps)); + ps->reader = reader; + ps->closure = closure; + ps->reporter = reporter; +} + +void +PREF_FinalizeParseState(PrefParseState *ps) +{ + if (ps->lb) + free(ps->lb); +} + +/** + * Pseudo-BNF + * ---------- + * function = LJUNK function-name JUNK function-args + * function-name = "user_pref" | "pref" | "sticky_pref" + * function-args = "(" JUNK pref-name JUNK "," JUNK pref-value JUNK ")" JUNK ";" + * pref-name = quoted-string + * pref-value = quoted-string | "true" | "false" | integer-value + * JUNK = *(WS | comment-block | comment-line) + * LJUNK = *(WS | comment-block | comment-line | bcomment-line) + * WS = SP | HT | LF | VT | FF | CR + * SP = <US-ASCII SP, space (32)> + * HT = <US-ASCII HT, horizontal-tab (9)> + * LF = <US-ASCII LF, linefeed (10)> + * VT = <US-ASCII HT, vertical-tab (11)> + * FF = <US-ASCII FF, form-feed (12)> + * CR = <US-ASCII CR, carriage return (13)> + * comment-block = <C/C++ style comment block> + * comment-line = <C++ style comment line> + * bcomment-line = <bourne-shell style comment line> + */ +bool +PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen) +{ + const char *end; + char c; + char udigit; + int state; + + // The line number is currently only used for the error/warning reporting. + int lineNum = 0; + + state = ps->state; + for (end = buf + bufLen; buf != end; ++buf) { + c = *buf; + if (c == '\r' || c == '\n' || c == 0x1A) { + lineNum ++; + } + + switch (state) { + /* initial state */ + case PREF_PARSE_INIT: + if (ps->lbcur != ps->lb) { /* reset state */ + ps->lbcur = ps->lb; + ps->vb = nullptr; + ps->vtype = PrefType::Invalid; + ps->fdefault = false; + ps->fstickydefault = false; + } + switch (c) { + case '/': /* begin comment block or line? */ + state = PREF_PARSE_COMMENT_MAYBE_START; + break; + case '#': /* accept shell style comments */ + state = PREF_PARSE_UNTIL_EOL; + break; + case 'u': /* indicating user_pref */ + case 's': /* indicating sticky_pref */ + case 'p': /* indicating pref */ + if (c == 'u') { + ps->smatch = kUserPref; + } else if (c == 's') { + ps->smatch = kPrefSticky; + } else { + ps->smatch = kPref; + } + ps->sindex = 1; + ps->nextstate = PREF_PARSE_UNTIL_OPEN_PAREN; + state = PREF_PARSE_MATCH_STRING; + break; + /* else skip char */ + } + break; + + /* string matching */ + case PREF_PARSE_MATCH_STRING: + if (c == ps->smatch[ps->sindex++]) { + /* if we've matched all characters, then move to next state. */ + if (ps->smatch[ps->sindex] == '\0') { + state = ps->nextstate; + ps->nextstate = PREF_PARSE_INIT; /* reset next state */ + } + /* else wait for next char */ + } + else { + pref_ReportParseProblem(*ps, "non-matching string", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + + /* quoted string parsing */ + case PREF_PARSE_QUOTED_STRING: + /* we assume that the initial quote has already been consumed */ + if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps)) + return false; /* out of memory */ + if (c == '\\') + state = PREF_PARSE_ESC_SEQUENCE; + else if (c == ps->quotechar) { + *ps->lbcur++ = '\0'; + state = ps->nextstate; + ps->nextstate = PREF_PARSE_INIT; /* reset next state */ + } + else + *ps->lbcur++ = c; + break; + + /* name parsing */ + case PREF_PARSE_UNTIL_NAME: + if (c == '\"' || c == '\'') { + ps->fdefault = (ps->smatch == kPref || ps->smatch == kPrefSticky); + ps->fstickydefault = (ps->smatch == kPrefSticky); + ps->quotechar = c; + ps->nextstate = PREF_PARSE_UNTIL_COMMA; /* return here when done */ + state = PREF_PARSE_QUOTED_STRING; + } + else if (c == '/') { /* allow embedded comment */ + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need space, comment or quote", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + + /* parse until we find a comma separating name and value */ + case PREF_PARSE_UNTIL_COMMA: + if (c == ',') { + ps->vb = ps->lbcur; + state = PREF_PARSE_UNTIL_VALUE; + } + else if (c == '/') { /* allow embedded comment */ + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need space, comment or comma", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + + /* value parsing */ + case PREF_PARSE_UNTIL_VALUE: + /* the pref value type is unknown. so, we scan for the first + * character of the value, and determine the type from that. */ + if (c == '\"' || c == '\'') { + ps->vtype = PrefType::String; + ps->quotechar = c; + ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN; + state = PREF_PARSE_QUOTED_STRING; + } + else if (c == 't' || c == 'f') { + ps->vb = (char *) (c == 't' ? kTrue : kFalse); + ps->vtype = PrefType::Bool; + ps->smatch = ps->vb; + ps->sindex = 1; + ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN; + state = PREF_PARSE_MATCH_STRING; + } + else if (isdigit(c) || (c == '-') || (c == '+')) { + ps->vtype = PrefType::Int; + /* write c to line buffer... */ + if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps)) + return false; /* out of memory */ + *ps->lbcur++ = c; + state = PREF_PARSE_INT_VALUE; + } + else if (c == '/') { /* allow embedded comment */ + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need value, comment or space", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + case PREF_PARSE_INT_VALUE: + /* grow line buffer if necessary... */ + if (ps->lbcur == ps->lbend && !pref_GrowBuf(ps)) + return false; /* out of memory */ + if (isdigit(c)) + *ps->lbcur++ = c; + else { + *ps->lbcur++ = '\0'; /* stomp null terminator; we are done. */ + if (c == ')') + state = PREF_PARSE_UNTIL_SEMICOLON; + else if (c == '/') { /* allow embedded comment */ + ps->nextstate = PREF_PARSE_UNTIL_CLOSE_PAREN; + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (isspace(c)) + state = PREF_PARSE_UNTIL_CLOSE_PAREN; + else { + pref_ReportParseProblem(*ps, "while parsing integer", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + } + break; + + /* comment parsing */ + case PREF_PARSE_COMMENT_MAYBE_START: + switch (c) { + case '*': /* comment block */ + state = PREF_PARSE_COMMENT_BLOCK; + break; + case '/': /* comment line */ + state = PREF_PARSE_UNTIL_EOL; + break; + default: + /* pref file is malformed */ + pref_ReportParseProblem(*ps, "while parsing comment", lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + case PREF_PARSE_COMMENT_BLOCK: + if (c == '*') + state = PREF_PARSE_COMMENT_BLOCK_MAYBE_END; + break; + case PREF_PARSE_COMMENT_BLOCK_MAYBE_END: + switch (c) { + case '/': + state = ps->nextstate; + ps->nextstate = PREF_PARSE_INIT; + break; + case '*': /* stay in this state */ + break; + default: + state = PREF_PARSE_COMMENT_BLOCK; + } + break; + + /* string escape sequence parsing */ + case PREF_PARSE_ESC_SEQUENCE: + /* not necessary to resize buffer here since we should be writing + * only one character and the resize check would have been done + * for us in the previous state */ + switch (c) { + case '\"': + case '\'': + case '\\': + break; + case 'r': + c = '\r'; + break; + case 'n': + c = '\n'; + break; + case 'x': /* hex escape -- always interpreted as Latin-1 */ + case 'u': /* UTF16 escape */ + ps->esctmp[0] = c; + ps->esclen = 1; + ps->utf16[0] = ps->utf16[1] = 0; + ps->sindex = (c == 'x' ) ? + HEX_ESC_NUM_DIGITS : + UTF16_ESC_NUM_DIGITS; + state = PREF_PARSE_HEX_ESCAPE; + continue; + default: + pref_ReportParseProblem(*ps, "preserving unexpected JS escape sequence", + lineNum, false); + NS_WARNING("preserving unexpected JS escape sequence"); + /* Invalid escape sequence so we do have to write more than + * one character. Grow line buffer if necessary... */ + if ((ps->lbcur+1) == ps->lbend && !pref_GrowBuf(ps)) + return false; /* out of memory */ + *ps->lbcur++ = '\\'; /* preserve the escape sequence */ + break; + } + *ps->lbcur++ = c; + state = PREF_PARSE_QUOTED_STRING; + break; + + /* parsing a hex (\xHH) or utf16 escape (\uHHHH) */ + case PREF_PARSE_HEX_ESCAPE: + if ( c >= '0' && c <= '9' ) + udigit = (c - '0'); + else if ( c >= 'A' && c <= 'F' ) + udigit = (c - 'A') + 10; + else if ( c >= 'a' && c <= 'f' ) + udigit = (c - 'a') + 10; + else { + /* bad escape sequence found, write out broken escape as-is */ + pref_ReportParseProblem(*ps, "preserving invalid or incomplete hex escape", + lineNum, false); + NS_WARNING("preserving invalid or incomplete hex escape"); + *ps->lbcur++ = '\\'; /* original escape slash */ + if ((ps->lbcur + ps->esclen) >= ps->lbend && !pref_GrowBuf(ps)) + return false; + for (int i = 0; i < ps->esclen; ++i) + *ps->lbcur++ = ps->esctmp[i]; + + /* push the non-hex character back for re-parsing. */ + /* (++buf at the top of the loop keeps this safe) */ + --buf; + state = PREF_PARSE_QUOTED_STRING; + continue; + } + + /* have a digit */ + ps->esctmp[ps->esclen++] = c; /* preserve it */ + ps->utf16[1] <<= BITS_PER_HEX_DIGIT; + ps->utf16[1] |= udigit; + ps->sindex--; + if (ps->sindex == 0) { + /* have the full escape. Convert to UTF8 */ + int utf16len = 0; + if (ps->utf16[0]) { + /* already have a high surrogate, this is a two char seq */ + utf16len = 2; + } + else if (0xD800 == (0xFC00 & ps->utf16[1])) { + /* a high surrogate, can't convert until we have the low */ + ps->utf16[0] = ps->utf16[1]; + ps->utf16[1] = 0; + state = PREF_PARSE_UTF16_LOW_SURROGATE; + break; + } + else { + /* a single utf16 character */ + ps->utf16[0] = ps->utf16[1]; + utf16len = 1; + } + + /* actual conversion */ + /* make sure there's room, 6 bytes is max utf8 len (in */ + /* theory; 4 bytes covers the actual utf16 range) */ + if (ps->lbcur+6 >= ps->lbend && !pref_GrowBuf(ps)) + return false; + + ConvertUTF16toUTF8 converter(ps->lbcur); + converter.write(ps->utf16, utf16len); + ps->lbcur += converter.Size(); + state = PREF_PARSE_QUOTED_STRING; + } + break; + + /* looking for beginning of utf16 low surrogate */ + case PREF_PARSE_UTF16_LOW_SURROGATE: + if (ps->sindex == 0 && c == '\\') { + ++ps->sindex; + } + else if (ps->sindex == 1 && c == 'u') { + /* escape sequence is correct, now parse hex */ + ps->sindex = UTF16_ESC_NUM_DIGITS; + ps->esctmp[0] = 'u'; + ps->esclen = 1; + state = PREF_PARSE_HEX_ESCAPE; + } + else { + /* didn't find expected low surrogate. Ignore high surrogate + * (it would just get converted to nothing anyway) and start + * over with this character */ + --buf; + if (ps->sindex == 1) + state = PREF_PARSE_ESC_SEQUENCE; + else + state = PREF_PARSE_QUOTED_STRING; + continue; + } + break; + + /* function open and close parsing */ + case PREF_PARSE_UNTIL_OPEN_PAREN: + /* tolerate only whitespace and embedded comments */ + if (c == '(') + state = PREF_PARSE_UNTIL_NAME; + else if (c == '/') { + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need space, comment or open parentheses", + lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + case PREF_PARSE_UNTIL_CLOSE_PAREN: + /* tolerate only whitespace and embedded comments */ + if (c == ')') { + state = PREF_PARSE_UNTIL_SEMICOLON; + } else if (c == '/') { + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need space, comment or closing parentheses", + lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + + /* function terminator ';' parsing */ + case PREF_PARSE_UNTIL_SEMICOLON: + /* tolerate only whitespace and embedded comments */ + if (c == ';') { + if (!pref_DoCallback(ps)) + return false; + state = PREF_PARSE_INIT; + } + else if (c == '/') { + ps->nextstate = state; /* return here when done with comment */ + state = PREF_PARSE_COMMENT_MAYBE_START; + } + else if (!isspace(c)) { + pref_ReportParseProblem(*ps, "need space, comment or semicolon", + lineNum, true); + NS_WARNING("malformed pref file"); + return false; + } + break; + + /* eol parsing */ + case PREF_PARSE_UNTIL_EOL: + /* need to handle mac, unix, or dos line endings. + * PREF_PARSE_INIT will eat the next \n in case + * we have \r\n. */ + if (c == '\r' || c == '\n' || c == 0x1A) { + state = ps->nextstate; + ps->nextstate = PREF_PARSE_INIT; /* reset next state */ + } + break; + } + } + ps->state = state; + return true; +} + +#ifdef TEST_PREFREAD + +static void +pref_reader(void *closure, + const char *pref, + PrefValue val, + PrefType type, + bool defPref) +{ + printf("%spref(\"%s\", ", defPref ? "" : "user_", pref); + switch (type) { + case PREF_STRING: + printf("\"%s\");\n", val.stringVal); + break; + case PREF_INT: + printf("%i);\n", val.intVal); + break; + case PREF_BOOL: + printf("%s);\n", val.boolVal == false ? "false" : "true"); + break; + } +} + +int +main(int argc, char **argv) +{ + PrefParseState ps; + char buf[4096]; /* i/o buffer */ + FILE *fp; + int n; + + if (argc == 1) { + printf("usage: prefread file.js\n"); + return -1; + } + + fp = fopen(argv[1], "r"); + if (!fp) { + printf("failed to open file\n"); + return -1; + } + + PREF_InitParseState(&ps, pref_reader, nullptr, nullptr); + + while ((n = fread(buf, 1, sizeof(buf), fp)) > 0) + PREF_ParseBuf(&ps, buf, n); + + PREF_FinalizeParseState(&ps); + + fclose(fp); + return 0; +} + +#endif /* TEST_PREFREAD */ diff --git a/components/preferences/src/prefread.h b/components/preferences/src/prefread.h new file mode 100644 index 000000000..2a09b30b6 --- /dev/null +++ b/components/preferences/src/prefread.h @@ -0,0 +1,118 @@ +/* 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/. */ + +#ifndef prefread_h__ +#define prefread_h__ + +#include "prefapi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Callback function used to notify consumer of preference name value pairs. + * The pref name and value must be copied by the implementor of the callback + * if they are needed beyond the scope of the callback function. + * + * @param closure + * user data passed to PREF_InitParseState + * @param pref + * preference name + * @param val + * preference value + * @param type + * preference type (PREF_STRING, PREF_INT, or PREF_BOOL) + * @param defPref + * preference type (true: default, false: user preference) + * @param stickyPref + * default preference marked as a "sticky" pref + */ +typedef void (*PrefReader)(void *closure, + const char *pref, + PrefValue val, + PrefType type, + bool defPref, + bool stickyPref); + +/** + * Report any errors or warnings we encounter during parsing. + */ +typedef void (*PrefParseErrorReporter)(const char* message, int line, bool error); + +/* structure fields are private */ +typedef struct PrefParseState { + PrefReader reader; + PrefParseErrorReporter reporter; + void *closure; + int state; /* PREF_PARSE_... */ + int nextstate; /* sometimes used... */ + const char *smatch; /* string to match */ + int sindex; /* next char of smatch to check */ + /* also, counter in \u parsing */ + char16_t utf16[2]; /* parsing UTF16 (\u) escape */ + int esclen; /* length in esctmp */ + char esctmp[6]; /* raw escape to put back if err */ + char quotechar; /* char delimiter for quotations */ + char *lb; /* line buffer (only allocation) */ + char *lbcur; /* line buffer cursor */ + char *lbend; /* line buffer end */ + char *vb; /* value buffer (ptr into lb) */ + PrefType vtype; /* PREF_STRING,INT,BOOL */ + bool fdefault; /* true if (default) pref */ + bool fstickydefault; /* true if (sticky) pref */ +} PrefParseState; + +/** + * PREF_InitParseState + * + * Called to initialize a PrefParseState instance. + * + * @param ps + * PrefParseState instance. + * @param reader + * PrefReader callback function, which will be called once for each + * preference name value pair extracted. + * @param reporter + * PrefParseErrorReporter callback function, which will be called if we + * encounter any errors (stop) or warnings (continue) during parsing. + * @param closure + * PrefReader closure. + */ +void PREF_InitParseState(PrefParseState *ps, PrefReader reader, + PrefParseErrorReporter reporter, void *closure); + +/** + * PREF_FinalizeParseState + * + * Called to release any memory in use by the PrefParseState instance. + * + * @param ps + * PrefParseState instance. + */ +void PREF_FinalizeParseState(PrefParseState *ps); + +/** + * PREF_ParseBuf + * + * Called to parse a buffer containing some portion of a preference file. This + * function may be called repeatedly as new data is made available. The + * PrefReader callback function passed PREF_InitParseState will be called as + * preference name value pairs are extracted from the data. + * + * @param ps + * PrefParseState instance. Must have been initialized. + * @param buf + * Raw buffer containing data to be parsed. + * @param bufLen + * Length of buffer. + * + * @return false if buffer contains malformed content. + */ +bool PREF_ParseBuf(PrefParseState *ps, const char *buf, int bufLen); + +#ifdef __cplusplus +} +#endif +#endif /* prefread_h__ */ |