fune/browser/components/preferences/preferences.js
Cristian Tuns b3bf09cc0d Backed out 6 changesets (bug 1816934, bug 1817182, bug 1817179, bug 1817183) for causing dt failures in browser_jsterm_autocomplete_null.js CLOSED TREE
Backed out changeset 17d4c013ed92 (bug 1817183)
Backed out changeset cfed8d9c23f3 (bug 1817183)
Backed out changeset 62fe2f589efe (bug 1817182)
Backed out changeset 557bd773fb85 (bug 1817179)
Backed out changeset 7f8a7865868b (bug 1816934)
Backed out changeset d6c1d4c0d2a0 (bug 1816934)
2023-02-17 10:51:33 -05:00

659 lines
21 KiB
JavaScript

/* - 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 globals from the files imported by the .xul files.
/* import-globals-from main.js */
/* import-globals-from home.js */
/* import-globals-from search.js */
/* import-globals-from containers.js */
/* import-globals-from privacy.js */
/* import-globals-from sync.js */
/* import-globals-from experimental.js */
/* import-globals-from moreFromMozilla.js */
/* import-globals-from findInPage.js */
/* import-globals-from /browser/base/content/utilityOverlay.js */
/* import-globals-from /toolkit/content/preferencesBindings.js */
"use strict";
var { AppConstants } = ChromeUtils.importESModule(
"resource://gre/modules/AppConstants.sys.mjs"
);
var { Downloads } = ChromeUtils.importESModule(
"resource://gre/modules/Downloads.sys.mjs"
);
var { Integration } = ChromeUtils.importESModule(
"resource://gre/modules/Integration.sys.mjs"
);
/* global DownloadIntegration */
Integration.downloads.defineESModuleGetter(
this,
"DownloadIntegration",
"resource://gre/modules/DownloadIntegration.sys.mjs"
);
var { PrivateBrowsingUtils } = ChromeUtils.importESModule(
"resource://gre/modules/PrivateBrowsingUtils.sys.mjs"
);
var { Weave } = ChromeUtils.importESModule(
"resource://services-sync/main.sys.mjs"
);
var { FxAccounts, getFxAccountsSingleton } = ChromeUtils.import(
"resource://gre/modules/FxAccounts.jsm"
);
var fxAccounts = getFxAccountsSingleton();
XPCOMUtils.defineLazyServiceGetters(this, {
gApplicationUpdateService: [
"@mozilla.org/updates/update-service;1",
"nsIApplicationUpdateService",
],
listManager: [
"@mozilla.org/url-classifier/listmanager;1",
"nsIUrlListManager",
],
gHandlerService: [
"@mozilla.org/uriloader/handler-service;1",
"nsIHandlerService",
],
gMIMEService: ["@mozilla.org/mime;1", "nsIMIMEService"],
});
ChromeUtils.defineESModuleGetters(this, {
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
ContextualIdentityService:
"resource://gre/modules/ContextualIdentityService.sys.mjs",
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs",
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
UIState: "resource://services-sync/UIState.sys.mjs",
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
UrlbarProviderQuickActions:
"resource:///modules/UrlbarProviderQuickActions.sys.mjs",
UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
});
XPCOMUtils.defineLazyModuleGetters(this, {
AMTelemetry: "resource://gre/modules/AddonManager.jsm",
DownloadUtils: "resource://gre/modules/DownloadUtils.jsm",
ExtensionPreferencesManager:
"resource://gre/modules/ExtensionPreferencesManager.jsm",
ExtensionSettingsStore: "resource://gre/modules/ExtensionSettingsStore.jsm",
FeatureGate: "resource://featuregates/FeatureGate.jsm",
FirefoxRelay: "resource://gre/modules/FirefoxRelay.jsm",
HomePage: "resource:///modules/HomePage.jsm",
LangPackMatcher: "resource://gre/modules/LangPackMatcher.jsm",
LoginHelper: "resource://gre/modules/LoginHelper.jsm",
NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
SelectionChangedMenulist: "resource:///modules/SelectionChangedMenulist.jsm",
SiteDataManager: "resource:///modules/SiteDataManager.jsm",
TransientPrefs: "resource:///modules/TransientPrefs.jsm",
});
XPCOMUtils.defineLazyGetter(this, "gSubDialog", function() {
const { SubDialogManager } = ChromeUtils.importESModule(
"resource://gre/modules/SubDialog.sys.mjs"
);
return new SubDialogManager({
dialogStack: document.getElementById("dialogStack"),
dialogTemplate: document.getElementById("dialogTemplate"),
dialogOptions: {
styleSheets: [
"chrome://browser/skin/preferences/dialog.css",
"chrome://browser/skin/preferences/preferences.css",
],
resizeCallback: async ({ title, frame }) => {
// Search within main document and highlight matched keyword.
await gSearchResultsPane.searchWithinNode(
title,
gSearchResultsPane.query
);
// Search within sub-dialog document and highlight matched keyword.
await gSearchResultsPane.searchWithinNode(
frame.contentDocument.firstElementChild,
gSearchResultsPane.query
);
// Creating tooltips for all the instances found
for (let node of gSearchResultsPane.listSearchTooltips) {
if (!node.tooltipNode) {
gSearchResultsPane.createSearchTooltip(
node,
gSearchResultsPane.query
);
}
}
},
},
});
});
var gLastCategory = { category: undefined, subcategory: undefined };
const gXULDOMParser = new DOMParser();
var gCategoryModules = new Map();
var gCategoryInits = new Map();
function init_category_if_required(category) {
let categoryInfo = gCategoryInits.get(category);
if (!categoryInfo) {
throw new Error(
"Unknown in-content prefs category! Can't init " + category
);
}
if (categoryInfo.inited) {
return null;
}
return categoryInfo.init();
}
function register_module(categoryName, categoryObject) {
gCategoryModules.set(categoryName, categoryObject);
gCategoryInits.set(categoryName, {
inited: false,
async init() {
let startTime = performance.now();
let template = document.getElementById("template-" + categoryName);
if (template) {
// Replace the template element with the nodes inside of it.
let frag = template.content;
await document.l10n.translateFragment(frag);
// Actually insert them into the DOM.
document.l10n.pauseObserving();
template.replaceWith(frag);
document.l10n.resumeObserving();
// We need to queue an update again because the previous update might
// have happened while we awaited on translateFragment.
Preferences.queueUpdateOfAllElements();
}
categoryObject.init();
this.inited = true;
ChromeUtils.addProfilerMarker(
"Preferences",
{ startTime },
categoryName + " init"
);
},
});
}
document.addEventListener("DOMContentLoaded", init_all, { once: true });
function init_all() {
Preferences.forceEnableInstantApply();
// Asks Preferences to queue an update of the attribute values of
// the entire document.
Preferences.queueUpdateOfAllElements();
Services.telemetry.setEventRecordingEnabled("aboutpreferences", true);
register_module("paneGeneral", gMainPane);
register_module("paneHome", gHomePane);
register_module("paneSearch", gSearchPane);
register_module("panePrivacy", gPrivacyPane);
register_module("paneContainers", gContainersPane);
if (Services.prefs.getBoolPref("browser.preferences.experimental")) {
// Set hidden based on previous load's hidden value.
document.getElementById(
"category-experimental"
).hidden = Services.prefs.getBoolPref(
"browser.preferences.experimental.hidden",
false
);
register_module("paneExperimental", gExperimentalPane);
}
NimbusFeatures.moreFromMozilla.recordExposureEvent({ once: true });
if (NimbusFeatures.moreFromMozilla.getVariable("enabled")) {
document.getElementById("category-more-from-mozilla").hidden = false;
gMoreFromMozillaPane.option = NimbusFeatures.moreFromMozilla.getVariable(
"template"
);
register_module("paneMoreFromMozilla", gMoreFromMozillaPane);
}
// The Sync category needs to be the last of the "real" categories
// registered and inititalized since many tests wait for the
// "sync-pane-loaded" observer notification before starting the test.
if (Services.prefs.getBoolPref("identity.fxaccounts.enabled")) {
document.getElementById("category-sync").hidden = false;
register_module("paneSync", gSyncPane);
}
register_module("paneSearchResults", gSearchResultsPane);
gSearchResultsPane.init();
gMainPane.preInit();
let categories = document.getElementById("categories");
categories.addEventListener("select", event => gotoPref(event.target.value));
document.documentElement.addEventListener("keydown", function(event) {
if (event.keyCode == KeyEvent.DOM_VK_TAB) {
categories.setAttribute("keyboard-navigation", "true");
}
});
categories.addEventListener("mousedown", function() {
this.removeAttribute("keyboard-navigation");
});
maybeDisplayPoliciesNotice();
window.addEventListener("hashchange", onHashChange);
document.getElementById("focusSearch1").addEventListener("command", () => {
gSearchResultsPane.searchInput.focus();
});
gotoPref().then(() => {
let helpButton = document.getElementById("helpButton");
let helpUrl =
Services.urlFormatter.formatURLPref("app.support.baseURL") +
"preferences";
helpButton.setAttribute("href", helpUrl);
document.getElementById("addonsButton").addEventListener("click", e => {
e.preventDefault();
if (e.button >= 2) {
// Ignore right clicks.
return;
}
let mainWindow = window.browsingContext.topChromeWindow;
mainWindow.BrowserOpenAddonsMgr();
AMTelemetry.recordLinkEvent({
object: "aboutPreferences",
value: "about:addons",
});
});
document.dispatchEvent(
new CustomEvent("Initialized", {
bubbles: true,
cancelable: true,
})
);
});
}
function telemetryBucketForCategory(category) {
category = category.toLowerCase();
switch (category) {
case "containers":
case "general":
case "home":
case "privacy":
case "search":
case "sync":
case "searchresults":
return category;
default:
return "unknown";
}
}
function onHashChange() {
gotoPref(null, "hash");
}
async function gotoPref(
aCategory,
aShowReason = aCategory ? "click" : "initial"
) {
let categories = document.getElementById("categories");
const kDefaultCategoryInternalName = "paneGeneral";
const kDefaultCategory = "general";
let hash = document.location.hash;
let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
let breakIndex = category.indexOf("-");
// Subcategories allow for selecting smaller sections of the preferences
// until proper search support is enabled (bug 1353954).
let subcategory = breakIndex != -1 && category.substring(breakIndex + 1);
if (subcategory) {
category = category.substring(0, breakIndex);
}
category = friendlyPrefCategoryNameToInternalName(category);
if (category != "paneSearchResults") {
gSearchResultsPane.query = null;
gSearchResultsPane.searchInput.value = "";
gSearchResultsPane.removeAllSearchIndicators(window, true);
} else if (!gSearchResultsPane.searchInput.value) {
// Something tried to send us to the search results pane without
// a query string. Default to the General pane instead.
category = kDefaultCategoryInternalName;
document.location.hash = kDefaultCategory;
gSearchResultsPane.query = null;
}
// Updating the hash (below) or changing the selected category
// will re-enter gotoPref.
if (gLastCategory.category == category && !subcategory) {
return;
}
let item;
if (category != "paneSearchResults") {
// Hide second level headers in normal view
for (let element of document.querySelectorAll(".search-header")) {
element.hidden = true;
}
item = categories.querySelector(".category[value=" + category + "]");
if (!item || item.hidden) {
category = kDefaultCategoryInternalName;
item = categories.querySelector(".category[value=" + category + "]");
}
}
if (
gLastCategory.category ||
category != kDefaultCategoryInternalName ||
subcategory
) {
let friendlyName = internalPrefCategoryNameToFriendlyName(category);
// Overwrite the hash, unless there is no hash and we're switching to the
// default category, e.g. by using the 'back' button after navigating to
// a different category.
if (
!(!document.location.hash && category == kDefaultCategoryInternalName)
) {
document.location.hash = friendlyName;
}
}
// Need to set the gLastCategory before setting categories.selectedItem since
// the categories 'select' event will re-enter the gotoPref codepath.
gLastCategory.category = category;
gLastCategory.subcategory = subcategory;
if (item) {
categories.selectedItem = item;
} else {
categories.clearSelection();
}
window.history.replaceState(category, document.title);
try {
await init_category_if_required(category);
} catch (ex) {
console.error(
new Error(
"Error initializing preference category " + category + ": " + ex
)
);
throw ex;
}
// Bail out of this goToPref if the category
// or subcategory changed during async operation.
if (
gLastCategory.category !== category ||
gLastCategory.subcategory !== subcategory
) {
return;
}
search(category, "data-category");
if (aShowReason != "initial") {
document.querySelector(".main-content").scrollTop = 0;
}
// Check to see if the category module wants to do any special
// handling of the subcategory - for example, opening a SubDialog.
//
// If not, just do a normal spotlight on the subcategory.
let categoryModule = gCategoryModules.get(category);
if (!categoryModule.handleSubcategory?.(subcategory)) {
spotlight(subcategory, category);
}
// Record which category is shown
Services.telemetry.recordEvent(
"aboutpreferences",
"show",
aShowReason,
category
);
}
function search(aQuery, aAttribute) {
let mainPrefPane = document.getElementById("mainPrefPane");
let elements = mainPrefPane.children;
for (let element of elements) {
// If the "data-hidden-from-search" is "true", the
// element will not get considered during search.
if (
element.getAttribute("data-hidden-from-search") != "true" ||
element.getAttribute("data-subpanel") == "true"
) {
let attributeValue = element.getAttribute(aAttribute);
if (attributeValue == aQuery) {
element.hidden = false;
} else {
element.hidden = true;
}
} else if (
element.getAttribute("data-hidden-from-search") == "true" &&
!element.hidden
) {
element.hidden = true;
}
element.classList.remove("visually-hidden");
}
let keysets = mainPrefPane.getElementsByTagName("keyset");
for (let element of keysets) {
let attributeValue = element.getAttribute(aAttribute);
if (attributeValue == aQuery) {
element.removeAttribute("disabled");
} else {
element.setAttribute("disabled", true);
}
}
}
async function spotlight(subcategory, category) {
let highlightedElements = document.querySelectorAll(".spotlight");
if (highlightedElements.length) {
for (let element of highlightedElements) {
element.classList.remove("spotlight");
}
}
if (subcategory) {
scrollAndHighlight(subcategory, category);
}
}
async function scrollAndHighlight(subcategory, category) {
let element = document.querySelector(`[data-subcategory="${subcategory}"]`);
if (!element) {
return;
}
let header = getClosestDisplayedHeader(element);
scrollContentTo(header);
element.classList.add("spotlight");
}
/**
* If there is no visible second level header it will return first level header,
* otherwise return second level header.
* @returns {Element} - The closest displayed header.
*/
function getClosestDisplayedHeader(element) {
let header = element.closest("groupbox");
let searchHeader = header.querySelector(".search-header");
if (
searchHeader &&
searchHeader.hidden &&
header.previousElementSibling.classList.contains("subcategory")
) {
header = header.previousElementSibling;
}
return header;
}
function scrollContentTo(element) {
const STICKY_CONTAINER_HEIGHT = document.querySelector(".sticky-container")
.clientHeight;
let mainContent = document.querySelector(".main-content");
let top = element.getBoundingClientRect().top - STICKY_CONTAINER_HEIGHT;
mainContent.scroll({
top,
behavior: "smooth",
});
}
function friendlyPrefCategoryNameToInternalName(aName) {
if (aName.startsWith("pane")) {
return aName;
}
return "pane" + aName.substring(0, 1).toUpperCase() + aName.substr(1);
}
// This function is duplicated inside of utilityOverlay.js's openPreferences.
function internalPrefCategoryNameToFriendlyName(aName) {
return (aName || "").replace(/^pane./, function(toReplace) {
return toReplace[4].toLowerCase();
});
}
// Put up a confirm dialog with "ok to restart", "revert without restarting"
// and "restart later" buttons and returns the index of the button chosen.
// We can choose not to display the "restart later", or "revert" buttons,
// altough the later still lets us revert by using the escape key.
//
// The constants are useful to interpret the return value of the function.
const CONFIRM_RESTART_PROMPT_RESTART_NOW = 0;
const CONFIRM_RESTART_PROMPT_CANCEL = 1;
const CONFIRM_RESTART_PROMPT_RESTART_LATER = 2;
async function confirmRestartPrompt(
aRestartToEnable,
aDefaultButtonIndex,
aWantRevertAsCancelButton,
aWantRestartLaterButton
) {
let [
msg,
title,
restartButtonText,
noRestartButtonText,
restartLaterButtonText,
] = await document.l10n.formatValues([
{
id: aRestartToEnable
? "feature-enable-requires-restart"
: "feature-disable-requires-restart",
},
{ id: "should-restart-title" },
{ id: "should-restart-ok" },
{ id: "cancel-no-restart-button" },
{ id: "restart-later" },
]);
// Set up the first (index 0) button:
let buttonFlags =
Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING;
// Set up the second (index 1) button:
if (aWantRevertAsCancelButton) {
buttonFlags +=
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_IS_STRING;
} else {
noRestartButtonText = null;
buttonFlags +=
Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL;
}
// Set up the third (index 2) button:
if (aWantRestartLaterButton) {
buttonFlags +=
Services.prompt.BUTTON_POS_2 * Services.prompt.BUTTON_TITLE_IS_STRING;
} else {
restartLaterButtonText = null;
}
switch (aDefaultButtonIndex) {
case 0:
buttonFlags += Services.prompt.BUTTON_POS_0_DEFAULT;
break;
case 1:
buttonFlags += Services.prompt.BUTTON_POS_1_DEFAULT;
break;
case 2:
buttonFlags += Services.prompt.BUTTON_POS_2_DEFAULT;
break;
default:
break;
}
let buttonIndex = Services.prompt.confirmEx(
window,
title,
msg,
buttonFlags,
restartButtonText,
noRestartButtonText,
restartLaterButtonText,
null,
{}
);
// If we have the second confirmation dialog for restart, see if the user
// cancels out at that point.
if (buttonIndex == CONFIRM_RESTART_PROMPT_RESTART_NOW) {
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].createInstance(
Ci.nsISupportsPRBool
);
Services.obs.notifyObservers(
cancelQuit,
"quit-application-requested",
"restart"
);
if (cancelQuit.data) {
buttonIndex = CONFIRM_RESTART_PROMPT_CANCEL;
}
}
return buttonIndex;
}
// This function is used to append search keywords found
// in the related subdialog to the button that will activate the subdialog.
function appendSearchKeywords(aId, keywords) {
let element = document.getElementById(aId);
let searchKeywords = element.getAttribute("searchkeywords");
if (searchKeywords) {
keywords.push(searchKeywords);
}
element.setAttribute("searchkeywords", keywords.join(" "));
}
async function ensureScrollPadding() {
let stickyContainer = document.querySelector(".sticky-container");
let height = await window.browsingContext.topChromeWindow
.promiseDocumentFlushed(() => stickyContainer.clientHeight)
.catch(err => Cu.reportError); // Can reject if the window goes away.
// Make it a bit more, to ensure focus rectangles etc. don't get cut off.
// This being 8px causes us to end up with 90px if the policies container
// is not visible (the common case), which matches the CSS and thus won't
// cause a style change, repaint, or other changes.
height += 8;
stickyContainer
.closest(".main-content")
.style.setProperty("scroll-padding-top", height + "px");
}
function maybeDisplayPoliciesNotice() {
if (Services.policies.status == Services.policies.ACTIVE) {
document.getElementById("policies-container").removeAttribute("hidden");
ensureScrollPadding();
}
}