summaryrefslogtreecommitdiffstats
path: root/extensions/pref/autoconfig
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /extensions/pref/autoconfig
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'extensions/pref/autoconfig')
-rw-r--r--extensions/pref/autoconfig/moz.build11
-rw-r--r--extensions/pref/autoconfig/src/components.conf21
-rw-r--r--extensions/pref/autoconfig/src/moz.build21
-rw-r--r--extensions/pref/autoconfig/src/nsAutoConfig.cpp464
-rw-r--r--extensions/pref/autoconfig/src/nsAutoConfig.h54
-rw-r--r--extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp170
-rw-r--r--extensions/pref/autoconfig/src/nsJSConfigTriggers.h23
-rw-r--r--extensions/pref/autoconfig/src/nsReadConfig.cpp312
-rw-r--r--extensions/pref/autoconfig/src/nsReadConfig.h34
-rw-r--r--extensions/pref/autoconfig/src/prefcalls.js225
-rw-r--r--extensions/pref/autoconfig/test/marionette/autoconfig.cfg18
-rw-r--r--extensions/pref/autoconfig/test/marionette/autoconfig.js5
-rw-r--r--extensions/pref/autoconfig/test/marionette/manifest.ini1
-rw-r--r--extensions/pref/autoconfig/test/marionette/test_autoconfig.py101
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-all.cfg31
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg5
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg6
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox-check.cfg5
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox.js5
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-snap.cfg4
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg6
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig.js6
-rw-r--r--extensions/pref/autoconfig/test/unit/autoconfig_snap.js5
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig.js83
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig_custom_path.js19
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig_default_path.js13
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig_no_sandbox.js59
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js110
-rw-r--r--extensions/pref/autoconfig/test/unit/test_autoconfig_snap.js77
-rw-r--r--extensions/pref/autoconfig/test/unit/xpcshell.ini23
-rw-r--r--extensions/pref/autoconfig/test/unit/xpcshell_snap.ini8
31 files changed, 1925 insertions, 0 deletions
diff --git a/extensions/pref/autoconfig/moz.build b/extensions/pref/autoconfig/moz.build
new file mode 100644
index 0000000000..a8292ea733
--- /dev/null
+++ b/extensions/pref/autoconfig/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += ["src"]
+
+XPCSHELL_TESTS_MANIFESTS += ["test/unit/xpcshell.ini", "test/unit/xpcshell_snap.ini"]
+
+MARIONETTE_UNIT_MANIFESTS += ["test/marionette/manifest.ini"]
diff --git a/extensions/pref/autoconfig/src/components.conf b/extensions/pref/autoconfig/src/components.conf
new file mode 100644
index 0000000000..41de352694
--- /dev/null
+++ b/extensions/pref/autoconfig/src/components.conf
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ 'cid': '{ba5bc4c6-1dd1-11b2-bb89-b844c6ec0339}',
+ 'contract_ids': ['@mozilla.org/readconfig;1'],
+ 'type': 'nsReadConfig',
+ 'headers': ['/extensions/pref/autoconfig/src/nsReadConfig.h'],
+ 'init_method': 'Init',
+ 'categories': {
+ 'pref-config-startup': {
+ 'name': 'ReadConfig Module',
+ 'backgroundtasks': BackgroundTasksSelector.ALL_TASKS,
+ },
+ },
+ },
+]
diff --git a/extensions/pref/autoconfig/src/moz.build b/extensions/pref/autoconfig/src/moz.build
new file mode 100644
index 0000000000..12722cf6d7
--- /dev/null
+++ b/extensions/pref/autoconfig/src/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+UNIFIED_SOURCES += [
+ "nsAutoConfig.cpp",
+ "nsJSConfigTriggers.cpp",
+ "nsReadConfig.cpp",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+FINAL_LIBRARY = "xul"
+
+FINAL_TARGET_FILES.defaults.autoconfig += [
+ "prefcalls.js",
+]
diff --git a/extensions/pref/autoconfig/src/nsAutoConfig.cpp b/extensions/pref/autoconfig/src/nsAutoConfig.cpp
new file mode 100644
index 0000000000..907d4b7646
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsAutoConfig.cpp
@@ -0,0 +1,464 @@
+/* -*- 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/. */
+
+#include "mozilla/ResultExtensions.h"
+#include "nsAutoConfig.h"
+#include "nsJSConfigTriggers.h"
+
+#include "nsIURI.h"
+#include "nsIHttpChannel.h"
+#include "nsThreadUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIObserverService.h"
+#include "nsLiteralString.h"
+#include "nsIPromptService.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIStringBundle.h"
+#include "nsContentUtils.h"
+#include "nsCRT.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nspr.h"
+#include <algorithm>
+
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/Logging.h"
+#include "mozilla/SpinEventLoopUntil.h"
+
+using mozilla::LogLevel;
+
+mozilla::LazyLogModule MCD("MCD");
+
+// nsISupports Implementation
+
+NS_IMPL_ISUPPORTS(nsAutoConfig, nsITimerCallback, nsIStreamListener,
+ nsIObserver, nsIRequestObserver, nsISupportsWeakReference,
+ nsINamed)
+
+nsAutoConfig::nsAutoConfig() {}
+
+nsresult nsAutoConfig::Init() {
+ // member initializers and constructor code
+
+ nsresult rv;
+ mLoaded = false;
+
+ // Registering the object as an observer to the profile-after-change topic
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = observerService->AddObserver(this, "profile-after-change", true);
+
+ return rv;
+}
+
+nsAutoConfig::~nsAutoConfig() {}
+
+void nsAutoConfig::SetConfigURL(const char* aConfigURL) {
+ mConfigURL.Assign(aConfigURL);
+}
+
+NS_IMETHODIMP
+nsAutoConfig::OnStartRequest(nsIRequest* request) { return NS_OK; }
+
+NS_IMETHODIMP
+nsAutoConfig::OnDataAvailable(nsIRequest* request, nsIInputStream* aIStream,
+ uint64_t aSourceOffset, uint32_t aLength) {
+ uint32_t amt, size;
+ nsresult rv;
+ char buf[1024];
+
+ while (aLength) {
+ size = std::min<size_t>(aLength, sizeof(buf));
+ rv = aIStream->Read(buf, size, &amt);
+ if (NS_FAILED(rv)) return rv;
+ mBuf.Append(buf, amt);
+ aLength -= amt;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoConfig::OnStopRequest(nsIRequest* request, nsresult aStatus) {
+ nsresult rv;
+
+ // If the request is failed, go read the failover.jsc file
+ if (NS_FAILED(aStatus)) {
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("mcd request failed with status %" PRIx32 "\n",
+ static_cast<uint32_t>(aStatus)));
+ return readOfflineFile();
+ }
+
+ // Checking for the http response, if failure go read the failover file.
+ nsCOMPtr<nsIHttpChannel> pHTTPCon(do_QueryInterface(request));
+ if (pHTTPCon) {
+ uint32_t httpStatus;
+ rv = pHTTPCon->GetResponseStatus(&httpStatus);
+ if (NS_FAILED(rv) || httpStatus != 200) {
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("mcd http request failed with status %x\n", httpStatus));
+ return readOfflineFile();
+ }
+ }
+
+ // Send the autoconfig.jsc to javascript engine.
+
+ rv = EvaluateAdminConfigScript(mBuf.get(), mBuf.Length(), nullptr, false,
+ true, false);
+ if (NS_SUCCEEDED(rv)) {
+ // Write the autoconfig.jsc to failover.jsc (cached copy)
+ rv = writeFailoverFile();
+
+ if (NS_FAILED(rv)) NS_WARNING("Error writing failover.jsc file");
+
+ // Releasing the lock to allow the main thread to start execution
+ mLoaded = true;
+
+ return NS_OK;
+ }
+ // there is an error in parsing of the autoconfig file.
+ NS_WARNING(
+ "Error reading autoconfig.jsc from the network, reading the offline "
+ "version");
+ return readOfflineFile();
+}
+
+// Notify method as a TimerCallBack function
+NS_IMETHODIMP nsAutoConfig::Notify(nsITimer* timer) {
+ downloadAutoConfig();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAutoConfig::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsAutoConfig");
+ return NS_OK;
+}
+
+/* Observe() is called twice: once at the instantiation time and other
+ after the profile is set. It doesn't do anything but return NS_OK during the
+ creation time. Second time it calls downloadAutoConfig().
+*/
+
+NS_IMETHODIMP nsAutoConfig::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ nsresult rv = NS_OK;
+ if (!nsCRT::strcmp(aTopic, "profile-after-change")) {
+ // We will be calling downloadAutoConfig even if there is no profile
+ // name. Nothing will be passed as a parameter to the URL and the
+ // default case will be picked up by the script.
+
+ rv = downloadAutoConfig();
+ }
+
+ return rv;
+}
+
+nsresult nsAutoConfig::downloadAutoConfig() {
+ nsresult rv;
+ nsAutoCString emailAddr;
+ static bool firstTime = true;
+
+ if (mConfigURL.IsEmpty()) {
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("global config url is empty - did you set "
+ "autoadmin.global_config_url?\n"));
+ NS_WARNING("AutoConfig called without global_config_url");
+ return NS_OK;
+ }
+
+ // If there is an email address appended as an argument to the ConfigURL
+ // in the previous read, we need to remove it when timer kicks in and
+ // downloads the autoconfig file again.
+ // If necessary, the email address will be added again as an argument.
+ int32_t index = mConfigURL.RFindChar((char16_t)'?');
+ if (index != -1) mConfigURL.Truncate(index);
+
+ // Clean up the previous read, the new read is going to use the same buffer
+ if (!mBuf.IsEmpty()) mBuf.Truncate(0);
+
+ // Get the preferences branch and save it to the member variable
+ if (!mPrefBranch) {
+ nsCOMPtr<nsIPrefService> prefs =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv = prefs->GetBranch(nullptr, getter_AddRefs(mPrefBranch));
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Check to see if the network is online/offline
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ bool offline;
+ rv = ios->GetOffline(&offline);
+ if (NS_FAILED(rv)) return rv;
+
+ if (offline) {
+ bool offlineFailover;
+ rv = mPrefBranch->GetBoolPref("autoadmin.offline_failover",
+ &offlineFailover);
+ // Read the failover.jsc if the network is offline and the pref says so
+ if (NS_SUCCEEDED(rv) && offlineFailover) return readOfflineFile();
+ }
+
+ /* Append user's identity at the end of the URL if the pref says so.
+ First we are checking for the user's email address but if it is not
+ available in the case where the client is used without messenger, user's
+ profile name will be used as an unique identifier
+ */
+ bool appendMail;
+ rv = mPrefBranch->GetBoolPref("autoadmin.append_emailaddr", &appendMail);
+ if (NS_SUCCEEDED(rv) && appendMail) {
+ rv = getEmailAddr(emailAddr);
+ if (NS_SUCCEEDED(rv) && emailAddr.get()) {
+ /* Adding the unique identifier at the end of autoconfig URL.
+ In this case the autoconfig URL is a script and
+ emailAddr as passed as an argument
+ */
+ mConfigURL.Append('?');
+ mConfigURL.Append(emailAddr);
+ }
+ }
+
+ // create a new url
+ nsCOMPtr<nsIURI> url;
+ nsCOMPtr<nsIChannel> channel;
+
+ rv = NS_NewURI(getter_AddRefs(url), mConfigURL);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(
+ MCD, LogLevel::Debug,
+ ("failed to create URL - is autoadmin.global_config_url valid? - %s\n",
+ mConfigURL.get()));
+ return rv;
+ }
+
+ MOZ_LOG(MCD, LogLevel::Debug, ("running MCD url %s\n", mConfigURL.get()));
+ // open a channel for the url
+ rv = NS_NewChannel(
+ getter_AddRefs(channel), url, nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER,
+ nullptr, // nsICookieJarSettings
+ nullptr, // PerformanceStorage
+ nullptr, // loadGroup
+ nullptr, // aCallbacks
+ nsIRequest::INHIBIT_PERSISTENT_CACHING | nsIRequest::LOAD_BYPASS_CACHE);
+
+ if (NS_FAILED(rv)) return rv;
+
+ rv = channel->AsyncOpen(this);
+ if (NS_FAILED(rv)) {
+ readOfflineFile();
+ return rv;
+ }
+
+ // Set a repeating timer if the pref is set.
+ // This is to be done only once.
+ // Also We are having the event queue processing only for the startup
+ // It is not needed with the repeating timer.
+ if (firstTime) {
+ firstTime = false;
+
+ /* process events until we're finished. AutoConfig.jsc reading needs
+ to be finished before the browser starts loading up
+ We are waiting for the mLoaded which will be set through
+ onStopRequest or readOfflineFile methods
+ There is a possibility of deadlock so we need to make sure
+ that mLoaded will be set to true in any case (success/failure)
+ */
+
+ if (!mozilla::SpinEventLoopUntil("nsAutoConfig::downloadAutoConfig"_ns,
+ [&]() { return mLoaded; })) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t minutes;
+ rv = mPrefBranch->GetIntPref("autoadmin.refresh_interval", &minutes);
+ if (NS_SUCCEEDED(rv) && minutes > 0) {
+ // Create a new timer and pass this nsAutoConfig
+ // object as a timer callback.
+ MOZ_TRY_VAR(mTimer,
+ NS_NewTimerWithCallback(this, minutes * 60 * 1000,
+ nsITimer::TYPE_REPEATING_SLACK));
+ }
+ } // first_time
+
+ return NS_OK;
+} // nsPref::downloadAutoConfig()
+
+nsresult nsAutoConfig::readOfflineFile() {
+ nsresult rv;
+
+ /* Releasing the lock to allow main thread to start
+ execution. At this point we do not need to stall
+ the thread since all network activities are done.
+ */
+ mLoaded = true;
+
+ bool failCache;
+ rv = mPrefBranch->GetBoolPref("autoadmin.failover_to_cached", &failCache);
+ if (NS_SUCCEEDED(rv) && !failCache) {
+ // disable network connections and return.
+
+ nsCOMPtr<nsIIOService> ios = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ bool offline;
+ rv = ios->GetOffline(&offline);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!offline) {
+ rv = ios->SetOffline(true);
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // lock the "network.online" prference so user cannot toggle back to
+ // online mode.
+ rv = mPrefBranch->SetBoolPref("network.online", false);
+ if (NS_FAILED(rv)) return rv;
+
+ mPrefBranch->LockPref("network.online");
+ return NS_OK;
+ }
+
+ /* faiover_to_cached is set to true so
+ Open the file and read the content.
+ execute the javascript file
+ */
+
+ nsCOMPtr<nsIFile> failoverFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(failoverFile));
+ if (NS_FAILED(rv)) return rv;
+
+ failoverFile->AppendNative("failover.jsc"_ns);
+ rv = evaluateLocalFile(failoverFile);
+ if (NS_FAILED(rv))
+ NS_WARNING("Couldn't open failover.jsc, going back to default prefs");
+ return NS_OK;
+}
+
+nsresult nsAutoConfig::evaluateLocalFile(nsIFile* file) {
+ nsresult rv;
+ nsCOMPtr<nsIInputStream> inStr;
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), file);
+ if (NS_FAILED(rv)) return rv;
+
+ int64_t fileSize;
+ file->GetFileSize(&fileSize);
+ uint32_t fs = fileSize; // Converting 64 bit structure to unsigned int
+ char* buf = (char*)malloc(fs * sizeof(char));
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+
+ uint32_t amt = 0;
+ rv = inStr->Read(buf, fs, &amt);
+ if (NS_SUCCEEDED(rv)) {
+ EvaluateAdminConfigScript(buf, fs, nullptr, false, true, false);
+ }
+ inStr->Close();
+ free(buf);
+ return rv;
+}
+
+nsresult nsAutoConfig::writeFailoverFile() {
+ nsresult rv;
+ nsCOMPtr<nsIFile> failoverFile;
+ nsCOMPtr<nsIOutputStream> outStr;
+ uint32_t amt;
+
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+ getter_AddRefs(failoverFile));
+ if (NS_FAILED(rv)) return rv;
+
+ failoverFile->AppendNative("failover.jsc"_ns);
+
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outStr), failoverFile);
+ if (NS_FAILED(rv)) return rv;
+ rv = outStr->Write(mBuf.get(), mBuf.Length(), &amt);
+ outStr->Close();
+ return rv;
+}
+
+nsresult nsAutoConfig::getEmailAddr(nsACString& emailAddr) {
+ nsresult rv;
+ nsAutoCString prefValue;
+
+ /* Getting an email address through set of three preferences:
+ First getting a default account with
+ "mail.accountmanager.defaultaccount"
+ second getting an associated id with the default account
+ Third getting an email address with id
+ */
+
+ rv =
+ mPrefBranch->GetCharPref("mail.accountmanager.defaultaccount", prefValue);
+ if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty()) {
+ emailAddr = "mail.account."_ns + prefValue + ".identities"_ns;
+ rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
+ prefValue);
+ if (NS_FAILED(rv) || prefValue.IsEmpty())
+ return PromptForEMailAddress(emailAddr);
+ int32_t commandIndex = prefValue.FindChar(',');
+ if (commandIndex != kNotFound) prefValue.Truncate(commandIndex);
+ emailAddr = "mail.identity."_ns + prefValue + ".useremail"_ns;
+ rv = mPrefBranch->GetCharPref(PromiseFlatCString(emailAddr).get(),
+ prefValue);
+ if (NS_FAILED(rv) || prefValue.IsEmpty())
+ return PromptForEMailAddress(emailAddr);
+ emailAddr = prefValue;
+ } else {
+ // look for 4.x pref in case we just migrated.
+ rv = mPrefBranch->GetCharPref("mail.identity.useremail", prefValue);
+ if (NS_SUCCEEDED(rv) && !prefValue.IsEmpty())
+ emailAddr = prefValue;
+ else
+ PromptForEMailAddress(emailAddr);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsAutoConfig::PromptForEMailAddress(nsACString& emailAddress) {
+ nsresult rv;
+ nsCOMPtr<nsIPromptService> promptService =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ rv = bundleService->CreateBundle(
+ "chrome://autoconfig/locale/autoconfig.properties",
+ getter_AddRefs(bundle));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString title;
+ rv = bundle->GetStringFromName("emailPromptTitle", title);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString err;
+ rv = bundle->GetStringFromName("emailPromptMsg", err);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool check = false;
+ nsString emailResult;
+ bool success;
+ rv = promptService->Prompt(nullptr, title.get(), err.get(),
+ getter_Copies(emailResult), nullptr, &check,
+ &success);
+ if (!success) return NS_ERROR_FAILURE;
+ NS_ENSURE_SUCCESS(rv, rv);
+ LossyCopyUTF16toASCII(emailResult, emailAddress);
+ return NS_OK;
+}
diff --git a/extensions/pref/autoconfig/src/nsAutoConfig.h b/extensions/pref/autoconfig/src/nsAutoConfig.h
new file mode 100644
index 0000000000..3a78239286
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsAutoConfig.h
@@ -0,0 +1,54 @@
+/* -*- 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/. */
+
+#ifndef nsAutoConfig_h
+#define nsAutoConfig_h
+
+#include "nsITimer.h"
+#include "nsIFile.h"
+#include "nsINamed.h"
+#include "nsIObserver.h"
+#include "nsIStreamListener.h"
+#include "nsWeakReference.h"
+#include "nsString.h"
+
+class nsIPrefBranch;
+
+class nsAutoConfig final : public nsITimerCallback,
+ public nsIStreamListener,
+ public nsIObserver,
+ public nsSupportsWeakReference,
+ public nsINamed
+
+{
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIREQUESTOBSERVER
+ NS_DECL_NSISTREAMLISTENER
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsAutoConfig();
+ nsresult Init();
+
+ void SetConfigURL(const char* aConfigURL);
+
+ protected:
+ virtual ~nsAutoConfig();
+ nsresult downloadAutoConfig();
+ nsresult readOfflineFile();
+ nsresult evaluateLocalFile(nsIFile* file);
+ nsresult writeFailoverFile();
+ nsresult getEmailAddr(nsACString& emailAddr);
+ nsresult PromptForEMailAddress(nsACString& emailAddress);
+ nsCString mBuf;
+ nsCOMPtr<nsIPrefBranch> mPrefBranch;
+ bool mLoaded;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCString mConfigURL;
+};
+
+#endif
diff --git a/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp
new file mode 100644
index 0000000000..e0b4324f40
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.cpp
@@ -0,0 +1,170 @@
+/* -*- 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/. */
+
+#include "nsJSConfigTriggers.h"
+
+#include "jsapi.h"
+#include "nsIXPConnect.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nspr.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NullPrincipal.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ChromeUtilsBinding.h"
+#include "nsContentUtils.h"
+#include "nsJSPrincipals.h"
+#include "nsIScriptError.h"
+#include "js/PropertyAndElement.h" // JS_DefineProperty
+#include "js/Wrapper.h"
+#include "mozilla/Utf8.h"
+
+extern mozilla::LazyLogModule MCD;
+using mozilla::AutoSafeJSContext;
+using mozilla::IsUtf8;
+using mozilla::NullPrincipal;
+using mozilla::dom::AutoJSAPI;
+
+//*****************************************************************************
+
+static JS::PersistentRooted<JSObject*> autoconfigSystemSb;
+static JS::PersistentRooted<JSObject*> autoconfigSb;
+bool sandboxEnabled;
+
+nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled) {
+ // If the sandbox is already created, no need to create it again.
+ if (autoconfigSb.initialized()) return NS_OK;
+
+ sandboxEnabled = aSandboxEnabled;
+
+ // Grab XPConnect.
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+
+ // Grab the system principal.
+ nsCOMPtr<nsIPrincipal> principal;
+ nsContentUtils::GetSecurityManager()->GetSystemPrincipal(
+ getter_AddRefs(principal));
+
+ // Create a sandbox.
+ AutoSafeJSContext cx;
+ JS::Rooted<JSObject*> sandbox(cx);
+ nsresult rv = xpc->CreateSandbox(cx, principal, sandbox.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Unwrap, store and root the sandbox.
+ NS_ENSURE_STATE(sandbox);
+ autoconfigSystemSb.init(cx, js::UncheckedUnwrap(sandbox));
+
+ // Create an unprivileged sandbox.
+ principal = NullPrincipal::CreateWithoutOriginAttributes();
+ rv = xpc->CreateSandbox(cx, principal, sandbox.address());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ autoconfigSb.init(cx, js::UncheckedUnwrap(sandbox));
+
+ // Define gSandbox on system sandbox.
+ JSAutoRealm ar(cx, autoconfigSystemSb);
+
+ JS::Rooted<JS::Value> value(cx, JS::ObjectValue(*sandbox));
+
+ if (!JS_WrapValue(cx, &value) ||
+ !JS_DefineProperty(cx, autoconfigSystemSb, "gSandbox", value,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Define ChromeUtils for ChromeUtils.import.
+ if (!mozilla::dom::ChromeUtils_Binding::GetConstructorObject(cx)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult CentralizedAdminPrefManagerFinish() {
+ if (autoconfigSb.initialized()) {
+ AutoSafeJSContext cx;
+ autoconfigSb.reset();
+ autoconfigSystemSb.reset();
+ JS_MaybeGC(cx);
+ }
+ return NS_OK;
+}
+
+nsresult EvaluateAdminConfigScript(const char* js_buffer, size_t length,
+ const char* filename, bool globalContext,
+ bool callbacks, bool skipFirstLine,
+ bool isPrivileged) {
+ if (!sandboxEnabled) {
+ isPrivileged = true;
+ }
+ return EvaluateAdminConfigScript(
+ isPrivileged ? autoconfigSystemSb : autoconfigSb, js_buffer, length,
+ filename, globalContext, callbacks, skipFirstLine);
+}
+
+nsresult EvaluateAdminConfigScript(JS::Handle<JSObject*> sandbox,
+ const char* js_buffer, size_t length,
+ const char* filename, bool globalContext,
+ bool callbacks, bool skipFirstLine) {
+ if (skipFirstLine) {
+ /* In order to protect the privacy of the JavaScript preferences file
+ * from loading by the browser, we make the first line unparseable
+ * by JavaScript. We must skip that line here before executing
+ * the JavaScript code.
+ */
+ unsigned int i = 0;
+ while (i < length) {
+ char c = js_buffer[i++];
+ if (c == '\r') {
+ if (js_buffer[i] == '\n') i++;
+ break;
+ }
+ if (c == '\n') break;
+ }
+
+ length -= i;
+ js_buffer += i;
+ }
+
+ // Grab XPConnect.
+ nsCOMPtr<nsIXPConnect> xpc = nsIXPConnect::XPConnect();
+
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(sandbox)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ JSContext* cx = jsapi.cx();
+
+ nsAutoCString script(js_buffer, length);
+ JS::Rooted<JS::Value> v(cx);
+
+ nsString convertedScript;
+ bool isUTF8 = IsUtf8(script);
+ if (isUTF8) {
+ CopyUTF8toUTF16(script, convertedScript);
+ } else {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ nsLiteralString(
+ u"Your AutoConfig file is ASCII. Please convert it to UTF-8."),
+ nsIScriptError::warningFlag, "autoconfig"_ns, nullptr);
+ /* If the length is 0, the conversion failed. Fallback to ASCII */
+ convertedScript = NS_ConvertASCIItoUTF16(script);
+ }
+ {
+ JSAutoRealm ar(cx, autoconfigSystemSb);
+ JS::Rooted<JS::Value> value(cx, JS::BooleanValue(isUTF8));
+ if (!JS_DefineProperty(cx, autoconfigSystemSb, "gIsUTF8", value,
+ JSPROP_ENUMERATE)) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+ nsresult rv =
+ xpc->EvalInSandboxObject(convertedScript, filename, cx, sandbox, &v);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
diff --git a/extensions/pref/autoconfig/src/nsJSConfigTriggers.h b/extensions/pref/autoconfig/src/nsJSConfigTriggers.h
new file mode 100644
index 0000000000..1f7c3804b9
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsJSConfigTriggers.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 nsConfigTriggers_h
+#define nsConfigTriggers_h
+
+#include "nscore.h"
+#include "js/TypeDecls.h"
+
+nsresult EvaluateAdminConfigScript(const char* js_buffer, size_t length,
+ const char* filename, bool bGlobalContext,
+ bool bCallbacks, bool skipFirstLine,
+ bool isPrivileged = false);
+
+nsresult EvaluateAdminConfigScript(JS::Handle<JSObject*> sandbox,
+ const char* js_buffer, size_t length,
+ const char* filename, bool bGlobalContext,
+ bool bCallbacks, bool skipFirstLine);
+
+#endif // nsConfigTriggers_h
diff --git a/extensions/pref/autoconfig/src/nsReadConfig.cpp b/extensions/pref/autoconfig/src/nsReadConfig.cpp
new file mode 100644
index 0000000000..3da68faac6
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsReadConfig.cpp
@@ -0,0 +1,312 @@
+/* -*- 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/. */
+
+#include "nsReadConfig.h"
+#include "nsJSConfigTriggers.h"
+
+#include "mozilla/Logging.h"
+#include "mozilla/Components.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIAppStartup.h"
+#include "nsIChannel.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsIPromptService.h"
+#include "nsIStringBundle.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "nsCRT.h"
+#include "nspr.h"
+#include "nsXULAppAPI.h"
+
+#if defined(MOZ_WIDGET_GTK)
+# include "mozilla/WidgetUtilsGtk.h"
+#endif // defined(MOZ_WIDGET_GTK)
+
+using namespace mozilla;
+
+extern bool sandboxEnabled;
+
+extern mozilla::LazyLogModule MCD;
+
+extern nsresult CentralizedAdminPrefManagerInit(bool aSandboxEnabled);
+extern nsresult CentralizedAdminPrefManagerFinish();
+
+static nsresult DisplayError(void) {
+ nsresult rv;
+
+ nsCOMPtr<nsIPromptService> promptService =
+ do_GetService("@mozilla.org/prompter;1");
+ if (!promptService) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ if (!bundleService) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ bundleService->CreateBundle(
+ "chrome://autoconfig/locale/autoconfig.properties",
+ getter_AddRefs(bundle));
+ if (!bundle) return NS_ERROR_FAILURE;
+
+ nsAutoString title;
+ rv = bundle->GetStringFromName("readConfigTitle", title);
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoString err;
+ rv = bundle->GetStringFromName("readConfigMsg", err);
+ if (NS_FAILED(rv)) return rv;
+
+ return promptService->Alert(nullptr, title.get(), err.get());
+}
+
+// nsISupports Implementation
+
+NS_IMPL_ISUPPORTS(nsReadConfig, nsIObserver)
+
+nsReadConfig::nsReadConfig() : mRead(false) {}
+
+nsresult nsReadConfig::Init() {
+ nsresult rv;
+
+ nsCOMPtr<nsIObserverService> observerService =
+ do_GetService("@mozilla.org/observer-service;1", &rv);
+
+ if (observerService) {
+ rv =
+ observerService->AddObserver(this, NS_PREFSERVICE_READ_TOPIC_ID, false);
+ }
+ return (rv);
+}
+
+nsReadConfig::~nsReadConfig() { CentralizedAdminPrefManagerFinish(); }
+
+NS_IMETHODIMP nsReadConfig::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData) {
+ nsresult rv = NS_OK;
+
+ if (!nsCRT::strcmp(aTopic, NS_PREFSERVICE_READ_TOPIC_ID)) {
+ rv = readConfigFile();
+ // Don't show error alerts if the sandbox is enabled, just show
+ // sandbox warning.
+ if (NS_FAILED(rv)) {
+ if (sandboxEnabled) {
+ nsContentUtils::ReportToConsoleNonLocalized(
+ u"Autoconfig is sandboxed by default. See "
+ "https://support.mozilla.org/products/"
+ "firefox-enterprise for more information."_ns,
+ nsIScriptError::warningFlag, "autoconfig"_ns, nullptr);
+ } else {
+ rv = DisplayError();
+ if (NS_FAILED(rv)) {
+ nsCOMPtr<nsIAppStartup> appStartup =
+ components::AppStartup::Service();
+ if (appStartup) {
+ bool userAllowedQuit = true;
+ appStartup->Quit(nsIAppStartup::eAttemptQuit, 0, &userAllowedQuit);
+ }
+ }
+ }
+ }
+ }
+ return rv;
+}
+
+/**
+ * This is the blocklist for known bad autoconfig files.
+ */
+static const char* gBlockedConfigs[] = {"dsengine.cfg"};
+
+nsresult nsReadConfig::readConfigFile() {
+ nsresult rv = NS_OK;
+ nsAutoCString lockFileName;
+ nsAutoCString lockVendor;
+ uint32_t fileNameLen = 0;
+
+ nsCOMPtr<nsIPrefBranch> defaultPrefBranch;
+ nsCOMPtr<nsIPrefService> prefService =
+ do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ rv =
+ prefService->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch));
+ if (NS_FAILED(rv)) return rv;
+
+ constexpr auto channel = nsLiteralCString{MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)};
+
+ bool sandboxEnabled =
+ channel.EqualsLiteral("beta") || channel.EqualsLiteral("release");
+
+ mozilla::Unused << defaultPrefBranch->GetBoolPref(
+ "general.config.sandbox_enabled", &sandboxEnabled);
+
+ rv = defaultPrefBranch->GetCharPref("general.config.filename", lockFileName);
+
+ if (NS_FAILED(rv)) return rv;
+
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("general.config.filename = %s\n", lockFileName.get()));
+
+ for (size_t index = 0, len = mozilla::ArrayLength(gBlockedConfigs);
+ index < len; ++index) {
+ if (lockFileName == gBlockedConfigs[index]) {
+ // This is NS_OK because we don't want to show an error to the user
+ return rv;
+ }
+ }
+
+ // This needs to be read only once.
+ //
+ if (!mRead) {
+ // Initiate the new JS Context for Preference management
+
+ rv = CentralizedAdminPrefManagerInit(sandboxEnabled);
+ if (NS_FAILED(rv)) return rv;
+
+ // Open and evaluate function calls to set/lock/unlock prefs
+ rv = openAndEvaluateJSFile("prefcalls.js", 0, false, false);
+ if (NS_FAILED(rv)) return rv;
+
+ mRead = true;
+ }
+ // If the lockFileName is nullptr return ok, because no lockFile will be used
+
+ // Once the config file is read, we should check that the vendor name
+ // is consistent By checking for the vendor name after reading the config
+ // file we allow for the preference to be set (and locked) by the creator
+ // of the cfg file meaning the file can not be renamed (successfully).
+
+ nsCOMPtr<nsIPrefBranch> prefBranch;
+ rv = prefService->GetBranch(nullptr, getter_AddRefs(prefBranch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t obscureValue = 0;
+ (void)defaultPrefBranch->GetIntPref("general.config.obscure_value",
+ &obscureValue);
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("evaluating .cfg file %s with obscureValue %d\n", lockFileName.get(),
+ obscureValue));
+ rv = openAndEvaluateJSFile(lockFileName.get(), obscureValue, true, true);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(MCD, LogLevel::Debug,
+ ("error evaluating .cfg file %s %" PRIx32 "\n", lockFileName.get(),
+ static_cast<uint32_t>(rv)));
+ return rv;
+ }
+
+ rv = prefBranch->GetCharPref("general.config.filename", lockFileName);
+ if (NS_FAILED(rv))
+ // There is NO REASON we should ever get here. This is POST reading
+ // of the config file.
+ return NS_ERROR_FAILURE;
+
+ rv = prefBranch->GetCharPref("general.config.vendor", lockVendor);
+ // If vendor is not nullptr, do this check
+ if (NS_SUCCEEDED(rv)) {
+ fileNameLen = strlen(lockFileName.get());
+
+ // lockVendor and lockFileName should be the same with the addtion of
+ // .cfg to the filename by checking this post reading of the cfg file
+ // this value can be set within the cfg file adding a level of security.
+
+ if (strncmp(lockFileName.get(), lockVendor.get(), fileNameLen - 4) != 0) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // get the value of the autoconfig url
+ nsAutoCString urlName;
+ rv = prefBranch->GetCharPref("autoadmin.global_config_url", urlName);
+ if (NS_SUCCEEDED(rv) && !urlName.IsEmpty()) {
+ // Instantiating nsAutoConfig object if the pref is present
+ mAutoConfig = new nsAutoConfig();
+
+ rv = mAutoConfig->Init();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+
+ mAutoConfig->SetConfigURL(urlName.get());
+ }
+
+ return NS_OK;
+} // ReadConfigFile
+
+nsresult nsReadConfig::openAndEvaluateJSFile(const char* aFileName,
+ int32_t obscureValue,
+ bool isEncoded, bool isBinDir) {
+ nsresult rv;
+
+ nsCOMPtr<nsIInputStream> inStr;
+ if (isBinDir) {
+ nsCOMPtr<nsIFile> jsFile;
+#if defined(MOZ_WIDGET_GTK)
+ if (!mozilla::widget::IsRunningUnderFlatpakOrSnap()) {
+#endif // defined(MOZ_WIDGET_GTK)
+ rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(jsFile));
+#if defined(MOZ_WIDGET_GTK)
+ } else {
+ rv = NS_GetSpecialDirectory(NS_OS_SYSTEM_CONFIG_DIR,
+ getter_AddRefs(jsFile));
+ }
+#endif // defined(MOZ_WIDGET_GTK)
+ if (NS_FAILED(rv)) return rv;
+
+ rv = jsFile->AppendNative(nsDependentCString(aFileName));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), jsFile);
+ if (NS_FAILED(rv)) return rv;
+
+ } else {
+ nsAutoCString location("resource://gre/defaults/autoconfig/");
+ location += aFileName;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), location);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel), uri,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = channel->Open(getter_AddRefs(inStr));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ uint64_t fs64;
+ uint32_t amt = 0;
+ rv = inStr->Available(&fs64);
+ if (NS_FAILED(rv)) return rv;
+ // This used to use PR_Malloc(), which doesn't support over 4GB.
+ if (fs64 > UINT32_MAX) return NS_ERROR_FILE_TOO_BIG;
+ uint32_t fs = (uint32_t)fs64;
+
+ char* buf = (char*)malloc(fs * sizeof(char));
+ if (!buf) return NS_ERROR_OUT_OF_MEMORY;
+
+ rv = inStr->Read(buf, (uint32_t)fs, &amt);
+ NS_ASSERTION((amt == fs), "failed to read the entire configuration file!!");
+ if (NS_SUCCEEDED(rv)) {
+ if (obscureValue > 0) {
+ // Unobscure file by subtracting some value from every char.
+ for (uint32_t i = 0; i < amt; i++) buf[i] -= obscureValue;
+ }
+ rv = EvaluateAdminConfigScript(buf, amt, aFileName, false, true, isEncoded,
+ !isBinDir);
+ }
+ inStr->Close();
+ free(buf);
+
+ return rv;
+}
diff --git a/extensions/pref/autoconfig/src/nsReadConfig.h b/extensions/pref/autoconfig/src/nsReadConfig.h
new file mode 100644
index 0000000000..465a517186
--- /dev/null
+++ b/extensions/pref/autoconfig/src/nsReadConfig.h
@@ -0,0 +1,34 @@
+/* -*- 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/. */
+
+#ifndef nsReadConfig_h
+#define nsReadConfig_h
+
+#include "mozilla/RefPtr.h"
+#include "nsAutoConfig.h"
+#include "nsIObserver.h"
+
+class nsReadConfig final : public nsIObserver {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ nsReadConfig();
+
+ nsresult Init();
+
+ protected:
+ virtual ~nsReadConfig();
+
+ nsresult readConfigFile();
+ nsresult openAndEvaluateJSFile(const char* aFileName, int32_t obscureValue,
+ bool isEncoded, bool isBinDir);
+ bool mRead;
+
+ private:
+ RefPtr<nsAutoConfig> mAutoConfig;
+};
+
+#endif
diff --git a/extensions/pref/autoconfig/src/prefcalls.js b/extensions/pref/autoconfig/src/prefcalls.js
new file mode 100644
index 0000000000..b66e5df898
--- /dev/null
+++ b/extensions/pref/autoconfig/src/prefcalls.js
@@ -0,0 +1,225 @@
+/* global processLDAPValues */
+/* -*- tab-width: 4; indent-tabs-mode: nil; js-indent-level: 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/. */
+
+/* globals gSandbox */
+
+const LDAPSyncQueryContractID = "@mozilla.org/ldapsyncquery;1";
+
+var gVersion;
+var gIsUTF8;
+
+function getPrefBranch() {
+ return Services.prefs.getBranch(null);
+}
+
+function pref(prefName, value) {
+ try {
+ var prefBranch = getPrefBranch();
+
+ if (typeof value == "string") {
+ if (gIsUTF8) {
+ prefBranch.setStringPref(prefName, value);
+ return;
+ }
+ prefBranch.setCharPref(prefName, value);
+ } else if (typeof value == "number") {
+ prefBranch.setIntPref(prefName, value);
+ } else if (typeof value == "boolean") {
+ prefBranch.setBoolPref(prefName, value);
+ }
+ } catch (e) {
+ displayError("pref", e);
+ }
+}
+
+function defaultPref(prefName, value) {
+ try {
+ var prefBranch = Services.prefs.getDefaultBranch(null);
+ if (typeof value == "string") {
+ if (gIsUTF8) {
+ prefBranch.setStringPref(prefName, value);
+ return;
+ }
+ prefBranch.setCharPref(prefName, value);
+ } else if (typeof value == "number") {
+ prefBranch.setIntPref(prefName, value);
+ } else if (typeof value == "boolean") {
+ prefBranch.setBoolPref(prefName, value);
+ }
+ } catch (e) {
+ displayError("defaultPref", e);
+ }
+}
+
+function lockPref(prefName, value) {
+ try {
+ var prefBranch = getPrefBranch();
+
+ if (prefBranch.prefIsLocked(prefName)) {
+ prefBranch.unlockPref(prefName);
+ }
+
+ defaultPref(prefName, value);
+
+ prefBranch.lockPref(prefName);
+ } catch (e) {
+ displayError("lockPref", e);
+ }
+}
+
+function unlockPref(prefName) {
+ try {
+ var prefBranch = getPrefBranch();
+ prefBranch.unlockPref(prefName);
+ } catch (e) {
+ displayError("unlockPref", e);
+ }
+}
+
+function getPref(prefName) {
+ try {
+ var prefBranch = getPrefBranch();
+
+ switch (prefBranch.getPrefType(prefName)) {
+ case prefBranch.PREF_STRING:
+ if (gIsUTF8) {
+ return prefBranch.getStringPref(prefName);
+ }
+ return prefBranch.getCharPref(prefName);
+
+ case prefBranch.PREF_INT:
+ return prefBranch.getIntPref(prefName);
+
+ case prefBranch.PREF_BOOL:
+ return prefBranch.getBoolPref(prefName);
+ default:
+ return null;
+ }
+ } catch (e) {
+ displayError("getPref", e);
+ }
+ return undefined;
+}
+
+function clearPref(prefName) {
+ try {
+ var prefBranch = getPrefBranch();
+ prefBranch.clearUserPref(prefName);
+ } catch (e) {}
+}
+
+function setLDAPVersion(version) {
+ gVersion = version;
+}
+
+function getLDAPAttributes(host, base, filter, attribs, isSecure) {
+ try {
+ var urlSpec =
+ "ldap" +
+ (isSecure ? "s" : "") +
+ "://" +
+ host +
+ (isSecure ? ":636" : "") +
+ "/" +
+ base +
+ "?" +
+ attribs +
+ "?sub?" +
+ filter;
+
+ // nsILDAP* are only defined in comm-central.
+ // eslint-disable-next-line mozilla/valid-ci-uses
+ var url = Services.io.newURI(urlSpec).QueryInterface(Ci.nsILDAPURL);
+
+ var ldapquery = Cc[LDAPSyncQueryContractID].createInstance(
+ // eslint-disable-next-line mozilla/valid-ci-uses
+ Ci.nsILDAPSyncQuery
+ );
+ // default to LDAP v3
+ if (!gVersion) {
+ // eslint-disable-next-line mozilla/valid-ci-uses
+ gVersion = Ci.nsILDAPConnection.VERSION3;
+ }
+ // user supplied method
+ if ("processLDAPValues" in gSandbox) {
+ gSandbox.processLDAPValues(ldapquery.getQueryResults(url, gVersion));
+ } else {
+ processLDAPValues(ldapquery.getQueryResults(url, gVersion));
+ }
+ } catch (e) {
+ displayError("getLDAPAttributes", e);
+ }
+}
+
+function getLDAPValue(str, key) {
+ try {
+ if (str == null || key == null) {
+ return null;
+ }
+
+ var search_key = "\n" + key + "=";
+
+ var start_pos = str.indexOf(search_key);
+ if (start_pos == -1) {
+ return null;
+ }
+
+ start_pos += search_key.length;
+
+ var end_pos = str.indexOf("\n", start_pos);
+ if (end_pos == -1) {
+ end_pos = str.length;
+ }
+
+ return str.substring(start_pos, end_pos);
+ } catch (e) {
+ displayError("getLDAPValue", e);
+ }
+ return undefined;
+}
+
+function displayError(funcname, message) {
+ try {
+ var bundle = Services.strings.createBundle(
+ "chrome://autoconfig/locale/autoconfig.properties"
+ );
+
+ var title = bundle.GetStringFromName("autoConfigTitle");
+ var msg = bundle.formatStringFromName("autoConfigMsg", [funcname]);
+ Services.prompt.alert(null, title, msg + " " + message);
+ } catch (e) {}
+}
+
+function getenv(name) {
+ try {
+ return Services.env.get(name);
+ } catch (e) {
+ displayError("getEnvironment", e);
+ }
+ return undefined;
+}
+
+var APIs = {
+ pref,
+ defaultPref,
+ lockPref,
+ unlockPref,
+ getPref,
+ clearPref,
+ setLDAPVersion,
+ getLDAPAttributes,
+ getLDAPValue,
+ displayError,
+ getenv,
+};
+
+for (let [defineAs, func] of Object.entries(APIs)) {
+ Cu.exportFunction(func, gSandbox, { defineAs });
+}
+
+Object.defineProperty(Cu.waiveXrays(gSandbox), "gIsUTF8", {
+ get: Cu.exportFunction(() => gIsUTF8, gSandbox),
+});
diff --git a/extensions/pref/autoconfig/test/marionette/autoconfig.cfg b/extensions/pref/autoconfig/test/marionette/autoconfig.cfg
new file mode 100644
index 0000000000..1e6fde18bf
--- /dev/null
+++ b/extensions/pref/autoconfig/test/marionette/autoconfig.cfg
@@ -0,0 +1,18 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+// Verify this one has a user value
+pref("_autoconfig_.test.userpref", "userpref");
+
+// Verify this one has a default pref
+defaultPref("_autoconfig_.test.defaultpref", "defaultpref");
+
+// Verify this one is locked
+lockPref("_autoconfig_.test.lockpref", "lockpref");
+
+lockPref("_autoconfig_.test.unlockpref", "unlockpref");
+// Verify this one is unlocked
+unlockPref("_autoconfig_.test.unlockpref");
+
+pref("_autoconfig_.test.clearpref", "clearpref");
+// Verify this one has no value
+clearPref("_autoconfig_.test.clearpref");
diff --git a/extensions/pref/autoconfig/test/marionette/autoconfig.js b/extensions/pref/autoconfig/test/marionette/autoconfig.js
new file mode 100644
index 0000000000..c891c5d108
--- /dev/null
+++ b/extensions/pref/autoconfig/test/marionette/autoconfig.js
@@ -0,0 +1,5 @@
+/* global pref */
+pref("general.config.sandbox_enabled", true);
+pref("general.config.filename", "autoconfig.cfg");
+pref("general.config.vendor", "autoconfig");
+pref("general.config.obscure_value", 0);
diff --git a/extensions/pref/autoconfig/test/marionette/manifest.ini b/extensions/pref/autoconfig/test/marionette/manifest.ini
new file mode 100644
index 0000000000..c10c20c2ae
--- /dev/null
+++ b/extensions/pref/autoconfig/test/marionette/manifest.ini
@@ -0,0 +1 @@
+[test_autoconfig.py]
diff --git a/extensions/pref/autoconfig/test/marionette/test_autoconfig.py b/extensions/pref/autoconfig/test/marionette/test_autoconfig.py
new file mode 100644
index 0000000000..0cdc0feede
--- /dev/null
+++ b/extensions/pref/autoconfig/test/marionette/test_autoconfig.py
@@ -0,0 +1,101 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+import shutil
+
+from marionette_harness import MarionetteTestCase
+
+
+class TestAutoConfig(MarionetteTestCase):
+ def tearDown(self):
+ self.marionette.quit(in_app=False, clean=True)
+
+ if hasattr(self, "pref_file"):
+ os.remove(self.pref_file)
+ if hasattr(self, "autoconfig_file"):
+ os.remove(self.autoconfig_file)
+
+ super(TestAutoConfig, self).tearDown()
+
+ def pref_has_user_value(self, pref):
+ with self.marionette.using_context("chrome"):
+ return self.marionette.execute_script(
+ """
+ return Services.prefs.prefHasUserValue(arguments[0]);
+ """,
+ script_args=(pref,),
+ )
+
+ def pref_is_locked(self, pref):
+ with self.marionette.using_context("chrome"):
+ return self.marionette.execute_script(
+ """
+ return Services.prefs.prefIsLocked(arguments[0]);
+ """,
+ script_args=(pref,),
+ )
+
+ def test_autoconfig(self):
+ with self.marionette.using_context("chrome"):
+ self.exe_dir = self.marionette.execute_script(
+ """
+ return Services.dirsvc.get("GreD", Ci.nsIFile).path;
+ """
+ )
+
+ self.marionette.quit()
+
+ test_dir = os.path.dirname(__file__)
+ self.pref_file = os.path.join(self.exe_dir, "defaults", "pref", "autoconfig.js")
+ shutil.copyfile(os.path.join(test_dir, "autoconfig.js"), self.pref_file)
+ self.autoconfig_file = os.path.join(self.exe_dir, "autoconfig.cfg")
+ shutil.copyfile(os.path.join(test_dir, "autoconfig.cfg"), self.autoconfig_file)
+
+ self.marionette.start_session()
+
+ with self.marionette.using_context("chrome"):
+ self.assertTrue(
+ self.pref_has_user_value("_autoconfig_.test.userpref"),
+ "Pref should have user value",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref("_autoconfig_.test.userpref"),
+ "userpref",
+ "User pref should be set",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref("_autoconfig_.test.defaultpref", True),
+ "defaultpref",
+ "Default pref should be set",
+ )
+
+ self.assertTrue(
+ self.pref_is_locked("_autoconfig_.test.lockpref"),
+ "Pref should be locked",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref("_autoconfig_.test.lockpref"),
+ "lockpref",
+ "Locked pref should be set",
+ )
+
+ self.assertFalse(
+ self.pref_is_locked("_autoconfig_.test.unlockpref"),
+ "Pref should be unlocked",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref("_autoconfig_.test.unlockpref"),
+ "unlockpref",
+ "Unlocked pref should be set",
+ )
+
+ self.assertFalse(
+ self.pref_has_user_value("_autoconfig_.test.clearpref"),
+ "Pref should be cleared",
+ )
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg
new file mode 100644
index 0000000000..f636c3ec22
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-all.cfg
@@ -0,0 +1,31 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+// Verify this one has a user value
+pref("_autoconfig_.test.userpref", "userpref");
+
+// Verify this one has a default pref
+defaultPref("_autoconfig_.test.defaultpref", "defaultpref");
+
+// Verify this one is locked
+lockPref("_autoconfig_.test.lockpref", "lockpref");
+
+lockPref("_autoconfig_.test.unlockpref", "unlockpref");
+// Verify this one is unlocked
+unlockPref("_autoconfig_.test.unlockpref");
+
+pref("_autoconfig_.test.clearpref", "clearpref");
+// Verify this one has no value
+clearPref("_autoconfig_.test.clearpref");
+
+// Verify this one is set to the correct value
+pref("_autoconfig_.test.getpref.query", "getpref");
+pref("_autoconfig_.test.getpref", getPref("_autoconfig_.test.getpref.query"));
+
+// Verify this one is set to the correct value
+pref("_autoconfig_.test.getenv", getenv("AUTOCONFIG_TEST_GETENV"));
+
+// Since we can't test displayError directly, verify that it
+// exists and is a function
+pref("_autoconfig_.test.displayerror", typeof(displayError));
+
+// We are not getPrefBranch because it is being removed
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg
new file mode 100644
index 0000000000..2af6cfc109
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-chromecheck.cfg
@@ -0,0 +1,5 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+lockPref("_test.string.typeofComponents", typeof Components);
+lockPref("_test.string.typeofChromeUtils", typeof ChromeUtils);
+lockPref("_test.string.typeofServices", typeof Services);
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg
new file mode 100644
index 0000000000..6b96c65fd0
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-latin1.cfg
@@ -0,0 +1,6 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+// ©
+lockPref("_test.string.ASCII", "ASCII");
+lockPref("_test.string.non-ASCII", "日本語");
+lockPref("_test.string.getPref", getPref("_test.string.non-ASCII"));
+lockPref("_test.string.gIsUTF8", String(this.gIsUTF8));
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox-check.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox-check.cfg
new file mode 100644
index 0000000000..a39080562f
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox-check.cfg
@@ -0,0 +1,5 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+lockPref("_test.typeof_Components", typeof Components);
+lockPref("_test.typeof_ChromeUtils", typeof ChromeUtils);
+lockPref("_test.typeof_Services", typeof Services);
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox.js b/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox.js
new file mode 100644
index 0000000000..a53b53dfb6
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-no-sandbox.js
@@ -0,0 +1,5 @@
+/* global pref */
+pref("general.config.sandbox_enabled", false);
+pref("general.config.filename", "autoconfig.cfg");
+pref("general.config.vendor", "autoconfig");
+pref("general.config.obscure_value", 0);
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-snap.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-snap.cfg
new file mode 100644
index 0000000000..c5c67bfbb7
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-snap.cfg
@@ -0,0 +1,4 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+// Verify this one has a user value
+pref("_autoconfig_.test.userpref-snap", "userpref-snap");
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg b/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg
new file mode 100644
index 0000000000..eec7899420
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig-utf8.cfg
@@ -0,0 +1,6 @@
+// # don't remove this comment! (the first line is ignored by Mozilla)
+
+lockPref("_test.string.ASCII", "UTF-8");
+lockPref("_test.string.non-ASCII", "日本語");
+lockPref("_test.string.getPref", getPref("_test.string.non-ASCII"));
+lockPref("_test.string.gIsUTF8", String(this.gIsUTF8));
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig.js b/extensions/pref/autoconfig/test/unit/autoconfig.js
new file mode 100644
index 0000000000..284c9c66c5
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig.js
@@ -0,0 +1,6 @@
+/* global pref */
+pref("general.config.sandbox_enabled", true);
+pref("general.config.filename", "autoconfig.cfg");
+pref("general.config.vendor", "autoconfig");
+pref("general.config.obscure_value", 0);
+
diff --git a/extensions/pref/autoconfig/test/unit/autoconfig_snap.js b/extensions/pref/autoconfig/test/unit/autoconfig_snap.js
new file mode 100644
index 0000000000..bfa1df4859
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/autoconfig_snap.js
@@ -0,0 +1,5 @@
+/* global pref */
+pref("general.config.sandbox_enabled", true);
+pref("general.config.filename", "autoconfig-snap.cfg");
+pref("general.config.vendor", "autoconfig-snap");
+pref("general.config.obscure_value", 0);
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig.js b/extensions/pref/autoconfig/test/unit/test_autoconfig.js
new file mode 100644
index 0000000000..60284c4544
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unsafe-finally: "off"*/
+/* Turning off this rule to allow control flow operations in finally block
+ * http://eslint.org/docs/rules/no-unsafe-finally */
+
+function run_test() {
+ let prefs = Services.prefs.getBranch(null);
+ let defPrefs = Services.prefs.getDefaultBranch(null);
+
+ let greD = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let defaultPrefD = Services.dirsvc.get("PrfDef", Ci.nsIFile);
+ let testDir = do_get_cwd();
+
+ try {
+ let autoConfigJS = testDir.clone();
+ autoConfigJS.append("autoconfig.js");
+ autoConfigJS.copyTo(defaultPrefD, "autoconfig.js");
+
+ // Make sure nsReadConfig is initialized.
+ Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports);
+ Services.prefs.resetPrefs();
+
+ let autoConfigCfg = testDir.clone();
+ autoConfigCfg.append("autoconfig-all.cfg");
+ autoConfigCfg.copyTo(greD, "autoconfig.cfg");
+
+ Services.env.set("AUTOCONFIG_TEST_GETENV", "getenv");
+
+ Services.obs.notifyObservers(
+ Services.prefs,
+ "prefservice:before-read-userprefs"
+ );
+
+ ok(prefs.prefHasUserValue("_autoconfig_.test.userpref"));
+ equal("userpref", prefs.getStringPref("_autoconfig_.test.userpref"));
+
+ equal(
+ "defaultpref",
+ defPrefs.getStringPref("_autoconfig_.test.defaultpref")
+ );
+ equal("defaultpref", prefs.getStringPref("_autoconfig_.test.defaultpref"));
+
+ ok(prefs.prefIsLocked("_autoconfig_.test.lockpref"));
+ equal("lockpref", prefs.getStringPref("_autoconfig_.test.lockpref"));
+
+ ok(!prefs.prefIsLocked("_autoconfig_.test.unlockpref"));
+ equal("unlockpref", prefs.getStringPref("_autoconfig_.test.unlockpref"));
+
+ ok(!prefs.prefHasUserValue("_autoconfig_.test.clearpref"));
+
+ equal("getpref", prefs.getStringPref("_autoconfig_.test.getpref"));
+
+ equal("getenv", prefs.getStringPref("_autoconfig_.test.getenv"));
+
+ equal("function", prefs.getStringPref("_autoconfig_.test.displayerror"));
+
+ Services.prefs.resetPrefs();
+ } finally {
+ try {
+ let autoConfigJS = defaultPrefD.clone();
+ autoConfigJS.append("autoconfig.js");
+ autoConfigJS.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ try {
+ let autoConfigCfg = greD.clone();
+ autoConfigCfg.append("autoconfig.cfg");
+ autoConfigCfg.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ Services.prefs.resetPrefs();
+ }
+}
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_custom_path.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_custom_path.js
new file mode 100644
index 0000000000..b237e0620c
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_custom_path.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+
+function run_test() {
+ let testDirName = do_get_cwd().clone();
+ Services.env.set("MOZ_SYSTEM_CONFIG_DIR", testDirName.path);
+
+ updateAppInfo();
+
+ let customSysConfD = Services.dirsvc.get("SysConfD", Ci.nsIFile);
+ let parent = customSysConfD.parent;
+ let child = customSysConfD.leafName;
+ notEqual("/etc", parent.path, "SysConfD is not in /etc");
+ equal("xpcshell", child, "SysConfD is xpcshell");
+}
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_default_path.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_default_path.js
new file mode 100644
index 0000000000..761a192ac7
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_default_path.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+
+function run_test() {
+ updateAppInfo();
+
+ let defaultSysConfD = Services.dirsvc.get("SysConfD", Ci.nsIFile);
+ equal("/etc/xpcshell", defaultSysConfD.path, "SysConfD is in /etc");
+}
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_no_sandbox.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_no_sandbox.js
new file mode 100644
index 0000000000..7553432bc0
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_no_sandbox.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unsafe-finally: "off"*/
+
+function run_test() {
+ let prefs = Services.prefs.getBranch(null);
+
+ let greD = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let defaultPrefD = Services.dirsvc.get("PrfDef", Ci.nsIFile);
+ let testDir = do_get_cwd();
+
+ try {
+ let autoConfigJS = testDir.clone();
+ autoConfigJS.append("autoconfig-no-sandbox.js");
+ autoConfigJS.copyTo(defaultPrefD, "autoconfig.js");
+
+ // Make sure nsReadConfig is initialized.
+ Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports);
+ Services.prefs.resetPrefs();
+
+ let autoConfigCfg = testDir.clone();
+ autoConfigCfg.append("autoconfig-no-sandbox-check.cfg");
+ autoConfigCfg.copyTo(greD, "autoconfig.cfg");
+
+ Services.obs.notifyObservers(
+ Services.prefs,
+ "prefservice:before-read-userprefs"
+ );
+
+ equal("object", prefs.getStringPref("_test.typeof_Components"));
+ equal("object", prefs.getStringPref("_test.typeof_ChromeUtils"));
+ equal("object", prefs.getStringPref("_test.typeof_Services"));
+
+ Services.prefs.resetPrefs();
+ } finally {
+ try {
+ let autoConfigJS = defaultPrefD.clone();
+ autoConfigJS.append("autoconfig.js");
+ autoConfigJS.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ try {
+ let autoConfigCfg = greD.clone();
+ autoConfigCfg.append("autoconfig.cfg");
+ autoConfigCfg.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ Services.prefs.resetPrefs();
+ }
+}
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
new file mode 100644
index 0000000000..e535469360
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_nonascii.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unsafe-finally: "off"*/
+/* Turning off this rule to allow control flow operations in finally block
+ * http://eslint.org/docs/rules/no-unsafe-finally */
+
+function run_test() {
+ let greD = Services.dirsvc.get("GreD", Ci.nsIFile);
+ let defaultPrefD = Services.dirsvc.get("PrfDef", Ci.nsIFile);
+ let testDir = do_get_cwd();
+
+ try {
+ let autoConfigJS = testDir.clone();
+ autoConfigJS.append("autoconfig.js");
+ autoConfigJS.copyTo(defaultPrefD, "autoconfig.js");
+
+ // Make sure nsReadConfig is initialized.
+ Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports);
+ Services.prefs.resetPrefs();
+
+ var tests = [
+ {
+ filename: "autoconfig-utf8.cfg",
+ prefs: {
+ "_test.string.ASCII": "UTF-8",
+ "_test.string.non-ASCII": "日本語",
+ "_test.string.getPref": "日本語",
+ "_test.string.gIsUTF8": "true",
+ },
+ },
+ {
+ filename: "autoconfig-latin1.cfg",
+ prefs: {
+ "_test.string.ASCII": "ASCII",
+ "_test.string.non-ASCII": "日本語",
+ "_test.string.getPref": "日本語",
+ "_test.string.gIsUTF8": "false",
+ },
+ },
+ {
+ filename: "autoconfig-chromecheck.cfg",
+ prefs: {
+ "_test.string.typeofComponents": "undefined",
+ "_test.string.typeofChromeUtils": "undefined",
+ "_test.string.typeofServices": "undefined",
+ },
+ },
+ ];
+
+ function testAutoConfig(test) {
+ // Make sure pref values are unset.
+ for (let prefName in test.prefs) {
+ Assert.equal(
+ Ci.nsIPrefBranch.PREF_INVALID,
+ Services.prefs.getPrefType(prefName)
+ );
+ }
+
+ let autoConfigCfg = testDir.clone();
+ autoConfigCfg.append(test.filename);
+ autoConfigCfg.copyTo(greD, "autoconfig.cfg");
+
+ Services.obs.notifyObservers(
+ Services.prefs,
+ "prefservice:before-read-userprefs"
+ );
+
+ for (let prefName in test.prefs) {
+ Assert.equal(
+ test.prefs[prefName],
+ Services.prefs.getStringPref(prefName)
+ );
+ }
+
+ Services.prefs.resetPrefs();
+ // Make sure pref values are reset.
+ for (let prefName in test.prefs) {
+ Assert.equal(
+ Ci.nsIPrefBranch.PREF_INVALID,
+ Services.prefs.getPrefType(prefName)
+ );
+ }
+ }
+
+ tests.forEach(testAutoConfig);
+ } finally {
+ try {
+ let autoConfigJS = defaultPrefD.clone();
+ autoConfigJS.append("autoconfig.js");
+ autoConfigJS.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ try {
+ let autoConfigCfg = greD.clone();
+ autoConfigCfg.append("autoconfig.cfg");
+ autoConfigCfg.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+
+ Services.prefs.resetPrefs();
+ }
+}
diff --git a/extensions/pref/autoconfig/test/unit/test_autoconfig_snap.js b/extensions/pref/autoconfig/test/unit/test_autoconfig_snap.js
new file mode 100644
index 0000000000..1a58e11cf5
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/test_autoconfig_snap.js
@@ -0,0 +1,77 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/* eslint no-unsafe-finally: "off"*/
+/* Turning off this rule to allow control flow operations in finally block
+ * http://eslint.org/docs/rules/no-unsafe-finally */
+
+const { updateAppInfo } = ChromeUtils.importESModule(
+ "resource://testing-common/AppInfo.sys.mjs"
+);
+
+function ensureRemove(file) {
+ try {
+ file.remove(false);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
+ throw e;
+ }
+ }
+}
+
+async function run_test() {
+ let prefs = Services.prefs.getBranch(null);
+
+ let testDir = do_get_cwd();
+ let confDir = testDir.clone();
+ confDir.append("MozSystemConfigDir");
+ Services.env.set("MOZ_SYSTEM_CONFIG_DIR", confDir.path);
+ Services.env.set("SNAP_INSTANCE_NAME", "xpcshell");
+
+ updateAppInfo();
+
+ let sysConfD = Services.dirsvc.get("SysConfD", Ci.nsIFile);
+
+ let defaultPrefDExtra = sysConfD.clone();
+ defaultPrefDExtra.append("defaults");
+ defaultPrefDExtra.append("pref");
+
+ await IOUtils.makeDirectory(defaultPrefDExtra.path);
+
+ const kAutoConfigFile = defaultPrefDExtra.clone();
+ kAutoConfigFile.append("autoconfig_snap.js");
+ const kAutoConfigCfg = sysConfD.clone();
+ kAutoConfigCfg.append("autoconfig-snap.cfg");
+
+ let autoConfigJS = testDir.clone();
+ autoConfigJS.append(kAutoConfigFile.leafName);
+
+ let autoConfigCfg = testDir.clone();
+ autoConfigCfg.append(kAutoConfigCfg.leafName);
+
+ try {
+ autoConfigJS.copyTo(kAutoConfigFile.parent, kAutoConfigFile.leafName);
+ autoConfigCfg.copyTo(kAutoConfigCfg.parent, kAutoConfigCfg.leafName);
+
+ // Make sure nsReadConfig is initialized.
+ Cc["@mozilla.org/readconfig;1"].getService(Ci.nsISupports);
+ Services.prefs.resetPrefs();
+
+ Services.obs.notifyObservers(
+ Services.prefs,
+ "prefservice:before-read-userprefs"
+ );
+
+ ok(prefs.prefHasUserValue("_autoconfig_.test.userpref-snap"));
+ equal(
+ "userpref-snap",
+ prefs.getStringPref("_autoconfig_.test.userpref-snap")
+ );
+
+ Services.prefs.resetPrefs();
+ } finally {
+ ensureRemove(kAutoConfigFile);
+ ensureRemove(kAutoConfigCfg);
+ Services.prefs.resetPrefs();
+ }
+}
diff --git a/extensions/pref/autoconfig/test/unit/xpcshell.ini b/extensions/pref/autoconfig/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..fd09da01af
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/xpcshell.ini
@@ -0,0 +1,23 @@
+[DEFAULT]
+head =
+skip-if =
+ toolkit == 'android'
+ os == 'win' && msix # Does not work in MSIX builds.
+support-files =
+ autoconfig-all.cfg
+ autoconfig-latin1.cfg
+ autoconfig-utf8.cfg
+ autoconfig-chromecheck.cfg
+ autoconfig-no-sandbox-check.cfg
+ autoconfig.js
+ autoconfig-no-sandbox.js
+
+[test_autoconfig.js]
+[test_autoconfig_nonascii.js]
+run-sequentially = very high failure rate in parallel
+[test_autoconfig_no_sandbox.js]
+run-sequentially = very high failure rate in parallel
+[test_autoconfig_default_path.js]
+run-if = os == 'linux'
+[test_autoconfig_custom_path.js]
+run-if = os == 'linux'
diff --git a/extensions/pref/autoconfig/test/unit/xpcshell_snap.ini b/extensions/pref/autoconfig/test/unit/xpcshell_snap.ini
new file mode 100644
index 0000000000..ff24d46646
--- /dev/null
+++ b/extensions/pref/autoconfig/test/unit/xpcshell_snap.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+head =
+skip-if = os != 'linux'
+
+[test_autoconfig_snap.js]
+support-files =
+ autoconfig_snap.js
+ autoconfig-snap.cfg