/* -*- indent-tabs-mode: nil; js-indent-level: 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/. */
// This file is loaded into the browser window scope. /* eslint-env mozilla/browser-window */
var { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs"
);
handleEvent(e) { if (e.type == "click") {
e.preventDefault();
window.openTrustedLinkIn(this.href, "tab", { // Make sure the newly open tab is going to be focused, independently // from general user prefs.
forceForeground: true,
});
}
}
},
{ extends: "a" }
);
this.#clearChildElements(); // Re-enable "Allow" button if it was disabled by a previous request with // isUserScriptsRequest=true. this.#setAllowButtonEnabled(true);
if (strings.text) {
textEl.textContent = strings.text; // By default, multiline strings don't get formatted properly. These // are presently only used in site permission add-ons, so we treat it // as a special case to avoid unintended effects on other things. if (strings.text.includes("\n\n")) {
textEl.classList.add("addon-webext-perm-text-multiline");
}
textEl.hidden = false;
}
if (strings.listIntro) {
introEl.textContent = strings.listIntro;
introEl.hidden = false;
}
// Return earlier if there are no permissions to list. if (this.hasNoPermissions) { return;
}
// If there are multiple permissions entries to be shown, // add to the list element one entry for each granted permission // (and one for the private browsing checkbox, if it should // be shown) and return earlier. if (this.hasMultiplePermissionsEntries) { for (let [idx, msg] of strings.msgs.entries()) {
let item = doc.createElementNS(HTML_NS, "li");
item.classList.add("webext-perm-granted"); if ( this.hasFullDomainsList && this.#isFullDomainsListEntryIndex(idx)
) {
item.append(this.#createFullDomainsListFragment(msg));
} else {
item.textContent = msg;
}
permsListEl.appendChild(item);
} if (showIncognitoCheckbox) {
let item = doc.createElementNS(HTML_NS, "li");
item.classList.add( "webext-perm-optional", "webext-perm-privatebrowsing"
);
item.appendChild(this.#createPrivateBrowsingCheckbox());
permsListEl.appendChild(item);
}
permsListEl.hidden = false; return;
}
if (isUserScriptsRequest) { // The "userScripts" permission cannot be granted until the user has // confirmed again in the notification's content, as described at // https://bugzilla.mozilla.org/show_bug.cgi?id=1917000#c1
let { checkboxEl, warningEl } = this.#createUserScriptsPermissionItems( // "userScripts" can only be requested with "permissions.request()", // which enforces that it is the only permission in the request.
strings.msgs[0]
);
// Render a single permission entry, which will be either: // - an entry for the private browsing checkbox // - or single granted permission entry. if (showIncognitoCheckbox) {
permsSingleEl.appendChild(this.#createPrivateBrowsingCheckbox());
permsSingleEl.hidden = false;
permsSingleEl.classList.add( "webext-perm-optional", "webext-perm-privatebrowsing"
); return;
}
// Enforce max-height and ensure the domains list is // scrollable when there are more than 5 domains. if (this.domainsSet.size > 5) {
domainsList.classList.add("scrollable-domains-list");
}
for (const domain of this.domainsSet) {
let domainItem = doc.createElementNS(HTML_NS, "li");
domainItem.textContent = domain;
domainsList.appendChild(domainItem);
} const { DocumentFragment } = this.ownerGlobal; const fragment = new DocumentFragment();
fragment.append(label);
fragment.append(domainsList); return fragment;
}
// Clear all changes to the child elements that may have been changed // by a previous call of the render method.
textEl.textContent = "";
textEl.hidden = true;
textEl.classList.remove("addon-webext-perm-text-multiline");
let checkboxEl = doc.createXULElement("checkbox");
checkboxEl.label = userScriptsPermissionMessage;
checkboxEl.checked = false;
checkboxEl.addEventListener("CheckboxStateChange", () => { // The main "Allow" button is disabled until the checkbox is checked. this.#setAllowButtonEnabled(checkboxEl.checked);
});
#setAllowButtonEnabled(allowed) {
let disabled = !allowed; // "mainactiondisabled" mirrors the "disabled" boolean attribute of the // "Allow" button. toggleAttribute("mainactiondisabled", disabled) cannot // be used due to bug 1938481. if (disabled) { this.setAttribute("mainactiondisabled", "true");
} else { this.removeAttribute("mainactiondisabled");
}
// The "mainactiondisabled" attribute may also be toggled by the // PopupNotifications._setNotificationUIState() method, which can be // called as a side effect of toggling a checkbox within the notification // (via PopupNotifications._onCommand). // // To prevent PopupNotifications._setNotificationUIState() from setting // the "mainactiondisabled" attribute to a different state, also set the // "invalidselection" attribute, since _setNotificationUIState() mirrors // its value to "mainactiondisabled". // // TODO bug 1938623: Remove this when a better alternative exists. this.toggleAttribute("invalidselection", disabled);
}
let checkboxEl = doc.createXULElement("checkbox");
checkboxEl.checked = grantPrivateBrowsingAllowed;
checkboxEl.addEventListener("CheckboxStateChange", () => { // NOTE: the popupnotification instances will be reused // and so the callback function is destructured here to // avoid this custom element to prevent it from being // garbage collected. const { onPrivateBrowsingAllowedChanged } = this.notification.options.customElementOptions;
onPrivateBrowsingAllowedChanged?.(checkboxEl.checked);
});
doc.l10n.setAttributes(
checkboxEl, "popup-notification-addon-privatebrowsing-checkbox"
); return checkboxEl;
}
}
);
// Calling updateProgress can sometimes cause this notification to be // removed in the middle of refreshing the notification panel which // makes the panel get refreshed again. Just initialise to the // undetermined state and then schedule a proper check at the next // opportunity this.setProgress(0, -1); this._updateProgressTimeout = setTimeout( this.updateProgress.bind(this),
0
);
}
let delta = now - this.notification.lastUpdate; if (delta < 400 && aProgress < aMaxProgress) { return;
}
// Set min. time delta to avoid division by zero in the upcoming speed calculation
delta = Math.max(delta, 400);
delta /= 1000;
// This algorithm is the same used by the downloads code.
let speed = (aProgress - this.notification.lastProgress) / delta; if (this.notification.speed) {
speed = speed * 0.9 + this.notification.speed * 0.1;
}
// This custom element wraps the messagebar shown in the extensions panel // and used in both ext-browserAction.js and browser-unified-extensions.js
customElements.define( "unified-extensions-item-messagebar-wrapper", classextends HTMLElement {
get extensionPolicy() { return WebExtensionPolicy.getByID(this.extensionId);
}
get extensionName() { returnthis.extensionPolicy?.name;
}
get isSoftBlocked() { returnthis.extensionPolicy?.extension?.isSoftBlocked;
}
async refresh() { if (!this.messagebar) { // Nothing to refresh, the custom element has not been // connected to the DOM yet. return;
} if (!customElements.get("moz-message-bar")) {
document.createElement("moz-message-bar");
await customElements.whenDefined("moz-message-bar");
} const { messagebar } = this; if (this.isSoftBlocked) { const SOFTBLOCK_FLUENTID = "unified-extensions-item-messagebar-softblocked"; if (
messagebar.messageL10nId === SOFTBLOCK_FLUENTID &&
messagebar.messageL10nArgs?.extensionName === this.extensionName
) { // nothing to refresh. return;
}
messagebar.removeAttribute("hidden");
messagebar.setAttribute("type", "warning");
messagebar.messageL10nId = SOFTBLOCK_FLUENTID;
messagebar.messageL10nArgs = {
extensionName: this.extensionName,
};
} else { if (messagebar.hasAttribute("hidden")) { // nothing to refresh. return;
}
messagebar.setAttribute("hidden", "true");
messagebar.messageL10nId = null;
messagebar.messageL10nArgs = null;
}
messagebar.requestUpdate();
}
}
);
// Removes a doorhanger notification if all of the installs it was notifying // about have ended in some way. function removeNotificationOnEnd(notification, installs) {
let count = installs.length;
function maybeRemove(install) {
install.removeListener(this);
if (--count == 0) { // Check that the notification is still showing
let current = PopupNotifications.getNotification(
notification.id,
notification.browser
); if (current === notification) {
notification.remove();
}
}
}
for (let install of installs) {
install.addListener({
onDownloadCancelled: maybeRemove,
onDownloadFailed: maybeRemove,
onInstallFailed: maybeRemove,
onInstallEnded: maybeRemove,
});
}
}
function buildNotificationAction(msg, callback) {
let label = "";
let accessKey = ""; for (let { name, value } of msg.attributes) { switch (name) { case"label":
label = value; break; case"accesskey":
accessKey = value; break;
}
} return { label, accessKey, callback };
}
var gXPInstallObserver = {
pendingInstalls: new WeakMap(),
showInstallConfirmation(browser, installInfo, height = undefined) { // If the confirmation notification is already open cache the installInfo // and the new confirmation will be shown later if (
PopupNotifications.getNotification("addon-install-confirmation", browser)
) {
let pending = this.pendingInstalls.get(browser); if (pending) {
pending.push(installInfo);
} else { this.pendingInstalls.set(browser, [installInfo]);
} return;
}
let showNextConfirmation = () => { // Make sure the browser is still alive. if (!gBrowser.browsers.includes(browser)) { return;
}
let pending = this.pendingInstalls.get(browser); if (pending && pending.length) { this.showInstallConfirmation(browser, pending.shift());
}
};
// If all installs have already been cancelled in some way then just show // the next confirmation if (
installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)
) {
showNextConfirmation(); return;
}
let cancelInstallation = () => { if (installInfo) { for (let install of installInfo.installs) { // The notification may have been closed because the add-ons got // cancelled elsewhere, only try to cancel those that are still // pending install. if (install.state != AddonManager.STATE_CANCELLED) {
install.cancel();
}
}
}
showNextConfirmation();
};
let unsigned = installInfo.installs.filter(
i => i.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING
);
let someUnsigned =
!!unsigned.length && unsigned.length < installInfo.installs.length;
for (let install of installInfo.installs) {
let container = document.createXULElement("hbox");
let name = document.createXULElement("label");
name.setAttribute("value", install.addon.name);
name.setAttribute("class", "addon-install-confirmation-name");
container.appendChild(name);
let msgId;
let notification = document.getElementById( "addon-install-confirmation-notification"
); if (unsigned.length == installInfo.installs.length) { // None of the add-ons are verified
msgId = "addon-confirm-install-unsigned-message";
notification.setAttribute("warning", "true");
options.learnMoreURL += "unsigned-addons";
} elseif (!unsigned.length) { // All add-ons are verified or don't need to be verified
msgId = "addon-confirm-install-message";
notification.removeAttribute("warning");
options.learnMoreURL += "find-and-install-add-ons";
} else { // Some of the add-ons are unverified, the list of names will indicate // which
msgId = "addon-confirm-install-some-unsigned-message";
notification.setAttribute("warning", "true");
options.learnMoreURL += "unsigned-addons";
} const addonCount = installInfo.installs.length; const messageString = lazy.l10n.formatValueSync(msgId, { addonCount });
PopupNotifications.show(
browser, "xpinstall-disabled",
await lazy.l10n.formatValue(msgId),
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
secondaryActions,
options
); break;
} case"addon-install-fullscreen-blocked": { // AddonManager denied installation because we are in DOM fullscreen this.logWarningFullScreenInstallBlocked(); break;
} case"addon-install-webapi-blocked": case"addon-install-policy-blocked": case"addon-install-origin-blocked": { const msgId =
aTopic == "addon-install-policy-blocked"
? "addon-install-domain-blocked-by-policy"
: "xpinstall-prompt";
let messageString = await lazy.l10n.formatValue(msgId); if (Services.policies) {
let extensionSettings = Services.policies.getExtensionSettings("*"); if (
extensionSettings && "blocked_install_message" in extensionSettings
) {
messageString += " " + extensionSettings.blocked_install_message;
}
}
options.removeOnDismissal = true;
options.persistent = false;
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
let popup = PopupNotifications.show(
browser,
aTopic,
messageString,
gUnifiedExtensions.getPopupAnchorID(browser, window), null, null,
options
);
removeNotificationOnEnd(popup, installInfo.installs); break;
} case"addon-install-blocked": { // Dismiss the progress notification. Note that this is bad if // there are multiple simultaneous installs happening, see // bug 1329884 for a longer explanation.
let progressNotification = PopupNotifications.getNotification( "addon-progress",
browser
); if (progressNotification) {
progressNotification.remove();
}
// The informational content differs somewhat for site permission // add-ons. AOM no longer supports installing multiple addons, // so the array handling here is vestigial.
let isSitePermissionAddon = installInfo.installs.every(
({ addon }) => addon?.type === lazy.SITEPERMS_ADDON_TYPE
);
let hasHost = false;
let headerId, msgId; if (isSitePermissionAddon) { // At present, WebMIDI is the only consumer of the site permission // add-on infrastructure, and so we can hard-code a midi string here. // If and when we use it for other things, we'll need to plumb that // information through. See bug 1826747.
headerId = "site-permission-install-first-prompt-midi-header";
msgId = "site-permission-install-first-prompt-midi-message";
} elseif (options.displayURI) { // PopupNotifications.show replaces <> with options.name.
headerId = { id: "xpinstall-prompt-header", args: { host: "<>" } }; // BrowserUIUtils.getLocalizedFragment replaces %1$S with options.name.
msgId = { id: "xpinstall-prompt-message", args: { host: "%1$S" } };
options.name = options.displayURI.displayHost;
hasHost = true;
} else {
headerId = "xpinstall-prompt-header-unknown";
msgId = "xpinstall-prompt-message-unknown";
} const [headerString, msgString] = await lazy.l10n.formatValues([
headerId,
msgId,
]);
// displayURI becomes it's own label, so we unset it for this panel. It will become part of the // messageString above.
let displayURI = options.displayURI;
options.displayURI = undefined;
options.eventCallback = topic => { if (topic !== "showing") { return;
}
let doc = browser.ownerDocument;
let message = doc.getElementById("addon-install-blocked-message"); // We must remove any prior use of this panel message in this window. while (message.firstChild) {
message.firstChild.remove();
}
if (!hasHost) {
message.textContent = msgString;
} else {
let b = doc.createElementNS("http://www.w3.org/1999/xhtml", "b");
b.textContent = options.name;
let fragment = BrowserUIUtils.getLocalizedFragment(
doc,
msgString,
b
);
message.appendChild(fragment);
}
let article = isSitePermissionAddon
? "site-permission-addons"
: "unlisted-extensions-risks";
let learnMore = doc.getElementById("addon-install-blocked-info");
learnMore.setAttribute("support-page", article);
};
Services.telemetry
.getHistogramById("SECURITY_UI")
.add(Ci.nsISecurityUITelemetry.WARNING_ADDON_ASKING_PREVENTED);
const neverAllowCallback = () => {
SitePermissions.setForPrincipal(
browser.contentPrincipal, "install",
SitePermissions.BLOCK
); for (let install of installInfo.installs) { if (install.state != AddonManager.STATE_CANCELLED) {
install.cancel();
}
} if (installInfo.cancel) {
installInfo.cancel();
}
};
const declineActions = [
buildNotificationAction(dontAllowMsg, () => { for (let install of installInfo.installs) { if (install.state != AddonManager.STATE_CANCELLED) {
install.cancel();
}
} if (installInfo.cancel) {
installInfo.cancel();
}
}),
buildNotificationAction(neverAllowMsg, neverAllowCallback),
];
if (isSitePermissionAddon) { // Restrict this to site permission add-ons for now pending a decision // from product about how to approach this for extensions.
declineActions.push(
buildNotificationAction(neverAllowAndReportMsg, () => {
AMTelemetry.recordSuspiciousSiteEvent({ displayURI });
neverAllowCallback();
})
);
}
let popup = PopupNotifications.show(
browser,
aTopic,
headerString,
gUnifiedExtensions.getPopupAnchorID(browser, window),
action,
declineActions,
options
);
removeNotificationOnEnd(popup, installInfo.installs); break;
} case"addon-install-started": { // If all installs have already been downloaded then there is no need to // show the download progress if (
installInfo.installs.every(
aInstall => aInstall.state == AddonManager.STATE_DOWNLOADED
)
) { return;
}
// TODO This isn't terribly ideal for the multiple failure case for (let install of installInfo.installs) {
let host; try {
host = options.displayURI.host;
} catch (e) { // displayURI might be missing or 'host' might throw for non-nsStandardURL nsIURIs.
}
let messageString; if (
install.addon &&
!Services.policies.mayInstallAddon(install.addon)
) {
messageString = lazy.l10n.formatValueSync( "addon-installation-blocked-by-policy",
{ addonName: install.name, addonId: install.addon.id }
);
let extensionSettings = Services.policies.getExtensionSettings(
install.addon.id
); if (
extensionSettings && "blocked_install_message" in extensionSettings
) {
messageString += " " + extensionSettings.blocked_install_message;
}
} else { // TODO bug 1834484: simplify computation of isLocal. const isLocal = !host;
let errorId = ERROR_L10N_IDS.get(install.error)?.[isLocal ? 1 : 0]; const args = {
addonName: install.name,
appVersion: Services.appinfo.version,
}; // TODO: Bug 1846725 - when there is no error ID (which shouldn't // happen but... we never know) we use the "incompatible" error // message for now but we should have a better error message // instead. if (!errorId) {
errorId = "addon-install-error-incompatible";
}
messageString = lazy.l10n.formatValueSync(errorId, args);
}
// Add Learn More link when refusing to install an unsigned add-on if (install.error == AddonManager.ERROR_SIGNEDSTATE_REQUIRED) {
options.learnMoreURL =
Services.urlFormatter.formatURLPref("app.support.baseURL") + "unsigned-addons";
}
// On blocklist-related install failures: // - use "addon-install-failed-blocklist" as the notificationId // (which will use the popupnotification with id // "addon-install-failed-blocklist-notification" defined // in popup-notification.inc) // - add an eventCallback that will take care of filling in the // blocklistURL into the href attribute of the link element // with id "addon-install-failed-blocklist-info" if (isBlocklistError) { const blocklistURL = await install.addon?.getBlocklistURL();
notificationId = `${aTopic}-blocklist`;
options.eventCallback = topic => { if (topic !== "showing") { return;
}
let doc = browser.ownerDocument;
let blocklistURLEl = doc.getElementById( "addon-install-failed-blocklist-info"
); if (blocklistURL) {
blocklistURLEl.setAttribute("href", blocklistURL);
} else {
blocklistURLEl.removeAttribute("href");
}
};
}
// Can't have multiple notifications with the same ID, so stop here. break;
} this._removeProgressNotification(browser); break;
} case"addon-install-confirmation": {
let showNotification = () => {
let height = undefined;
if (PopupNotifications.isPanelOpen) {
let rect = window.windowUtils.getBoundsWithoutFlushing(
document.getElementById("addon-progress-notification")
);
height = rect.height;
}
uninit() { // uninit() can race ahead of init() in some cases, if that happens, // we have no handler to remove. if (!this.initialized) { return;
}
ExtensionsUI.off("change", this.boundUpdate);
},
updateAlerts() {
let sideloaded = ExtensionsUI.sideloaded;
let updates = ExtensionsUI.updates;
let container = PanelUI.addonNotificationContainer;
while (container.firstChild) {
container.firstChild.remove();
}
let items = 0; if (lazy.AMBrowserExtensionsImport.canCompleteOrCancelInstalls) { this._createAddonButton("webext-imported-addons", null, () => {
lazy.AMBrowserExtensionsImport.completeInstalls();
});
items++;
}
for (let update of updates) { if (++items > 4) { break;
} this._createAddonButton( "webext-perms-update-menu-item",
update.addon,
() => {
ExtensionsUI.showUpdate(gBrowser, update);
}
);
}
for (let addon of sideloaded) { if (++items > 4) { break;
} this._createAddonButton("webext-perms-sideload-menu-item", addon, () => { // We need to hide the main menu manually because the toolbarbutton is // removed immediately while processing this event, and PanelUI is // unable to identify which panel should be closed automatically.
PanelUI.hide();
ExtensionsUI.showSideloaded(gBrowser, addon);
});
}
},
};
var BrowserAddonUI = {
async promptRemoveExtension(addon) {
let { name } = addon;
let [title, btnTitle] = await lazy.l10n.formatValues([
{ id: "addon-removal-title", args: { name } },
{ id: "addon-removal-button" },
]);
// Enable abuse report checkbox in the remove extension dialog, // if enabled by the about:config prefs and the addon type // is currently supported.
let checkboxMessage = null; if (
gAddonAbuseReportEnabled &&
["extension", "theme"].includes(addon.type)
) {
checkboxMessage = await lazy.l10n.formatValue( "addon-removal-abuse-report-checkbox"
);
}
let checkboxState = { value: false };
let result = confirmEx(
window,
title, null,
btnFlags,
btnTitle, /* button1 */ null, /* button2 */ null,
checkboxMessage,
checkboxState
);
return { remove: result === 0, report: checkboxState.value };
},
async reportAddon(addonId, _reportEntryPoint) {
let addon = addonId && (await AddonManager.getAddonByID(addonId)); if (!addon) { return;
}
const amoUrl = lazy.AbuseReporter.getAMOFormURL({ addonId });
window.openTrustedLinkIn(amoUrl, "tab", { // Make sure the newly open tab is going to be focused, independently // from general user prefs.
forceForeground: true,
});
},
async removeAddon(addonId) {
let addon = addonId && (await AddonManager.getAddonByID(addonId)); if (!addon || !(addon.permissions & AddonManager.PERM_CAN_UNINSTALL)) { return;
}
let { remove, report } = await this.promptRemoveExtension(addon);
if (remove) { // Leave the extension in pending uninstall if we are also reporting the // add-on.
await addon.uninstall(report);
if (report) {
await this.reportAddon(addon.id, "uninstall");
}
}
},
async manageAddon(addonId) {
let addon = addonId && (await AddonManager.getAddonByID(addonId)); if (!addon) { return;
}
/** * Open about:addons page by given view id. * @param {String} aView * View id of page that will open. * e.g. "addons://discover/" * @param {Object} options * { * selectTabByViewId: If true, if there is the tab opening page having * same view id, select the tab. Else if the current * page is blank, load on it. Otherwise, open a new * tab, then load on it. * If false, if there is the tab opening * about:addoons page, select the tab and load page * for view id on it. Otherwise, leave the loading * behavior to switchToTabHavingURI(). * If no options, handles as false. * } * @returns {Promise} When the Promise resolves, returns window object loaded the * view id.
*/
openAddonsMgr(aView, { selectTabByViewId = false } = {}) { returnnew Promise(resolve => {
let emWindow;
let browserWindow;
const receivePong = function (aSubject) { const browserWin = aSubject.browsingContext.topChromeWindow; if (!emWindow || browserWin == window /* favor the current window */) { if (
selectTabByViewId &&
aSubject.gViewController.currentViewId !== aView
) { return;
}
if (emWindow) { if (aView && !selectTabByViewId) {
emWindow.loadView(aView);
}
let tab = browserWindow.gBrowser.getTabForBrowser(
emWindow.docShell.chromeEventHandler
);
browserWindow.gBrowser.selectedTab = tab;
emWindow.focus();
resolve(emWindow); return;
}
if (selectTabByViewId) { const target = isBlankPageURL(gBrowser.currentURI.spec)
? "current"
: "tab";
openTrustedLinkIn("about:addons", target);
} else { // This must be a new load, else the ping/pong would have // found the window above.
switchToTabHavingURI("about:addons", true);
}
// We must declare `gUnifiedExtensions` using `var` below to avoid a // "redeclaration" syntax error. var gUnifiedExtensions = {
_initialized: false,
// We use a `<deck>` in the extension items to show/hide messages below each // extension name. We have a default message for origin controls, and // optionally a second message shown on hover, which describes the action // (when clicking on the action button). We have another message shown when // the menu button is hovered/focused. The constants below define the indexes // of each message in the `<deck>`.
MESSAGE_DECK_INDEX_DEFAULT: 0,
MESSAGE_DECK_INDEX_HOVER: 1,
MESSAGE_DECK_INDEX_MENU_HOVER: 2,
init() { if (this._initialized) { return;
}
this._button = document.getElementById("unified-extensions-button"); // TODO: Bug 1778684 - Auto-hide button when there is no active extension. this._button.hidden = false;
onLocationChange(browser, webProgress, _request, _uri, flags) { // Only update on top-level cross-document navigations in the selected tab. if (
webProgress.isTopLevel &&
browser === gBrowser.selectedBrowser &&
!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)
) { this.updateAttention();
}
},
// Update the attention indicator for the whole unified extensions button.
updateAttention() {
let permissionsAttention = false;
let quarantinedAttention = false;
let blocklistAttention = AddonManager.shouldShowBlocklistAttention();
// Computing the OriginControls state for all active extensions is potentially // more expensive, and so we don't compute it if we have already determined that // there is a blocklist attention to be shown. if (!blocklistAttention) { for (let policy of this.getActivePolicies()) {
let widget = this.browserActionFor(policy)?.widget;
// Only show for extensions which are not already visible in the toolbar. if (!widget || widget.areaType !== CustomizableUI.TYPE_TOOLBAR) { if (lazy.OriginControls.getAttentionState(policy, window).attention) {
permissionsAttention = true; break;
}
}
}
// If the domain is quarantined and we have extensions not allowed, we'll // show a notification in the panel so we want to let the user know about // it.
quarantinedAttention = this._shouldShowQuarantinedNotification();
}
this.button.toggleAttribute( "attention",
quarantinedAttention || permissionsAttention || blocklistAttention
);
let msgId = permissionsAttention
? "unified-extensions-button-permissions-needed"
: "unified-extensions-button"; // Quarantined state takes precedence over anything else. if (quarantinedAttention) {
msgId = "unified-extensions-button-quarantined";
} // blocklistAttention state takes precedence over the other ones // because it is dismissible and, once dismissed, the tooltip will // show one of the other messages if appropriate. if (blocklistAttention) {
msgId = "unified-extensions-button-blocklisted";
} this.button.ownerDocument.l10n.setAttributes(this.button, msgId);
},
/** * Gets a list of active WebExtensionPolicy instances of type "extension", * sorted alphabetically based on add-on's names. Optionally, filter out * extensions with browser action. * * @param {bool} all When set to true (the default), return the list of all * active policies, including the ones that have a * browser action. Otherwise, extensions with browser * action are filtered out. * @returns {Array<WebExtensionPolicy>} An array of active policies.
*/
getActivePolicies(all = true) {
let policies = WebExtensionPolicy.getActiveExtensions();
policies = policies.filter(policy => {
let { extension } = policy; if (!policy.active || extension?.type !== "extension") { returnfalse;
}
// Ignore hidden and extensions that cannot access the current window // (because of PB mode when we are in a private window), since users // cannot do anything with those extensions anyway. if (extension.isHidden || !policy.canAccessWindow(window)) { returnfalse;
}
return all || !extension.hasBrowserActionUI;
});
policies.sort((a, b) => a.name.localeCompare(b.name)); return policies;
},
/** * Returns true when there are active extensions listed/shown in the unified * extensions panel, and false otherwise (e.g. when extensions are pinned in * the toolbar OR there are 0 active extensions). * * @returns {boolean} Whether there are extensions listed in the panel.
*/
hasExtensionsInPanel() { const policies = this.getActivePolicies();
onPanelViewShowing(panelview) { const list = panelview.querySelector(".unified-extensions-list"); // Only add extensions that do not have a browser action in this list since // the extensions with browser action have CUI widgets and will appear in // the panel (or toolbar) via the CUI mechanism. for (const policy of this.getActivePolicies(/* all */ false)) { const item = document.createElement("unified-extensions-item");
item.setExtension(policy.extension);
list.appendChild(item);
}
onPanelViewHiding(panelview) { if (window.closed) { return;
} const list = panelview.querySelector(".unified-extensions-list"); while (list.lastChild) {
list.lastChild.remove();
} // If temporary access was granted, (maybe) clear attention indicator.
requestAnimationFrame(() => this.updateAttention());
},
onToolbarVisibilityChange(toolbarId, isVisible) { // A list of extension widget IDs (possibly empty).
let widgetIDs;
try {
widgetIDs = CustomizableUI.getWidgetIdsInArea(toolbarId).filter(
CustomizableUI.isWebExtensionWidget
);
} catch { // Do nothing if the area does not exist for some reason. return;
}
// The list of overflowed extensions in the extensions panel. const overflowedExtensionsList = this.panel.querySelector( "#overflowed-extensions-list"
);
// We are going to move all the extension widgets via DOM manipulation // *only* so that it looks like these widgets have moved (and users will // see that) but CUI still thinks the widgets haven't been moved. // // We can move the extension widgets either from the toolbar to the // extensions panel OR the other way around (when the toolbar becomes // visible again). for (const widgetID of widgetIDs) { const widget = CustomizableUI.getWidget(widgetID); if (!widget) { continue;
}
if (isVisible) { this._maybeMoveWidgetNodeBack(widget.id);
} else { const { node } = widget.forWindow(window); // Artificially overflow the extension widget in the extensions panel // when the toolbar is hidden.
node.setAttribute("overflowedItem", true);
node.setAttribute("artificallyOverflowed", true); // This attribute forces browser action popups to be anchored to the // extensions button.
node.setAttribute("cui-anchorid", "unified-extensions-button");
overflowedExtensionsList.appendChild(node);
// We only want to move back widget nodes that have been manually moved // previously via `onToolbarVisibilityChange()`. const { node } = widget.forWindow(window); if (!node.hasAttribute("artificallyOverflowed")) { return;
}
const { area, position } = CustomizableUI.getPlacementOfWidget(widgetID);
// This is where we are going to re-insert the extension widgets (DOM // nodes) but we need to account for some hidden DOM nodes already present // in this container when determining where to put the nodes back. const container = CustomizableUI.getCustomizationTarget(
document.getElementById(area)
);
let moved = false;
let currentPosition = 0;
for (const child of container.childNodes) { const isSkipToolbarset = child.getAttribute("skipintoolbarset") == "true"; if (isSkipToolbarset && child !== container.lastChild) { continue;
}
if (currentPosition === position) {
child.before(node);
moved = true; break;
}
if (child === container.lastChild) {
child.after(node);
moved = true; break;
}
currentPosition++;
}
if (moved) { // Remove the attribute set when we artificially overflow the widget.
node.removeAttribute("overflowedItem");
node.removeAttribute("artificallyOverflowed");
node.removeAttribute("cui-anchorid");
_panel: null,
get panel() { // Lazy load the unified-extensions-panel panel the first time we need to // display it. if (!this._panel) {
let template = document.getElementById( "unified-extensions-panel-template"
);
template.replaceWith(template.content);
--> --------------------
--> maximum size reached
--> --------------------
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.93Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.