Without this, in my testing with the previous patch, the scroll alignment on the initial load of the settings page is incorrect, with the top of the section being cut off. As this is the situation we care about (first load after a browser update), fixing it seemed important. I spent some time trying to work out why layout is changing - presumably items above the "Browser Layout" section are either appearing or disappearing in a way that races with the scroll. I could never reproduce on any loads other than the first one. But then I realized the code here was overly complex and that it would probably be nicer for items to appear in the middle of the view rather than the top anyway. Yes, this means that sometimes the relevant header is _still_ not _exactly_ in the middle, but that seems much less important. Differential Revision: https://phabricator.services.mozilla.com/D241628
624 lines
20 KiB
JavaScript
624 lines
20 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 translations.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.importESModule(
|
|
"resource://gre/modules/FxAccounts.sys.mjs"
|
|
);
|
|
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"],
|
|
});
|
|
|
|
if (Cc["@mozilla.org/gio-service;1"]) {
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
this,
|
|
"gGIOService",
|
|
"@mozilla.org/gio-service;1",
|
|
"nsIGIOService"
|
|
);
|
|
} else {
|
|
this.gGIOService = null;
|
|
}
|
|
|
|
ChromeUtils.defineESModuleGetters(this, {
|
|
BrowserUtils: "resource://gre/modules/BrowserUtils.sys.mjs",
|
|
ContextualIdentityService:
|
|
"resource://gre/modules/ContextualIdentityService.sys.mjs",
|
|
DownloadUtils: "resource://gre/modules/DownloadUtils.sys.mjs",
|
|
ExtensionPreferencesManager:
|
|
"resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
|
|
ExtensionSettingsStore:
|
|
"resource://gre/modules/ExtensionSettingsStore.sys.mjs",
|
|
FeatureGate: "resource://featuregates/FeatureGate.sys.mjs",
|
|
FileUtils: "resource://gre/modules/FileUtils.sys.mjs",
|
|
FirefoxRelay: "resource://gre/modules/FirefoxRelay.sys.mjs",
|
|
HomePage: "resource:///modules/HomePage.sys.mjs",
|
|
LangPackMatcher: "resource://gre/modules/LangPackMatcher.sys.mjs",
|
|
LoginHelper: "resource://gre/modules/LoginHelper.sys.mjs",
|
|
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
|
OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
|
|
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
|
|
QuickSuggest: "resource:///modules/QuickSuggest.sys.mjs",
|
|
Region: "resource://gre/modules/Region.sys.mjs",
|
|
SelectionChangedMenulist:
|
|
"resource:///modules/SelectionChangedMenulist.sys.mjs",
|
|
ShortcutUtils: "resource://gre/modules/ShortcutUtils.sys.mjs",
|
|
SiteDataManager: "resource:///modules/SiteDataManager.sys.mjs",
|
|
TransientPrefs: "resource:///modules/TransientPrefs.sys.mjs",
|
|
UIState: "resource://services-sync/UIState.sys.mjs",
|
|
UpdateUtils: "resource://gre/modules/UpdateUtils.sys.mjs",
|
|
UrlbarPrefs: "resource:///modules/UrlbarPrefs.sys.mjs",
|
|
UrlbarUtils: "resource:///modules/UrlbarUtils.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.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 register_module(categoryName, categoryObject) {
|
|
gCategoryModules.set(categoryName, categoryObject);
|
|
gCategoryInits.set(categoryName, {
|
|
_initted: false,
|
|
init() {
|
|
let startTime = performance.now();
|
|
if (this._initted) {
|
|
return;
|
|
}
|
|
this._initted = true;
|
|
let template = document.getElementById("template-" + categoryName);
|
|
if (template) {
|
|
// Replace the template element with the nodes inside of it.
|
|
template.replaceWith(template.content);
|
|
|
|
// We've inserted elements that rely on 'preference' attributes.
|
|
// So we need to update those by reading from the prefs.
|
|
// The bindings will do this using idle dispatch and avoid
|
|
// repeated runs if called multiple times before the task runs.
|
|
Preferences.queueUpdateOfAllElements();
|
|
}
|
|
|
|
categoryObject.init();
|
|
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();
|
|
|
|
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.translations.newSettingsUI.enable")) {
|
|
register_module("paneTranslations", gTranslationsPane);
|
|
}
|
|
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(() => {
|
|
document.getElementById("addonsButton").addEventListener("click", e => {
|
|
e.preventDefault();
|
|
if (e.button >= 2) {
|
|
// Ignore right clicks.
|
|
return;
|
|
}
|
|
let mainWindow = window.browsingContext.topChromeWindow;
|
|
mainWindow.BrowserAddonUI.openAddonsMgr();
|
|
});
|
|
|
|
document.dispatchEvent(
|
|
new CustomEvent("Initialized", {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
})
|
|
);
|
|
});
|
|
}
|
|
|
|
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);
|
|
|
|
let categoryInfo = gCategoryInits.get(category);
|
|
if (!categoryInfo) {
|
|
let err = new Error(
|
|
"Unknown in-content prefs category! Can't init " + category
|
|
);
|
|
console.error(err);
|
|
throw err;
|
|
}
|
|
categoryInfo.init();
|
|
|
|
if (document.hasPendingL10nMutations) {
|
|
await new Promise(r =>
|
|
document.addEventListener("L10nMutationsFinished", r, { once: true })
|
|
);
|
|
// 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
|
|
Glean.aboutpreferences["show" + aShowReason].record({ value: category });
|
|
|
|
document.dispatchEvent(
|
|
new CustomEvent("paneshown", {
|
|
bubbles: true,
|
|
cancelable: true,
|
|
detail: {
|
|
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");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
function scrollAndHighlight(subcategory) {
|
|
let element = document.querySelector(`[data-subcategory="${subcategory}"]`);
|
|
if (!element) {
|
|
return;
|
|
}
|
|
let header = getClosestDisplayedHeader(element);
|
|
|
|
header.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
});
|
|
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 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 button = await Services.prompt.asyncConfirmEx(
|
|
window.browsingContext,
|
|
Ci.nsIPrompt.MODAL_TYPE_CONTENT,
|
|
title,
|
|
msg,
|
|
buttonFlags,
|
|
restartButtonText,
|
|
noRestartButtonText,
|
|
restartLaterButtonText,
|
|
null,
|
|
{}
|
|
);
|
|
|
|
let buttonIndex = button.get("buttonNumClicked");
|
|
|
|
// 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(console.error); // 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();
|
|
}
|