summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
commit8dd16259287f58f9273002717ec4d27e97127719 (patch)
tree3863e62a53829a84037444beab3abd4ed9dfc7d0 /toolkit/mozapps
parentReleasing progress-linux version 126.0.1-1~progress7.99u1. (diff)
downloadfirefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz
firefox-8dd16259287f58f9273002717ec4d27e97127719.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/mozapps')
-rw-r--r--toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs2
-rw-r--r--toolkit/mozapps/defaultagent/assets/fox-doodle-peek.pngbin0 -> 49744 bytes
-rw-r--r--toolkit/mozapps/defaultagent/jar.mn6
-rw-r--r--toolkit/mozapps/defaultagent/moz.build2
-rw-r--r--toolkit/mozapps/extensions/AbuseReporter.sys.mjs587
-rw-r--r--toolkit/mozapps/extensions/AddonManager.sys.mjs88
-rw-r--r--toolkit/mozapps/extensions/amWebAPI.sys.mjs19
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddons.css6
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddons.html8
-rw-r--r--toolkit/mozapps/extensions/content/aboutaddonsCommon.js4
-rw-r--r--toolkit/mozapps/extensions/content/abuse-report-frame.html213
-rw-r--r--toolkit/mozapps/extensions/content/abuse-report-panel.css181
-rw-r--r--toolkit/mozapps/extensions/content/abuse-report-panel.js873
-rw-r--r--toolkit/mozapps/extensions/content/abuse-reports.js357
-rw-r--r--toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs59
-rw-r--r--toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs18
-rw-r--r--toolkit/mozapps/extensions/jar.mn3
-rw-r--r--toolkit/mozapps/extensions/metrics.yaml47
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser.toml14
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_amo_abuse_report.js21
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_abuse_report.js1091
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_abuse_report_dialog.js185
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js44
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js6
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js7
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_message_bar.js185
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_options_ui_dark_theme.js173
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_html_scroll_restoration.js14
-rw-r--r--toolkit/mozapps/extensions/test/browser/browser_webapi_abuse_report.js375
-rw-r--r--toolkit/mozapps/extensions/test/browser/head_abuse_report.js492
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js687
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js15
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js196
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml199
-rw-r--r--toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js1
-rw-r--r--toolkit/mozapps/installer/package-name.mk3
-rw-r--r--toolkit/mozapps/installer/packager.mk9
-rw-r--r--toolkit/mozapps/installer/upload-files.mk4
-rw-r--r--toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/Makefile.in25
-rw-r--r--toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/moz.build21
-rw-r--r--toolkit/mozapps/macos-frameworks/ChannelPrefs/ChannelPrefs.mm8
-rw-r--r--toolkit/mozapps/macos-frameworks/ChannelPrefs/Makefile.in23
-rw-r--r--toolkit/mozapps/macos-frameworks/moz.build1
-rw-r--r--toolkit/mozapps/update/BackgroundUpdate.sys.mjs40
-rw-r--r--toolkit/mozapps/update/UpdateService.sys.mjs1200
-rw-r--r--toolkit/mozapps/update/common/readstrings.cpp9
-rw-r--r--toolkit/mozapps/update/docs/MaintenanceServiceTests.rst4
-rw-r--r--toolkit/mozapps/update/nsUpdateService.manifest2
-rw-r--r--toolkit/mozapps/update/updater/Makefile.in11
-rw-r--r--toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/Makefile.in25
-rw-r--r--toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/moz.build18
-rw-r--r--toolkit/mozapps/update/updater/macos-frameworks/moz.build7
-rw-r--r--toolkit/mozapps/update/updater/updater.cpp2
53 files changed, 1507 insertions, 6083 deletions
diff --git a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
index c9da853cb5..d5323a0e30 100644
--- a/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
+++ b/toolkit/mozapps/defaultagent/BackgroundTask_defaultagent.sys.mjs
@@ -331,7 +331,7 @@ function makeAlert(options) {
let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
alert.init(
options.name,
- "chrome://branding/content/about-logo@2x.png",
+ "chrome://global/content/defaultagent/fox-doodle-peek.png",
options.title,
options.body,
true /* aTextClickable */,
diff --git a/toolkit/mozapps/defaultagent/assets/fox-doodle-peek.png b/toolkit/mozapps/defaultagent/assets/fox-doodle-peek.png
new file mode 100644
index 0000000000..844f2cef62
--- /dev/null
+++ b/toolkit/mozapps/defaultagent/assets/fox-doodle-peek.png
Binary files differ
diff --git a/toolkit/mozapps/defaultagent/jar.mn b/toolkit/mozapps/defaultagent/jar.mn
new file mode 100644
index 0000000000..a38790f9a2
--- /dev/null
+++ b/toolkit/mozapps/defaultagent/jar.mn
@@ -0,0 +1,6 @@
+# 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/.
+
+toolkit.jar:
+ content/global/defaultagent/fox-doodle-peek.png (assets/fox-doodle-peek.png)
diff --git a/toolkit/mozapps/defaultagent/moz.build b/toolkit/mozapps/defaultagent/moz.build
index f8ae506d9c..3b209ab6e1 100644
--- a/toolkit/mozapps/defaultagent/moz.build
+++ b/toolkit/mozapps/defaultagent/moz.build
@@ -6,6 +6,8 @@
SPHINX_TREES["default-browser-agent"] = "docs"
+JAR_MANIFESTS += ["jar.mn"]
+
DIRS += ["proxy"]
UNIFIED_SOURCES += [
diff --git a/toolkit/mozapps/extensions/AbuseReporter.sys.mjs b/toolkit/mozapps/extensions/AbuseReporter.sys.mjs
index 944cef507c..966e2a6dd5 100644
--- a/toolkit/mozapps/extensions/AbuseReporter.sys.mjs
+++ b/toolkit/mozapps/extensions/AbuseReporter.sys.mjs
@@ -2,310 +2,39 @@
* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
-const PREF_ABUSE_REPORT_URL = "extensions.abuseReport.url";
-const PREF_AMO_DETAILS_API_URL = "extensions.abuseReport.amoDetailsURL";
-
-// Name associated with the report dialog window.
-const DIALOG_WINDOW_NAME = "addons-abuse-report-dialog";
-
// Maximum length of the string properties sent to the API endpoint.
const MAX_STRING_LENGTH = 255;
-// Minimum time between report submissions (in ms).
-const MIN_MS_BETWEEN_SUBMITS = 30000;
-
-// The addon types currently supported by the integrated abuse report panel.
-const SUPPORTED_ADDON_TYPES = [
+const AMO_SUPPORTED_ADDON_TYPES = [
"extension",
"theme",
"sitepermission",
// TODO(Bug 1789718): Remove after the deprecated XPIProvider-based implementation is also removed.
"sitepermission-deprecated",
+ "dictionary",
];
-// An expanded set of addon types supported when the abuse report hosted on AMO is enabled
-// (based on the "extensions.abuseReport.amoFormEnabled" pref).
-const AMO_SUPPORTED_ADDON_TYPES = [...SUPPORTED_ADDON_TYPES, "dictionary"];
-
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- AMTelemetry: "resource://gre/modules/AddonManager.sys.mjs",
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
ClientID: "resource://gre/modules/ClientID.sys.mjs",
});
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "ABUSE_REPORT_URL",
- PREF_ABUSE_REPORT_URL
-);
-
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "AMO_DETAILS_API_URL",
- PREF_AMO_DETAILS_API_URL
-);
-
-// Whether the abuse report feature should open a form hosted by the url
-// derived from the one set on the extensions.abuseReport.amoFormURL pref
-// or use the abuse report panel integrated in Firefox.
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "ABUSE_REPORT_AMO_FORM_ENABLED",
- "extensions.abuseReport.amoFormEnabled",
- true
-);
-
-const PRIVATE_REPORT_PROPS = Symbol("privateReportProps");
-
-const ERROR_TYPES = Object.freeze([
- "ERROR_ABORTED_SUBMIT",
- "ERROR_ADDON_NOTFOUND",
- "ERROR_CLIENT",
- "ERROR_NETWORK",
- "ERROR_UNKNOWN",
- "ERROR_RECENT_SUBMIT",
- "ERROR_SERVER",
- "ERROR_AMODETAILS_NOTFOUND",
- "ERROR_AMODETAILS_FAILURE",
-]);
-
-export class AbuseReportError extends Error {
- constructor(errorType, errorInfo = undefined) {
- if (!ERROR_TYPES.includes(errorType)) {
- throw new Error(`Unknown AbuseReportError type "${errorType}"`);
- }
-
- let message = errorInfo ? `${errorType} - ${errorInfo}` : errorType;
-
- super(message);
- this.name = "AbuseReportError";
- this.errorType = errorType;
- this.errorInfo = errorInfo;
- }
-}
-
-/**
- * Create an error info string from a fetch response object.
- *
- * @param {Response} response
- * A fetch response object to convert into an errorInfo string.
- *
- * @returns {Promise<string>}
- * The errorInfo string to be included in an AbuseReportError.
- */
-async function responseToErrorInfo(response) {
- return JSON.stringify({
- status: response.status,
- responseText: await response.text().catch(() => ""),
- });
-}
-
/**
- * A singleton object used to create new AbuseReport instances for a given addonId
- * and enforce a minium amount of time between two report submissions .
+ * A singleton used to manage abuse reports for add-ons.
*/
export const AbuseReporter = {
- _lastReportTimestamp: null,
-
- get amoFormEnabled() {
- return lazy.ABUSE_REPORT_AMO_FORM_ENABLED;
- },
-
getAMOFormURL({ addonId }) {
return Services.urlFormatter
.formatURLPref("extensions.abuseReport.amoFormURL")
.replace(/%addonID%/g, addonId);
},
- // Error types.
- updateLastReportTimestamp() {
- this._lastReportTimestamp = Date.now();
- },
-
- getTimeFromLastReport() {
- const currentTimestamp = Date.now();
- if (this._lastReportTimestamp > currentTimestamp) {
- // Reset the last report timestamp if it is in the future.
- this._lastReportTimestamp = null;
- }
-
- if (!this._lastReportTimestamp) {
- return Infinity;
- }
-
- return currentTimestamp - this._lastReportTimestamp;
- },
-
isSupportedAddonType(addonType) {
- if (this.amoFormEnabled) {
- return AMO_SUPPORTED_ADDON_TYPES.includes(addonType);
- }
- return SUPPORTED_ADDON_TYPES.includes(addonType);
- },
-
- /**
- * Create an AbuseReport instance, given the addonId and a reportEntryPoint.
- *
- * @param {string} addonId
- * The id of the addon to create the report instance for.
- * @param {object} options
- * @param {string} options.reportEntryPoint
- * An identifier that represent the entry point for the report flow.
- *
- * @returns {Promise<AbuseReport>}
- * Returns a promise that resolves to an instance of the AbuseReport
- * class, which represent an ongoing report.
- */
- async createAbuseReport(addonId, { reportEntryPoint } = {}) {
- let addon = await lazy.AddonManager.getAddonByID(addonId);
-
- if (!addon) {
- // The addon isn't installed, query the details from the AMO API endpoint.
- addon = await this.queryAMOAddonDetails(addonId, reportEntryPoint);
- }
-
- if (!addon) {
- lazy.AMTelemetry.recordReportEvent({
- addonId,
- errorType: "ERROR_ADDON_NOTFOUND",
- reportEntryPoint,
- });
- throw new AbuseReportError("ERROR_ADDON_NOTFOUND");
- }
-
- const reportData = await this.getReportData(addon);
-
- return new AbuseReport({
- addon,
- reportData,
- reportEntryPoint,
- });
- },
-
- /**
- * Retrieves the addon details from the AMO API endpoint, used to create
- * abuse reports on non-installed addon-ons.
- *
- * For the addon details that may be translated (e.g. addon name, description etc.)
- * the function will try to retrieve the string localized in the same locale used
- * by Gecko (and fallback to "en-US" if that locale is unavailable).
- *
- * The addon creator properties are set to the first author available.
- *
- * @param {string} addonId
- * The id of the addon to retrieve the details available on AMO.
- * @param {string} reportEntryPoint
- * The entry point for the report flow (to be included in the telemetry
- * recorded in case of failures).
- *
- * @returns {Promise<AMOAddonDetails|null>}
- * Returns a promise that resolves to an AMOAddonDetails object,
- * which has the subset of the AddonWrapper properties which are
- * needed by the abuse report panel or the report data sent to
- * the abuse report API endpoint), or null if it fails to
- * retrieve the details from AMO.
- *
- * @typedef {object} AMOAddonDetails
- * @prop {string} id
- * @prop {string} name
- * @prop {string} version
- * @prop {string} description
- * @prop {string} type
- * @prop {string} iconURL
- * @prop {string} homepageURL
- * @prop {string} supportURL
- * @prop {AMOAddonCreator} creator
- * @prop {boolean} isRecommended
- * @prop {number} signedState=AddonManager.SIGNEDSTATE_UNKNOWN
- * @prop {object} installTelemetryInfo={ source: "not_installed" }
- *
- * @typedef {object} AMOAddonCreator
- * @prop {string} name
- * @prop {string} url
- */
- async queryAMOAddonDetails(addonId, reportEntryPoint) {
- let details;
- try {
- // This should be the API endpoint documented at:
- // https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#detail
- details = await fetch(`${lazy.AMO_DETAILS_API_URL}/${addonId}`, {
- credentials: "omit",
- referrerPolicy: "no-referrer",
- headers: { "Content-Type": "application/json" },
- }).then(async response => {
- if (response.status === 200) {
- return response.json();
- }
-
- let errorInfo = await responseToErrorInfo(response).catch(
- () => undefined
- );
-
- if (response.status === 404) {
- // Record a different telemetry event for 404 errors.
- throw new AbuseReportError("ERROR_AMODETAILS_NOTFOUND", errorInfo);
- }
-
- throw new AbuseReportError("ERROR_AMODETAILS_FAILURE", errorInfo);
- });
- } catch (err) {
- // Log the original error in the browser console.
- Cu.reportError(err);
-
- lazy.AMTelemetry.recordReportEvent({
- addonId,
- errorType: err.errorType || "ERROR_AMODETAILS_FAILURE",
- reportEntryPoint,
- });
-
- return null;
- }
-
- const locale = Services.locale.appLocaleAsBCP47;
-
- // Get a string value from a translated value
- // (https://addons-server.readthedocs.io/en/latest/topics/api/overview.html#api-overview-translations)
- const getTranslatedValue = value => {
- if (typeof value === "string") {
- return value;
- }
- return value && (value[locale] || value["en-US"]);
- };
-
- const getAuthorField = fieldName =>
- details.authors && details.authors[0] && details.authors[0][fieldName];
-
- // Normalize type "statictheme" (which is the type used on the AMO API side)
- // into "theme" (because it is the type we use and expect on the Firefox side
- // for this addon type).
- const addonType = details.type === "statictheme" ? "theme" : details.type;
-
- return {
- id: addonId,
- name: getTranslatedValue(details.name),
- version: details.current_version.version,
- description: getTranslatedValue(details.summary),
- type: addonType,
- iconURL: details.icon_url,
- homepageURL: getTranslatedValue(details.homepage),
- supportURL: getTranslatedValue(details.support_url),
- // Set the addon creator to the first author in the AMO details.
- creator: {
- name: getAuthorField("name"),
- url: getAuthorField("url"),
- },
- isRecommended: details.is_recommended,
- // Set signed state to unknown because it isn't installed.
- signedState: lazy.AddonManager.SIGNEDSTATE_UNKNOWN,
- // Set the installTelemetryInfo.source to "not_installed".
- installTelemetryInfo: { source: "not_installed" },
- };
+ return AMO_SUPPORTED_ADDON_TYPES.includes(addonType);
},
/**
@@ -392,312 +121,4 @@ export const AbuseReporter = {
return data;
},
-
- /**
- * Helper function that returns a reference to a report dialog window
- * already opened (if any).
- *
- * @returns {Window?}
- */
- getOpenDialog() {
- return Services.ww.getWindowByName(DIALOG_WINDOW_NAME);
- },
-
- /**
- * Helper function that opens an abuse report form in a new dialog window.
- *
- * @param {string} addonId
- * The addonId being reported.
- * @param {string} reportEntryPoint
- * The entry point from which the user has triggered the abuse report
- * flow.
- * @param {XULElement} browser
- * The browser element (if any) that is opening the report window.
- *
- * @return {Promise<AbuseReportDialog>}
- * Returns an AbuseReportDialog object, rejects if it fails to open
- * the dialog.
- *
- * @typedef {object} AbuseReportDialog
- * An object that represents the abuse report dialog.
- * @prop {function} close
- * A method that closes the report dialog (used by the caller
- * to close the dialog when the user chooses to close the window
- * that started the abuse report flow).
- * @prop {Promise<AbuseReport|undefined>} promiseReport
- * A promise resolved to an AbuseReport instance if the report should
- * be submitted, or undefined if the user has cancelled the report.
- * Rejects if it fails to create an AbuseReport instance or to open
- * the abuse report window.
- */
- async openDialog(addonId, reportEntryPoint, browser) {
- const chromeWin = browser && browser.ownerGlobal;
- if (!chromeWin) {
- throw new Error("Abuse Reporter dialog cancelled, opener tab closed");
- }
-
- const dialogWin = this.getOpenDialog();
-
- if (dialogWin) {
- // If an abuse report dialog is already open, cancel the
- // previous report flow and start a new one.
- const { deferredReport, promiseReport } =
- dialogWin.arguments[0].wrappedJSObject;
- deferredReport.resolve({ userCancelled: true });
- await promiseReport;
- }
-
- const report = await AbuseReporter.createAbuseReport(addonId, {
- reportEntryPoint,
- });
-
- if (!SUPPORTED_ADDON_TYPES.includes(report.addon.type)) {
- throw new Error(
- `Addon type "${report.addon.type}" is not currently supported by the integrated abuse reporting feature`
- );
- }
-
- const params = Cc["@mozilla.org/array;1"].createInstance(
- Ci.nsIMutableArray
- );
-
- const dialogInit = {
- report,
- openWebLink(url) {
- chromeWin.openWebLinkIn(url, "tab", {
- relatedToCurrent: true,
- });
- },
- };
-
- params.appendElement(dialogInit);
-
- let win;
- function closeDialog() {
- if (win && !win.closed) {
- win.close();
- }
- }
-
- const promiseReport = new Promise((resolve, reject) => {
- dialogInit.deferredReport = { resolve, reject };
- }).then(
- ({ userCancelled }) => {
- closeDialog();
- return userCancelled ? undefined : report;
- },
- err => {
- Cu.reportError(
- `Unexpected abuse report panel error: ${err} :: ${err.stack}`
- );
- closeDialog();
- return Promise.reject({
- message: "Unexpected abuse report panel error",
- });
- }
- );
-
- const promiseReportPanel = new Promise((resolve, reject) => {
- dialogInit.deferredReportPanel = { resolve, reject };
- });
-
- dialogInit.promiseReport = promiseReport;
- dialogInit.promiseReportPanel = promiseReportPanel;
-
- win = Services.ww.openWindow(
- chromeWin,
- "chrome://mozapps/content/extensions/abuse-report-frame.html",
- DIALOG_WINDOW_NAME,
- // Set the dialog window options (including a reasonable initial
- // window height size, eventually adjusted by the panel once it
- // has been rendered its content).
- "dialog,centerscreen,height=700",
- params
- );
-
- return {
- close: closeDialog,
- promiseReport,
-
- // Properties used in tests
- promiseReportPanel,
- window: win,
- };
- },
};
-
-/**
- * Represents an ongoing abuse report. Instances of this class are created
- * by the `AbuseReporter.createAbuseReport` method.
- *
- * This object is used by the reporting UI panel and message bars to:
- *
- * - get an errorType in case of a report creation error (e.g. because of a
- * previously submitted report)
- * - get the addon details used inside the reporting panel
- * - submit the abuse report (and re-submit if a previous submission failed
- * and the user choose to retry to submit it again)
- * - abort an ongoing submission
- *
- * @param {object} options
- * @param {AddonWrapper|null} options.addon
- * AddonWrapper instance for the extension/theme being reported.
- * (May be null if the extension has not been found).
- * @param {object|null} options.reportData
- * An object which contains addon and environment details to send as part of a submission
- * (may be null if the report has a createErrorType).
- * @param {string} options.reportEntryPoint
- * A string that identify how the report has been triggered.
- */
-class AbuseReport {
- constructor({ addon, reportData, reportEntryPoint }) {
- this[PRIVATE_REPORT_PROPS] = {
- aborted: false,
- abortController: new AbortController(),
- addon,
- reportData,
- reportEntryPoint,
- // message and reason are initially null, and then set by the panel
- // using the related set method.
- message: null,
- reason: null,
- };
- }
-
- recordTelemetry(errorType) {
- const { addon, reportEntryPoint } = this;
- lazy.AMTelemetry.recordReportEvent({
- addonId: addon.id,
- addonType: addon.type,
- errorType,
- reportEntryPoint,
- });
- }
-
- /**
- * Submit the current report, given a reason and a message.
- *
- * @returns {Promise<void>}
- * Resolves once the report has been successfully submitted.
- * It rejects with an AbuseReportError if the report couldn't be
- * submitted for a known reason (or another Error type otherwise).
- */
- async submit() {
- const {
- aborted,
- abortController,
- message,
- reason,
- reportData,
- reportEntryPoint,
- } = this[PRIVATE_REPORT_PROPS];
-
- // Record telemetry event and throw an AbuseReportError.
- const rejectReportError = async (errorType, { response } = {}) => {
- this.recordTelemetry(errorType);
-
- // Leave errorInfo empty if there is no response or fails to
- // be converted into an error info object.
- const errorInfo = response
- ? await responseToErrorInfo(response).catch(() => undefined)
- : undefined;
-
- throw new AbuseReportError(errorType, errorInfo);
- };
-
- if (aborted) {
- // Report aborted before being actually submitted.
- return rejectReportError("ERROR_ABORTED_SUBMIT");
- }
-
- // Prevent submit of a new abuse report in less than MIN_MS_BETWEEN_SUBMITS.
- let msFromLastReport = AbuseReporter.getTimeFromLastReport();
- if (msFromLastReport < MIN_MS_BETWEEN_SUBMITS) {
- return rejectReportError("ERROR_RECENT_SUBMIT");
- }
-
- let response;
- try {
- response = await fetch(lazy.ABUSE_REPORT_URL, {
- signal: abortController.signal,
- method: "POST",
- credentials: "omit",
- referrerPolicy: "no-referrer",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({
- ...reportData,
- report_entry_point: reportEntryPoint,
- message,
- reason,
- }),
- });
- } catch (err) {
- if (err.name === "AbortError") {
- return rejectReportError("ERROR_ABORTED_SUBMIT");
- }
- Cu.reportError(err);
- return rejectReportError("ERROR_NETWORK");
- }
-
- if (response.ok && response.status >= 200 && response.status < 400) {
- // Ensure that the response is also a valid json format.
- try {
- await response.json();
- } catch (err) {
- this.recordTelemetry("ERROR_UNKNOWN");
- throw err;
- }
- AbuseReporter.updateLastReportTimestamp();
- this.recordTelemetry();
- return undefined;
- }
-
- if (response.status >= 400 && response.status < 500) {
- return rejectReportError("ERROR_CLIENT", { response });
- }
-
- if (response.status >= 500 && response.status < 600) {
- return rejectReportError("ERROR_SERVER", { response });
- }
-
- // We got an unexpected HTTP status code.
- return rejectReportError("ERROR_UNKNOWN", { response });
- }
-
- /**
- * Abort the report submission.
- */
- abort() {
- const { abortController } = this[PRIVATE_REPORT_PROPS];
- abortController.abort();
- this[PRIVATE_REPORT_PROPS].aborted = true;
- }
-
- get addon() {
- return this[PRIVATE_REPORT_PROPS].addon;
- }
-
- get reportEntryPoint() {
- return this[PRIVATE_REPORT_PROPS].reportEntryPoint;
- }
-
- /**
- * Set the open message (called from the panel when the user submit the report)
- *
- * @parm {string} message
- * An optional string which contains a description for the reported issue.
- */
- setMessage(message) {
- this[PRIVATE_REPORT_PROPS].message = message;
- }
-
- /**
- * Set the report reason (called from the panel when the user submit the report)
- *
- * @parm {string} reason
- * String identifier for the report reason.
- */
- setReason(reason) {
- this[PRIVATE_REPORT_PROPS].reason = reason;
- }
-}
diff --git a/toolkit/mozapps/extensions/AddonManager.sys.mjs b/toolkit/mozapps/extensions/AddonManager.sys.mjs
index a07a651895..9890e15bf4 100644
--- a/toolkit/mozapps/extensions/AddonManager.sys.mjs
+++ b/toolkit/mozapps/extensions/AddonManager.sys.mjs
@@ -26,7 +26,6 @@ const MOZ_COMPATIBILITY_NIGHTLY = ![
const INTL_LOCALES_CHANGED = "intl:app-locales-changed";
-const PREF_AMO_ABUSEREPORT = "extensions.abuseReport.amWebAPI.enabled";
const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
const PREF_EM_UPDATE_ENABLED = "extensions.update.enabled";
const PREF_EM_LAST_APP_VERSION = "extensions.lastAppVersion";
@@ -81,7 +80,6 @@ var AsyncShutdown = realAsyncShutdown;
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
AddonRepository: "resource://gre/modules/addons/AddonRepository.sys.mjs",
Extension: "resource://gre/modules/Extension.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
@@ -3280,7 +3278,7 @@ var AddonManagerInternal = {
// the customConfirmationUI preference and responding to the
// "addon-install-confirmation" notification. If the application
// does not implement its own prompt, use the built-in xul dialog.
- if (info.addon.userPermissions) {
+ if (info.addon.installPermissions) {
let subject = {
wrappedJSObject: {
target: browser,
@@ -3288,7 +3286,7 @@ var AddonManagerInternal = {
},
};
subject.wrappedJSObject.info.permissions =
- info.addon.userPermissions;
+ info.addon.installPermissions;
Services.obs.notifyObservers(
subject,
"webextension-permission-prompt"
@@ -3628,54 +3626,6 @@ var AddonManagerInternal = {
}
}
},
-
- async addonReportAbuse(target, id) {
- if (!Services.prefs.getBoolPref(PREF_AMO_ABUSEREPORT, false)) {
- return Promise.reject({
- message: "amWebAPI reportAbuse not supported",
- });
- }
-
- let existingDialog = lazy.AbuseReporter.getOpenDialog();
- if (existingDialog) {
- existingDialog.close();
- }
-
- const dialog = await lazy.AbuseReporter.openDialog(
- id,
- "amo",
- target
- ).catch(err => {
- Cu.reportError(err);
- return Promise.reject({
- message: "Error creating abuse report",
- });
- });
-
- return dialog.promiseReport.then(
- async report => {
- if (!report) {
- return false;
- }
-
- await report.submit().catch(err => {
- Cu.reportError(err);
- return Promise.reject({
- message: "Error submitting abuse report",
- });
- });
-
- return true;
- },
- err => {
- Cu.reportError(err);
- dialog.close();
- return Promise.reject({
- message: "Error creating abuse report",
- });
- }
- );
- },
},
};
@@ -5251,40 +5201,6 @@ AMTelemetry = {
},
/**
- * Record an event on abuse report submissions.
- *
- * @params {object} opts
- * @params {string} opts.addonId
- * The id of the addon being reported.
- * @params {string} [opts.addonType]
- * The type of the addon being reported (only present for an existing
- * addonId).
- * @params {string} [opts.errorType]
- * The AbuseReport errorType for a submission failure.
- * @params {string} opts.reportEntryPoint
- * The entry point of the abuse report.
- */
- recordReportEvent({ addonId, addonType, errorType, reportEntryPoint }) {
- this.recordEvent({
- method: "report",
- object: reportEntryPoint,
- value: addonId,
- extra: this.formatExtraVars({
- addon_type: addonType,
- error_type: errorType,
- }),
- });
- Glean.addonsManager.report.record(
- this.formatExtraVars({
- addon_id: addonId,
- addon_type: addonType,
- entry_point: reportEntryPoint,
- error_type: errorType,
- })
- );
- },
-
- /**
* @params {object} opts
* @params {nsIURI} opts.displayURI
*/
diff --git a/toolkit/mozapps/extensions/amWebAPI.sys.mjs b/toolkit/mozapps/extensions/amWebAPI.sys.mjs
index ae1b94cac7..64d4bf78d2 100644
--- a/toolkit/mozapps/extensions/amWebAPI.sys.mjs
+++ b/toolkit/mozapps/extensions/amWebAPI.sys.mjs
@@ -2,17 +2,6 @@
* 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 { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-
-const lazy = {};
-
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "AMO_ABUSEREPORT",
- "extensions.abuseReport.amWebAPI.enabled",
- false
-);
-
const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
@@ -255,14 +244,6 @@ export class WebAPI extends APIObject {
});
}
- reportAbuse(id) {
- return this._apiTask("addonReportAbuse", [id]);
- }
-
- get abuseReportPanelEnabled() {
- return lazy.AMO_ABUSEREPORT;
- }
-
eventListenerAdded() {
if (this.listenerCount == 0) {
this.broker.setAddonListener(data => {
diff --git a/toolkit/mozapps/extensions/content/aboutaddons.css b/toolkit/mozapps/extensions/content/aboutaddons.css
index 2f5157bea3..bb0bf04dda 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.css
+++ b/toolkit/mozapps/extensions/content/aboutaddons.css
@@ -185,8 +185,7 @@ search-addons > search-textbox {
max-width: var(--section-width);
}
-global-warnings,
-#abuse-reports-messages {
+global-warnings {
margin-inline-start: var(--main-margin-start);
max-width: var(--section-width);
}
@@ -645,7 +644,7 @@ panel-item[action="report"]::part(button) {
.addon-inline-options {
width: 100%;
- background-color: white;
+ background-color: Canvas;
margin-block: 4px;
/*
* Makes sure the browser minimal height is going to be the same as when
@@ -675,6 +674,7 @@ addon-permissions-list > .addon-detail-row {
background-position: 0 center;
background-size: 1.6rem 1.6rem;
background-repeat: no-repeat;
+ word-break: break-all;
}
.addon-permissions-list > li:dir(rtl) {
diff --git a/toolkit/mozapps/extensions/content/aboutaddons.html b/toolkit/mozapps/extensions/content/aboutaddons.html
index d0930ef42d..b48c219c05 100644
--- a/toolkit/mozapps/extensions/content/aboutaddons.html
+++ b/toolkit/mozapps/extensions/content/aboutaddons.html
@@ -30,7 +30,6 @@
<link rel="localization" href="branding/brand.ftl" />
<link rel="localization" href="toolkit/about/aboutAddons.ftl" />
- <link rel="localization" href="toolkit/about/abuseReports.ftl" />
<!-- Defer scripts so all the templates are loaded by the time they run. -->
<script
@@ -163,13 +162,6 @@
></addon-page-header>
<addon-page-options id="page-options"></addon-page-options>
- <message-bar-stack
- id="abuse-reports-messages"
- reverse
- max-message-bar-count="3"
- >
- </message-bar-stack>
-
<div id="main"></div>
</div>
</div>
diff --git a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
index fd91ba58be..5aa5f7ea0a 100644
--- a/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
+++ b/toolkit/mozapps/extensions/content/aboutaddonsCommon.js
@@ -150,13 +150,13 @@ function shouldShowPermissionsPrompt(addon) {
return false;
}
- let perms = addon.userPermissions;
+ let perms = addon.installPermissions;
return perms?.origins.length || perms?.permissions.length;
}
function showPermissionsPrompt(addon) {
return new Promise(resolve => {
- const permissions = addon.userPermissions;
+ const permissions = addon.installPermissions;
const target = getBrowserElement();
const onAddonEnabled = () => {
diff --git a/toolkit/mozapps/extensions/content/abuse-report-frame.html b/toolkit/mozapps/extensions/content/abuse-report-frame.html
deleted file mode 100644
index 64148ecece..0000000000
--- a/toolkit/mozapps/extensions/content/abuse-report-frame.html
+++ /dev/null
@@ -1,213 +0,0 @@
-<!-- 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/. -->
-
-<!DOCTYPE html>
-<html>
- <head>
- <title></title>
- <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" />
- <link
- rel="stylesheet"
- href="chrome://mozapps/content/extensions/aboutaddons.css"
- />
- <link
- rel="stylesheet"
- href="chrome://mozapps/content/extensions/abuse-report-panel.css"
- />
-
- <link rel="localization" href="branding/brand.ftl" />
- <link rel="localization" href="toolkit/about/aboutAddons.ftl" />
- <link rel="localization" href="toolkit/about/abuseReports.ftl" />
- <script
- type="module"
- src="chrome://global/content/elements/moz-button-group.mjs"
- ></script>
- <script
- type="module"
- src="chrome://global/content/elements/moz-support-link.mjs"
- ></script>
- <script
- defer
- src="chrome://mozapps/content/extensions/abuse-report-panel.js"
- ></script>
- </head>
-
- <body>
- <addon-abuse-report></addon-abuse-report>
-
- <!-- WebComponents Templates -->
- <template id="tmpl-modal">
- <div class="modal-overlay-outer"></div>
- <div class="modal-panel-container"></div>
- </template>
-
- <template id="tmpl-abuse-report">
- <form class="addon-abuse-report" onsubmit="return false;">
- <div class="abuse-report-header">
- <img class="card-heading-icon addon-icon" />
- <div class="card-contents">
- <span class="addon-name"></span>
- <span
- class="addon-author-box"
- data-l10n-args='{"author-name": "author placeholder"}'
- data-l10n-id="abuse-report-addon-authored-by"
- >
- <a
- data-l10n-name="author-name"
- class="author"
- href="#"
- target="_blank"
- ></a>
- </span>
- </div>
- </div>
- <button class="abuse-report-close-icon" type="button"></button>
- <div class="abuse-report-contents">
- <abuse-report-reasons-panel></abuse-report-reasons-panel>
- <abuse-report-submit-panel hidden></abuse-report-submit-panel>
- </div>
- <div class="abuse-report-buttons">
- <moz-button-group class="abuse-report-reasons-buttons">
- <button
- class="abuse-report-cancel"
- type="button"
- data-l10n-id="abuse-report-cancel-button"
- ></button>
- <button
- class="primary abuse-report-next"
- type="button"
- data-l10n-id="abuse-report-next-button"
- ></button>
- </moz-button-group>
- <moz-button-group class="abuse-report-submit-buttons" hidden>
- <button
- class="abuse-report-goback"
- type="button"
- data-l10n-id="abuse-report-goback-button"
- ></button>
- <button
- class="primary abuse-report-submit"
- type="button"
- data-l10n-id="abuse-report-submit-button"
- ></button>
- </moz-button-group>
- </div>
- </form>
- </template>
-
- <template id="tmpl-reasons-panel">
- <h2 class="abuse-report-title"></h2>
- <hr />
- <p class="abuse-report-subtitle" data-l10n-id="abuse-report-subtitle"></p>
- <ul class="abuse-report-reasons">
- <li is="abuse-report-reason-listitem" report-reason="other"></li>
- </ul>
- <p>
- <span data-l10n-id="abuse-report-learnmore-intro"></span>
- <a
- is="moz-support-link"
- target="_blank"
- support-page="reporting-extensions-and-themes-abuse"
- data-l10n-id="abuse-report-learnmore-link"
- >
- </a>
- </p>
- </template>
-
- <template id="tmpl-submit-panel">
- <h2 class="abuse-reason-title"></h2>
- <abuse-report-reason-suggestions></abuse-report-reason-suggestions>
- <hr />
- <p
- class="abuse-report-subtitle"
- data-l10n-id="abuse-report-submit-description"
- ></p>
- <textarea name="message" data-l10n-id="abuse-report-textarea"></textarea>
- <p class="abuse-report-note" data-l10n-id="abuse-report-submit-note"></p>
- </template>
-
- <template id="tmpl-reason-listitem">
- <label>
- <input type="radio" name="reason" class="radio" />
- <span class="reason-description"></span>
- <span hidden class="abuse-report-note reason-example"></span>
- </label>
- </template>
-
- <template id="tmpl-suggestions-settings">
- <p data-l10n-id="abuse-report-settings-suggestions"></p>
- <p></p>
- <ul>
- <li>
- <a
- is="moz-support-link"
- target="_blank"
- data-l10n-id="abuse-report-settings-suggestions-search"
- support-page="prefs-search"
- >
- </a>
- </li>
- <li>
- <a
- is="moz-support-link"
- target="_blank"
- data-l10n-id="abuse-report-settings-suggestions-homepage"
- support-page="prefs-homepage"
- >
- </a>
- </li>
- </ul>
- </template>
-
- <template id="tmpl-suggestions-policy">
- <p data-l10n-id="abuse-report-policy-suggestions">
- <a
- class="abuse-policy-learnmore"
- target="_blank"
- data-l10n-name="report-infringement-link"
- >
- </a>
- </p>
- </template>
-
- <template id="tmpl-suggestions-broken-extension">
- <p data-l10n-id="abuse-report-broken-suggestions-extension">
- <a
- class="extension-support-link"
- target="_blank"
- data-l10n-name="support-link"
- >
- </a>
- </p>
-
- <p></p
- ></template>
-
- <template id="tmpl-suggestions-broken-theme">
- <p data-l10n-id="abuse-report-broken-suggestions-theme">
- <a
- class="extension-support-link"
- target="_blank"
- data-l10n-name="support-link"
- >
- </a>
- </p>
-
- <p></p
- ></template>
-
- <template id="tmpl-suggestions-broken-sitepermission">
- <p data-l10n-id="abuse-report-broken-suggestions-sitepermission">
- <a
- class="extension-support-link"
- target="_blank"
- data-l10n-name="support-link"
- >
- </a>
- </p>
-
- <p></p
- ></template>
- </body>
-</html>
diff --git a/toolkit/mozapps/extensions/content/abuse-report-panel.css b/toolkit/mozapps/extensions/content/abuse-report-panel.css
deleted file mode 100644
index aa2243c162..0000000000
--- a/toolkit/mozapps/extensions/content/abuse-report-panel.css
+++ /dev/null
@@ -1,181 +0,0 @@
-/* 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/. */
-
-/* Abuse Reports card */
-
-:root {
- --close-icon-url: url("chrome://global/skin/icons/close.svg");
- --close-icon-size: 20px;
-
- --modal-panel-min-width: 60%;
- --modal-panel-margin-top: 36px;
- --modal-panel-margin-bottom: 36px;
- --modal-panel-margin: 20%;
- --modal-panel-padding: 40px;
-
- --line-height: 20px;
- --textarea-height: 220px;
- --listitem-padding-bottom: 14px;
- --list-radio-column-size: 28px;
- --note-font-size: 14px;
- --note-font-weight: 400;
- --subtitle-font-size: 16px;
- --subtitle-font-weight: 600;
-}
-
-/* Ensure that the document (embedded in the XUL about:addons using a
- XUL browser) has a transparent background */
-html {
- background-color: transparent;
-}
-
-.modal-overlay-outer {
- background: var(--grey-90-a60);
- width: 100%;
- height: 100%;
- position: fixed;
- z-index: -1;
-}
-
-.modal-panel-container {
- padding-top: var(--modal-panel-margin-top);
- padding-bottom: var(--modal-panel-margin-bottom);
- padding-left: var(--modal-panel-margin);
- padding-right: var(--modal-panel-margin);
-}
-
-.addon-abuse-report {
- min-width: var(--modal-panel-min-width);
- padding: var(--modal-panel-padding);
- display: flex;
- flex-direction: column;
- position: relative;
-}
-
-.addon-abuse-report:hover {
- /* Avoid the card box highlighting on hover. */
- box-shadow: none;
-}
-
-
-.abuse-report-close-icon {
- /* position the close button in the panel upper-right corner */
- position: absolute;
- top: 12px;
- inset-inline-end: 16px;
-}
-
-button.abuse-report-close-icon {
- background: var(--close-icon-url) no-repeat center center;
- -moz-context-properties: fill;
- color: inherit !important;
- fill: currentColor;
- min-width: auto;
- min-height: auto;
- width: var(--close-icon-size);
- height: var(--close-icon-size);
- margin: 0;
- padding: 0;
-}
-
-button.abuse-report-close-icon:hover {
- fill-opacity: 0.1;
-}
-
-button.abuse-report-close-icon:hover:active {
- fill-opacity: 0.2;
-}
-
-.abuse-report-header {
- display: flex;
- flex-direction: row;
-}
-
-.abuse-report-contents,
-.abuse-report-contents > hr {
- width: 100%;
-}
-
-.abuse-report-note {
- color: var(--text-color-deemphasized);
- font-size: var(--note-font-size);
- font-weight: var(--note-font-weight);
- line-height: var(--line-height);
-}
-
-.abuse-report-subtitle {
- font-size: var(--subtitle-font-size);
- font-weight: var(--subtitle-font-weight);
- line-height: var(--line-height);
-}
-
-ul.abuse-report-reasons {
- list-style-type: none;
- padding-inline-start: 0;
-}
-
-ul.abuse-report-reasons > li {
- display: flex;
- padding-bottom: var(--listitem-padding-bottom);
-}
-
-ul.abuse-report-reasons > li > label {
- display: grid;
- grid-template-columns: var(--list-radio-column-size) auto;
- grid-template-rows: 50% auto;
- width: 100%;
- line-height: var(--line-height);
- font-size: var(--subtitle-font-size);
- font-weight: var(--note-font-weight);
- margin-inline-start: 4px;
-}
-
-ul.abuse-report-reasons > li > label > [type="radio"] {
- grid-column: 1;
-}
-
-ul.abuse-report-reasons > li > label > span {
- grid-column: 2;
-}
-
-abuse-report-submit-panel textarea {
- width: 100%;
- height: var(--textarea-height);
- resize: none;
- box-sizing: border-box;
-}
-
-/* Adapt styles for the panel opened in its own dialog window */
-
-html.dialog-window {
- background-color: var(--in-content-box-background);
- height: 100%;
- min-width: 740px;
-}
-
-html.dialog-window body {
- overflow: hidden;
- min-height: 100%;
- display: flex;
- flex-direction: column;
-}
-
-html.dialog-window .abuse-report-close-icon {
- display: none;
-}
-
-html.dialog-window addon-abuse-report {
- flex-grow: 1;
- display: flex;
- /* Ensure that the dialog window starts from a reasonable initial size */
- --modal-panel-min-width: 700px;
-}
-
-html.dialog-window addon-abuse-report form {
- display: flex;
-}
-
-html.dialog-window addon-abuse-report form .abuse-report-contents {
- flex-grow: 1;
-}
diff --git a/toolkit/mozapps/extensions/content/abuse-report-panel.js b/toolkit/mozapps/extensions/content/abuse-report-panel.js
deleted file mode 100644
index 53e0d76db0..0000000000
--- a/toolkit/mozapps/extensions/content/abuse-report-panel.js
+++ /dev/null
@@ -1,873 +0,0 @@
-/* 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/. */
-/* eslint max-len: ["error", 80] */
-
-"use strict";
-
-ChromeUtils.defineESModuleGetters(this, {
- AbuseReporter: "resource://gre/modules/AbuseReporter.sys.mjs",
-});
-
-const IS_DIALOG_WINDOW = window.arguments && window.arguments.length;
-
-let openWebLink = IS_DIALOG_WINDOW
- ? window.arguments[0].wrappedJSObject.openWebLink
- : url => {
- window.windowRoot.ownerGlobal.openWebLinkIn(url, "tab", {
- relatedToCurrent: true,
- });
- };
-
-const showOnAnyType = () => false;
-const hideOnAnyType = () => true;
-const hideOnAddonTypes = hideForTypes => {
- return addonType => hideForTypes.includes(addonType);
-};
-
-// The reasons string used as a key in this Map is expected to stay in sync
-// with the reasons string used in the "abuseReports.ftl" locale file and
-// the suggestions templates included in abuse-report-frame.html.
-const ABUSE_REASONS = (window.ABUSE_REPORT_REASONS = {
- damage: {
- isExampleHidden: showOnAnyType,
- isReasonHidden: hideOnAddonTypes(["theme"]),
- },
- spam: {
- isExampleHidden: showOnAnyType,
- isReasonHidden: hideOnAddonTypes(["sitepermission"]),
- },
- settings: {
- hasSuggestions: true,
- isExampleHidden: hideOnAnyType,
- isReasonHidden: hideOnAddonTypes(["theme", "sitepermission"]),
- },
- deceptive: {
- isExampleHidden: showOnAnyType,
- isReasonHidden: hideOnAddonTypes(["sitepermission"]),
- },
- broken: {
- hasAddonTypeL10nId: true,
- hasAddonTypeSuggestionTemplate: true,
- hasSuggestions: true,
- isExampleHidden: hideOnAddonTypes(["theme"]),
- isReasonHidden: showOnAnyType,
- requiresSupportURL: true,
- },
- policy: {
- hasSuggestions: true,
- isExampleHidden: hideOnAnyType,
- isReasonHidden: hideOnAddonTypes(["sitepermission"]),
- },
- unwanted: {
- isExampleHidden: showOnAnyType,
- isReasonHidden: hideOnAddonTypes(["theme"]),
- },
- other: {
- isExampleHidden: hideOnAnyType,
- isReasonHidden: showOnAnyType,
- },
-});
-
-// Maps the reason id to the last version of the related fluent id.
-// NOTE: when changing the localized string, increase the `-vN` suffix
-// in the abuseReports.ftl fluent file and update this mapping table.
-const REASON_L10N_STRING_MAPPING = {
- "abuse-report-damage-reason": "abuse-report-damage-reason-v2",
- "abuse-report-spam-reason": "abuse-report-spam-reason-v2",
- "abuse-report-settings-reason": "abuse-report-settings-reason-v2",
- "abuse-report-deceptive-reason": "abuse-report-deceptive-reason-v2",
- "abuse-report-broken-reason-extension":
- "abuse-report-broken-reason-extension-v2",
- "abuse-report-broken-reason-sitepermission":
- "abuse-report-broken-reason-sitepermission-v2",
- "abuse-report-broken-reason-theme": "abuse-report-broken-reason-theme-v2",
- "abuse-report-policy-reason": "abuse-report-policy-reason-v2",
- "abuse-report-unwanted-reason": "abuse-report-unwanted-reason-v2",
-};
-
-function getReasonL10nId(reason, addonType) {
- let reasonId = `abuse-report-${reason}-reason`;
- // Special case reasons that have a addonType-specific
- // l10n id.
- if (ABUSE_REASONS[reason].hasAddonTypeL10nId) {
- reasonId += `-${addonType}`;
- }
- // Map the reason to the corresponding versionized fluent string, using the
- // mapping table above, if available.
- return REASON_L10N_STRING_MAPPING[reasonId] || reasonId;
-}
-
-function getSuggestionsTemplate({ addonType, reason, supportURL }) {
- const reasonInfo = ABUSE_REASONS[reason];
-
- if (
- !addonType ||
- !reasonInfo.hasSuggestions ||
- (reasonInfo.requiresSupportURL && !supportURL)
- ) {
- return null;
- }
-
- let templateId = `tmpl-suggestions-${reason}`;
- // Special case reasons that have a addonType-specific
- // suggestion template.
- if (reasonInfo.hasAddonTypeSuggestionTemplate) {
- templateId += `-${addonType}`;
- }
-
- return document.getElementById(templateId);
-}
-
-// Map of the learnmore links metadata, keyed by link element class.
-const LEARNMORE_LINKS = {
- ".abuse-policy-learnmore": {
- baseURL: "https://www.mozilla.org/%LOCALE%/",
- path: "about/legal/report-infringement/",
- },
-};
-
-// Format links that match the selector in the LEARNMORE_LINKS map
-// found in a given container element.
-function formatLearnMoreURLs(containerEl) {
- for (const [linkClass, linkInfo] of Object.entries(LEARNMORE_LINKS)) {
- for (const element of containerEl.querySelectorAll(linkClass)) {
- const baseURL = Services.urlFormatter.formatURL(linkInfo.baseURL);
-
- element.href = baseURL + linkInfo.path;
- }
- }
-}
-
-// Define a set of getters from a Map<propertyName, selector>.
-function defineElementSelectorsGetters(object, propsMap) {
- const props = Object.entries(propsMap).reduce((acc, entry) => {
- const [name, selector] = entry;
- acc[name] = { get: () => object.querySelector(selector) };
- return acc;
- }, {});
- Object.defineProperties(object, props);
-}
-
-// Define a set of properties getters and setters for a
-// Map<propertyName, attributeName>.
-function defineElementAttributesProperties(object, propsMap) {
- const props = Object.entries(propsMap).reduce((acc, entry) => {
- const [name, attr] = entry;
- acc[name] = {
- get: () => object.getAttribute(attr),
- set: value => {
- object.setAttribute(attr, value);
- },
- };
- return acc;
- }, {});
- Object.defineProperties(object, props);
-}
-
-// Return an object with properties associated to elements
-// found using the related selector in the propsMap.
-function getElements(containerEl, propsMap) {
- return Object.entries(propsMap).reduce((acc, entry) => {
- const [name, selector] = entry;
- let elements = containerEl.querySelectorAll(selector);
- acc[name] = elements.length > 1 ? elements : elements[0];
- return acc;
- }, {});
-}
-
-function dispatchCustomEvent(el, eventName, detail) {
- el.dispatchEvent(new CustomEvent(eventName, { detail }));
-}
-
-// This WebComponent extends the li item to represent an abuse report reason
-// and it is responsible for:
-// - embedding a photon styled radio buttons
-// - localizing the reason list item
-// - optionally embedding a localized example, positioned
-// below the reason label, and adjusts the item height
-// accordingly
-class AbuseReasonListItem extends HTMLLIElement {
- constructor() {
- super();
- defineElementAttributesProperties(this, {
- addonType: "addon-type",
- reason: "report-reason",
- checked: "checked",
- });
- }
-
- connectedCallback() {
- this.update();
- }
-
- async update() {
- if (this.reason !== "other" && !this.addonType) {
- return;
- }
-
- const { reason, checked, addonType } = this;
-
- this.textContent = "";
- const content = document.importNode(this.template.content, true);
-
- if (reason) {
- const reasonId = `abuse-reason-${reason}`;
- const reasonInfo = ABUSE_REASONS[reason] || {};
-
- const { labelEl, descriptionEl, radioEl } = getElements(content, {
- labelEl: "label",
- descriptionEl: ".reason-description",
- radioEl: "input[type=radio]",
- });
-
- labelEl.setAttribute("for", reasonId);
- radioEl.id = reasonId;
- radioEl.value = reason;
- radioEl.checked = !!checked;
-
- // This reason has a different localized description based on the
- // addon type.
- document.l10n.setAttributes(
- descriptionEl,
- getReasonL10nId(reason, addonType)
- );
-
- // Show the reason example if supported for the addon type.
- if (!reasonInfo.isExampleHidden(addonType)) {
- const exampleEl = content.querySelector(".reason-example");
- document.l10n.setAttributes(
- exampleEl,
- `abuse-report-${reason}-example`
- );
- exampleEl.hidden = false;
- }
- }
-
- formatLearnMoreURLs(content);
-
- this.appendChild(content);
- }
-
- get template() {
- return document.getElementById("tmpl-reason-listitem");
- }
-}
-
-// This WebComponents implements the first step of the abuse
-// report submission and embeds a randomized reasons list.
-class AbuseReasonsPanel extends HTMLElement {
- constructor() {
- super();
- defineElementAttributesProperties(this, {
- addonType: "addon-type",
- });
- }
-
- connectedCallback() {
- this.update();
- }
-
- update() {
- if (!this.isConnected || !this.addonType) {
- return;
- }
-
- const { addonType } = this;
-
- this.textContent = "";
- const content = document.importNode(this.template.content, true);
-
- const { titleEl, listEl } = getElements(content, {
- titleEl: ".abuse-report-title",
- listEl: "ul.abuse-report-reasons",
- });
-
- // Change the title l10n-id if the addon type is theme.
- document.l10n.setAttributes(titleEl, `abuse-report-title-${addonType}`);
-
- // Create the randomized list of reasons.
- const reasons = Object.keys(ABUSE_REASONS)
- .filter(reason => reason !== "other")
- .sort(() => Math.random() - 0.5);
-
- for (const reason of reasons) {
- const reasonInfo = ABUSE_REASONS[reason];
- if (!reasonInfo || reasonInfo.isReasonHidden(addonType)) {
- // Skip an extension only reason while reporting a theme.
- continue;
- }
- const item = document.createElement("li", {
- is: "abuse-report-reason-listitem",
- });
- item.reason = reason;
- item.addonType = addonType;
-
- listEl.prepend(item);
- }
-
- listEl.firstElementChild.checked = true;
- formatLearnMoreURLs(content);
-
- this.appendChild(content);
- }
-
- get template() {
- return document.getElementById("tmpl-reasons-panel");
- }
-}
-
-// This WebComponent is responsible for the suggestions, which are:
-// - generated based on a template keyed by abuse report reason
-// - localized by assigning fluent ids generated from the abuse report reason
-// - learn more and extension support url are then generated when the
-// specific reason expects it
-class AbuseReasonSuggestions extends HTMLElement {
- constructor() {
- super();
- defineElementAttributesProperties(this, {
- extensionSupportURL: "extension-support-url",
- reason: "report-reason",
- });
- }
-
- update() {
- const { addonType, extensionSupportURL, reason } = this;
-
- this.textContent = "";
-
- let template = getSuggestionsTemplate({
- addonType,
- reason,
- supportURL: extensionSupportURL,
- });
-
- if (template) {
- let content = document.importNode(template.content, true);
-
- formatLearnMoreURLs(content);
-
- let extSupportLink = content.querySelector("a.extension-support-link");
- if (extSupportLink) {
- extSupportLink.href = extensionSupportURL;
- }
-
- this.appendChild(content);
- this.hidden = false;
- } else {
- this.hidden = true;
- }
- }
-}
-
-// This WebComponents implements the last step of the abuse report submission.
-class AbuseSubmitPanel extends HTMLElement {
- constructor() {
- super();
- defineElementAttributesProperties(this, {
- addonType: "addon-type",
- reason: "report-reason",
- extensionSupportURL: "extensionSupportURL",
- });
- defineElementSelectorsGetters(this, {
- _textarea: "textarea",
- _title: ".abuse-reason-title",
- _suggestions: "abuse-report-reason-suggestions",
- });
- }
-
- connectedCallback() {
- this.render();
- }
-
- render() {
- this.textContent = "";
- this.appendChild(document.importNode(this.template.content, true));
- }
-
- update() {
- if (!this.isConnected || !this.addonType) {
- return;
- }
- const { addonType, reason, _suggestions, _title } = this;
- document.l10n.setAttributes(_title, getReasonL10nId(reason, addonType));
- _suggestions.reason = reason;
- _suggestions.addonType = addonType;
- _suggestions.extensionSupportURL = this.extensionSupportURL;
- _suggestions.update();
- }
-
- clear() {
- this._textarea.value = "";
- }
-
- get template() {
- return document.getElementById("tmpl-submit-panel");
- }
-}
-
-// This WebComponent provides the abuse report
-class AbuseReport extends HTMLElement {
- constructor() {
- super();
- this._report = null;
- defineElementSelectorsGetters(this, {
- _form: "form",
- _textarea: "textarea",
- _radioCheckedReason: "[type=radio]:checked",
- _reasonsPanel: "abuse-report-reasons-panel",
- _submitPanel: "abuse-report-submit-panel",
- _reasonsPanelButtons: ".abuse-report-reasons-buttons",
- _submitPanelButtons: ".abuse-report-submit-buttons",
- _iconClose: ".abuse-report-close-icon",
- _btnNext: "button.abuse-report-next",
- _btnCancel: "button.abuse-report-cancel",
- _btnGoBack: "button.abuse-report-goback",
- _btnSubmit: "button.abuse-report-submit",
- _addonAuthorContainer: ".abuse-report-header .addon-author-box",
- _addonIconElement: ".abuse-report-header img.addon-icon",
- _addonNameElement: ".abuse-report-header .addon-name",
- _linkAddonAuthor: ".abuse-report-header .addon-author-box a.author",
- });
- }
-
- connectedCallback() {
- this.render();
-
- this.addEventListener("click", this);
-
- // Start listening to keydown events (to close the modal
- // when Escape has been pressed and to handling the keyboard
- // navigation).
- document.addEventListener("keydown", this);
- }
-
- disconnectedCallback() {
- this.textContent = "";
- this.removeEventListener("click", this);
- document.removeEventListener("keydown", this);
- }
-
- handleEvent(evt) {
- if (!this.isConnected || !this.addon) {
- return;
- }
-
- switch (evt.type) {
- case "keydown":
- if (evt.key === "Escape") {
- // Prevent Esc to close the panel if the textarea is
- // empty.
- if (this.message && !this._submitPanel.hidden) {
- return;
- }
- this.cancel();
- }
- if (!IS_DIALOG_WINDOW) {
- // Workaround keyboard navigation issues when
- // the panel is running in its own dialog window.
- this.handleKeyboardNavigation(evt);
- }
- break;
- case "click":
- if (evt.target === this._iconClose || evt.target === this._btnCancel) {
- // NOTE: clear the focus on the clicked element to ensure that
- // -moz-focusring pseudo class is not still set on the element
- // when the panel is going to be shown again (See Bug 1560949).
- evt.target.blur();
- this.cancel();
- }
- if (evt.target === this._btnNext) {
- this.switchToSubmitMode();
- }
- if (evt.target === this._btnGoBack) {
- this.switchToListMode();
- }
- if (evt.target === this._btnSubmit) {
- this.submit();
- }
- if (evt.target.localName === "a") {
- evt.preventDefault();
- evt.stopPropagation();
- const url = evt.target.getAttribute("href");
- // Ignore if url is empty.
- if (url) {
- openWebLink(url);
- }
- }
- break;
- }
- }
-
- handleKeyboardNavigation(evt) {
- if (
- evt.keyCode !== evt.DOM_VK_TAB ||
- evt.altKey ||
- evt.controlKey ||
- evt.metaKey
- ) {
- return;
- }
-
- const fm = Services.focus;
- const backward = evt.shiftKey;
-
- const isFirstFocusableElement = el => {
- // Also consider the document body as a valid first focusable element.
- if (el === document.body) {
- return true;
- }
- // XXXrpl unfortunately there is no way to get the first focusable element
- // without asking the focus manager to move focus to it (similar strategy
- // is also being used in about:prefereces subdialog.js).
- const rv = el == fm.moveFocus(window, null, fm.MOVEFOCUS_FIRST, 0);
- fm.setFocus(el, 0);
- return rv;
- };
-
- // If the focus is exiting the panel while navigating
- // backward, focus the previous element sibling on the
- // Firefox UI.
- if (backward && isFirstFocusableElement(evt.target)) {
- evt.preventDefault();
- evt.stopImmediatePropagation();
- const chromeWin = window.windowRoot.ownerGlobal;
- Services.focus.moveFocus(
- chromeWin,
- null,
- Services.focus.MOVEFOCUS_BACKWARD,
- Services.focus.FLAG_BYKEY
- );
- }
- }
-
- render() {
- this.textContent = "";
- const formTemplate = document.importNode(this.template.content, true);
- if (IS_DIALOG_WINDOW) {
- this.appendChild(formTemplate);
- } else {
- // Append the report form inside a modal overlay when the report panel
- // is a sub-frame of the about:addons tab.
- const modalTemplate = document.importNode(
- this.modalTemplate.content,
- true
- );
-
- this.appendChild(modalTemplate);
- this.querySelector(".modal-panel-container").appendChild(formTemplate);
-
- // Add the card styles to the form.
- this.querySelector("form").classList.add("card");
- }
- }
-
- async update() {
- if (!this.addon) {
- return;
- }
-
- const {
- addonId,
- addonType,
- _addonAuthorContainer,
- _addonIconElement,
- _addonNameElement,
- _linkAddonAuthor,
- _reasonsPanel,
- _submitPanel,
- } = this;
-
- // Ensure that the first step of the abuse submission is the one
- // currently visible.
- this.switchToListMode();
-
- // Cancel the abuse report if the addon is not an extension or theme.
- if (!AbuseReporter.isSupportedAddonType(addonType)) {
- Cu.reportError(
- new Error(
- `Closing abuse report panel on unexpected addon type: ${addonType}`
- )
- );
- this.cancel();
- return;
- }
-
- _addonNameElement.textContent = this.addonName;
-
- if (this.authorName) {
- _linkAddonAuthor.href = this.authorURL || this.homepageURL;
- _linkAddonAuthor.textContent = this.authorName;
- document.l10n.setAttributes(
- _linkAddonAuthor.parentNode,
- "abuse-report-addon-authored-by",
- { "author-name": this.authorName }
- );
- _addonAuthorContainer.hidden = false;
- } else {
- _addonAuthorContainer.hidden = true;
- }
-
- _addonIconElement.setAttribute("src", this.iconURL);
-
- _reasonsPanel.addonType = this.addonType;
- _reasonsPanel.update();
-
- _submitPanel.addonType = this.addonType;
- _submitPanel.reason = this.reason;
- _submitPanel.extensionSupportURL = this.supportURL;
- _submitPanel.update();
-
- this.focus();
-
- dispatchCustomEvent(this, "abuse-report:updated", {
- addonId,
- panel: "reasons",
- });
- }
-
- setAbuseReport(abuseReport) {
- this._report = abuseReport;
- // Clear the textarea from any previously entered content.
- this._submitPanel.clear();
-
- if (abuseReport) {
- this.update();
- this.hidden = false;
- } else {
- this.hidden = true;
- }
- }
-
- focus() {
- if (!this.isConnected || !this.addon) {
- return;
- }
- if (this._reasonsPanel.hidden) {
- const { _textarea } = this;
- _textarea.focus();
- _textarea.select();
- } else {
- const { _radioCheckedReason } = this;
- if (_radioCheckedReason) {
- _radioCheckedReason.focus();
- }
- }
- }
-
- cancel() {
- if (!this.isConnected || !this.addon) {
- return;
- }
- this._report = null;
- dispatchCustomEvent(this, "abuse-report:cancel");
- }
-
- submit() {
- if (!this.isConnected || !this.addon) {
- return;
- }
- this._report.setMessage(this.message);
- this._report.setReason(this.reason);
- dispatchCustomEvent(this, "abuse-report:submit", {
- addonId: this.addonId,
- report: this._report,
- });
- }
-
- switchToSubmitMode() {
- if (!this.isConnected || !this.addon) {
- return;
- }
- this._submitPanel.reason = this.reason;
- this._submitPanel.update();
- this._reasonsPanel.hidden = true;
- this._reasonsPanelButtons.hidden = true;
- this._submitPanel.hidden = false;
- this._submitPanelButtons.hidden = false;
- // Adjust the focused element when switching to the submit panel.
- this.focus();
- dispatchCustomEvent(this, "abuse-report:updated", {
- addonId: this.addonId,
- panel: "submit",
- });
- }
-
- switchToListMode() {
- if (!this.isConnected || !this.addon) {
- return;
- }
- this._submitPanel.hidden = true;
- this._submitPanelButtons.hidden = true;
- this._reasonsPanel.hidden = false;
- this._reasonsPanelButtons.hidden = false;
- // Adjust the focused element when switching back to the list of reasons.
- this.focus();
- dispatchCustomEvent(this, "abuse-report:updated", {
- addonId: this.addonId,
- panel: "reasons",
- });
- }
-
- get addon() {
- return this._report?.addon;
- }
-
- get addonId() {
- return this.addon?.id;
- }
-
- get addonName() {
- return this.addon?.name;
- }
-
- get addonType() {
- // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based
- // implementation is also removed.
- if (this.addon?.type === "sitepermission-deprecated") {
- return "sitepermission";
- }
- return this.addon?.type;
- }
-
- get addonCreator() {
- return this.addon?.creator;
- }
-
- get homepageURL() {
- return this.addon?.homepageURL || this.authorURL || "";
- }
-
- get authorName() {
- // The author name may be missing on some of the test extensions
- // (or for temporarily installed add-ons).
- return this.addonCreator?.name || "";
- }
-
- get authorURL() {
- return this.addonCreator?.url || "";
- }
-
- get iconURL() {
- if (this.addonType === "sitepermission") {
- return "chrome://mozapps/skin/extensions/category-sitepermission.svg";
- }
- return (
- this.addon?.iconURL ||
- // Some extensions (e.g. static theme addons) may not have an icon,
- // and so we fallback to use the generic extension icon.
- "chrome://mozapps/skin/extensions/extensionGeneric.svg"
- );
- }
-
- get supportURL() {
- let url = this.addon?.supportURL || this.homepageURL || "";
- if (!url && this.addonType === "sitepermission" && this.addon?.siteOrigin) {
- return this.addon.siteOrigin;
- }
- return url;
- }
-
- get message() {
- return this._form.elements.message.value;
- }
-
- get reason() {
- return this._form.elements.reason.value;
- }
-
- get modalTemplate() {
- return document.getElementById("tmpl-modal");
- }
-
- get template() {
- return document.getElementById("tmpl-abuse-report");
- }
-}
-
-customElements.define("abuse-report-reason-listitem", AbuseReasonListItem, {
- extends: "li",
-});
-customElements.define(
- "abuse-report-reason-suggestions",
- AbuseReasonSuggestions
-);
-customElements.define("abuse-report-reasons-panel", AbuseReasonsPanel);
-customElements.define("abuse-report-submit-panel", AbuseSubmitPanel);
-customElements.define("addon-abuse-report", AbuseReport);
-
-// The panel has been opened in a new dialog window.
-if (IS_DIALOG_WINDOW) {
- // CSS customizations when panel is in its own window
- // (vs. being an about:addons subframe).
- document.documentElement.className = "dialog-window";
-
- const { report, deferredReport, deferredReportPanel } =
- window.arguments[0].wrappedJSObject;
-
- window.addEventListener(
- "unload",
- () => {
- // If the window has been closed resolve the deferredReport
- // promise and reject the deferredReportPanel one, in case
- // they haven't been resolved yet.
- deferredReport.resolve({ userCancelled: true });
- deferredReportPanel.reject(new Error("report dialog closed"));
- },
- { once: true }
- );
-
- document.l10n.setAttributes(
- document.querySelector("head > title"),
- "abuse-report-dialog-title",
- {
- "addon-name": report.addon.name,
- }
- );
-
- const el = document.querySelector("addon-abuse-report");
- el.addEventListener("abuse-report:submit", () => {
- deferredReport.resolve({
- userCancelled: false,
- report,
- });
- });
- el.addEventListener(
- "abuse-report:cancel",
- () => {
- // Resolve the report panel deferred (in case the report
- // has been cancelled automatically before it has been fully
- // rendered, e.g. in case of non-supported addon types).
- deferredReportPanel.resolve(el);
- // Resolve the deferred report as cancelled.
- deferredReport.resolve({ userCancelled: true });
- },
- { once: true }
- );
-
- // Adjust window size (if needed) once the fluent strings have been
- // added to the document and the document has been flushed.
- el.addEventListener(
- "abuse-report:updated",
- async () => {
- const form = document.querySelector("form");
- await document.l10n.translateFragment(form);
- const { scrollWidth, scrollHeight } = await window.promiseDocumentFlushed(
- () => form
- );
- // Resolve promiseReportPanel once the panel completed the initial render
- // (used in tests).
- deferredReportPanel.resolve(el);
- if (
- window.innerWidth !== scrollWidth ||
- window.innerHeight !== scrollHeight
- ) {
- const width = window.outerWidth - window.innerWidth + scrollWidth;
- const height = window.outerHeight - window.innerHeight + scrollHeight;
- window.resizeTo(width, height);
- }
- },
- { once: true }
- );
- el.setAbuseReport(report);
-}
diff --git a/toolkit/mozapps/extensions/content/abuse-reports.js b/toolkit/mozapps/extensions/content/abuse-reports.js
index 38fa0f9f46..d978aa1293 100644
--- a/toolkit/mozapps/extensions/content/abuse-reports.js
+++ b/toolkit/mozapps/extensions/content/abuse-reports.js
@@ -4,188 +4,22 @@
/* eslint max-len: ["error", 80] */
/* import-globals-from aboutaddonsCommon.js */
-/* exported openAbuseReport */
+/* exported AbuseReporter, openAbuseReport */
/* global windowRoot */
/**
* This script is part of the HTML about:addons page and it provides some
- * helpers used for the Abuse Reporting submission (and related message bars).
+ * helpers used for abuse reports.
*/
const { AbuseReporter } = ChromeUtils.importESModule(
"resource://gre/modules/AbuseReporter.sys.mjs"
);
-// Message Bars definitions.
-const ABUSE_REPORT_MESSAGE_BARS = {
- // Idle message-bar (used while the submission is still ongoing).
- submitting: {
- actions: ["cancel"],
- l10n: {
- id: "abuse-report-messagebar-submitting2",
- actionIds: {
- cancel: "abuse-report-messagebar-action-cancel",
- },
- },
- },
- // Submitted report message-bar.
- submitted: {
- actions: ["remove", "keep"],
- dismissable: true,
- l10n: {
- id: "abuse-report-messagebar-submitted2",
- actionIdsPerAddonType: {
- extension: {
- remove: "abuse-report-messagebar-action-remove-extension",
- keep: "abuse-report-messagebar-action-keep-extension",
- },
- sitepermission: {
- remove: "abuse-report-messagebar-action-remove-sitepermission",
- keep: "abuse-report-messagebar-action-keep-sitepermission",
- },
- theme: {
- remove: "abuse-report-messagebar-action-remove-theme",
- keep: "abuse-report-messagebar-action-keep-theme",
- },
- },
- },
- },
- // Submitted report message-bar (with no remove actions).
- "submitted-no-remove-action": {
- dismissable: true,
- l10n: { id: "abuse-report-messagebar-submitted-noremove2" },
- },
- // Submitted report and remove addon message-bar.
- "submitted-and-removed": {
- dismissable: true,
- l10n: {
- idsPerAddonType: {
- extension: "abuse-report-messagebar-removed-extension2",
- sitepermission: "abuse-report-messagebar-removed-sitepermission2",
- theme: "abuse-report-messagebar-removed-theme2",
- },
- },
- },
- // The "aborted report" message bar is rendered as a generic informative one,
- // because aborting a report is triggered by a user choice.
- ERROR_ABORTED_SUBMIT: {
- type: "info",
- dismissable: true,
- l10n: { id: "abuse-report-messagebar-aborted2" },
- },
- // Errors message bars.
- ERROR_ADDON_NOTFOUND: {
- type: "error",
- dismissable: true,
- l10n: { id: "abuse-report-messagebar-error2" },
- },
- ERROR_CLIENT: {
- type: "error",
- dismissable: true,
- l10n: { id: "abuse-report-messagebar-error2" },
- },
- ERROR_NETWORK: {
- actions: ["retry", "cancel"],
- type: "error",
- l10n: {
- id: "abuse-report-messagebar-error2",
- actionIds: {
- retry: "abuse-report-messagebar-action-retry",
- cancel: "abuse-report-messagebar-action-cancel",
- },
- },
- },
- ERROR_RECENT_SUBMIT: {
- actions: ["retry", "cancel"],
- type: "error",
- l10n: {
- id: "abuse-report-messagebar-error-recent-submit2",
- actionIds: {
- retry: "abuse-report-messagebar-action-retry",
- cancel: "abuse-report-messagebar-action-cancel",
- },
- },
- },
- ERROR_SERVER: {
- actions: ["retry", "cancel"],
- type: "error",
- l10n: {
- id: "abuse-report-messagebar-error2",
- actionIds: {
- retry: "abuse-report-messagebar-action-retry",
- cancel: "abuse-report-messagebar-action-cancel",
- },
- },
- },
- ERROR_UNKNOWN: {
- actions: ["retry", "cancel"],
- type: "error",
- l10n: {
- id: "abuse-report-messagebar-error2",
- actionIds: {
- retry: "abuse-report-messagebar-action-retry",
- cancel: "abuse-report-messagebar-action-cancel",
- },
- },
- },
-};
+async function openAbuseReport({ addonId }) {
+ // TODO: `reportEntryPoint` is also passed to this function but we aren't
+ // using it currently. Maybe we should?
-async function openAbuseReport({ addonId, reportEntryPoint }) {
- try {
- const reportDialog = await AbuseReporter.openDialog(
- addonId,
- reportEntryPoint,
- window.docShell.chromeEventHandler
- );
-
- // Warn the user before the about:addons tab while an
- // abuse report dialog is still open, and close the
- // report dialog if the user choose to close the related
- // about:addons tab.
- const beforeunloadListener = evt => evt.preventDefault();
- const unloadListener = () => reportDialog.close();
- const clearUnloadListeners = () => {
- window.removeEventListener("beforeunload", beforeunloadListener);
- window.removeEventListener("unload", unloadListener);
- };
- window.addEventListener("beforeunload", beforeunloadListener);
- window.addEventListener("unload", unloadListener);
-
- reportDialog.promiseReport
- .then(
- report => {
- if (report) {
- submitReport({ report });
- }
- },
- err => {
- Cu.reportError(
- `Unexpected abuse report panel error: ${err} :: ${err.stack}`
- );
- reportDialog.close();
- }
- )
- .then(clearUnloadListeners);
- } catch (err) {
- // Log the detailed error to the browser console.
- Cu.reportError(err);
- document.dispatchEvent(
- new CustomEvent("abuse-report:create-error", {
- detail: {
- addonId,
- addon: err.addon,
- errorType: err.errorType,
- },
- })
- );
- }
-}
-
-// Unlike the openAbuseReport function, technically this method wouldn't need
-// to be async, but it is so that both the implementations will be providing
-// the same type signatures (returning a promise) to the callers, independently
-// from which abuse reporting feature is enabled.
-async function openAbuseReportAMOForm({ addonId }) {
const amoUrl = AbuseReporter.getAMOFormURL({ addonId });
windowRoot.ownerGlobal.openTrustedLinkIn(amoUrl, "tab", {
// Make sure the newly open tab is going to be focused, independently
@@ -194,183 +28,4 @@ async function openAbuseReportAMOForm({ addonId }) {
});
}
-window.openAbuseReport = AbuseReporter.amoFormEnabled
- ? openAbuseReportAMOForm
- : openAbuseReport;
-
-// Helper function used to create abuse report message bars in the
-// HTML about:addons page.
-function createReportMessageBar(
- definitionId,
- { addonId, addonName, addonType },
- { onclose, onaction } = {}
-) {
- const barInfo = ABUSE_REPORT_MESSAGE_BARS[definitionId];
- if (!barInfo) {
- throw new Error(`message-bar definition not found: ${definitionId}`);
- }
- const { dismissable, actions, type, l10n } = barInfo;
-
- // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based
- // implementation is also removed.
- const mappingAddonType =
- addonType === "sitepermission-deprecated" ? "sitepermission" : addonType;
-
- const getMessageL10n = () => {
- return l10n.idsPerAddonType
- ? l10n.idsPerAddonType[mappingAddonType]
- : l10n.id;
- };
- const getActionL10n = action => {
- return l10n.actionIdsPerAddonType
- ? l10n.actionIdsPerAddonType[mappingAddonType][action]
- : l10n.actionIds[action];
- };
-
- const messagebar = document.createElement("moz-message-bar");
-
- document.l10n.setAttributes(messagebar, getMessageL10n(), {
- "addon-name": addonName || addonId,
- });
- messagebar.setAttribute("data-l10n-attrs", "message");
-
- actions?.forEach(action => {
- const buttonEl = document.createElement("button");
- buttonEl.addEventListener("click", () => onaction && onaction(action));
- document.l10n.setAttributes(buttonEl, getActionL10n(action));
- buttonEl.setAttribute("slot", "actions");
- messagebar.appendChild(buttonEl);
- });
-
- messagebar.setAttribute("type", type || "info");
- messagebar.dismissable = dismissable;
- messagebar.addEventListener("message-bar:close", onclose, { once: true });
-
- document.getElementById("abuse-reports-messages").append(messagebar);
-
- document.dispatchEvent(
- new CustomEvent("abuse-report:new-message-bar", {
- detail: { definitionId, messagebar },
- })
- );
- return messagebar;
-}
-
-async function submitReport({ report }) {
- const { addon } = report;
- const addonId = addon.id;
- const addonName = addon.name;
- const addonType = addon.type;
-
- // Ensure that the tab that originated the report dialog is selected
- // when the user is submitting the report.
- const { gBrowser } = window.windowRoot.ownerGlobal;
- if (gBrowser && gBrowser.getTabForBrowser) {
- let tab = gBrowser.getTabForBrowser(window.docShell.chromeEventHandler);
- gBrowser.selectedTab = tab;
- }
-
- // Create a message bar while we are still submitting the report.
- const mbSubmitting = createReportMessageBar(
- "submitting",
- { addonId, addonName, addonType },
- {
- onaction: action => {
- if (action === "cancel") {
- report.abort();
- mbSubmitting.remove();
- }
- },
- }
- );
-
- try {
- await report.submit();
- mbSubmitting.remove();
-
- // Create a submitted message bar when the submission has been
- // successful.
- let barId;
- if (
- !(addon.permissions & AddonManager.PERM_CAN_UNINSTALL) &&
- !isPending(addon, "uninstall")
- ) {
- // Do not offer remove action if the addon can't be uninstalled.
- barId = "submitted-no-remove-action";
- } else if (report.reportEntryPoint === "uninstall") {
- // With reportEntryPoint "uninstall" a specific message bar
- // is going to be used.
- barId = "submitted-and-removed";
- } else {
- // All the other reportEntryPoint ("menu" and "toolbar_context_menu")
- // use the same kind of message bar.
- barId = "submitted";
- }
-
- const mbInfo = createReportMessageBar(
- barId,
- {
- addonId,
- addonName,
- addonType,
- },
- {
- onaction: action => {
- mbInfo.remove();
- // action "keep" doesn't require any further action,
- // just handle "remove".
- if (action === "remove") {
- report.addon.uninstall(true);
- }
- },
- }
- );
- } catch (err) {
- // Log the complete error in the console.
- console.error("Error submitting abuse report for", addonId, err);
- mbSubmitting.remove();
- // The report has a submission error, create a error message bar which
- // may optionally allow the user to retry to submit the same report.
- const barId =
- err.errorType in ABUSE_REPORT_MESSAGE_BARS
- ? err.errorType
- : "ERROR_UNKNOWN";
-
- const mbError = createReportMessageBar(
- barId,
- {
- addonId,
- addonName,
- addonType,
- },
- {
- onaction: action => {
- mbError.remove();
- switch (action) {
- case "retry":
- submitReport({ report });
- break;
- case "cancel":
- report.abort();
- break;
- }
- },
- }
- );
- }
-}
-
-document.addEventListener("abuse-report:submit", ({ detail }) => {
- submitReport(detail);
-});
-
-document.addEventListener("abuse-report:create-error", ({ detail }) => {
- const { addonId, addon, errorType } = detail;
- const barId =
- errorType in ABUSE_REPORT_MESSAGE_BARS ? errorType : "ERROR_UNKNOWN";
- createReportMessageBar(barId, {
- addonId,
- addonName: addon && addon.name,
- addonType: addon && addon.type,
- });
-});
+window.openAbuseReport = openAbuseReport;
diff --git a/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs b/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
index d7541167fa..e70322d3a4 100644
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.sys.mjs
@@ -200,6 +200,7 @@ const PROP_JSON_FIELDS = [
"incognito",
"userPermissions",
"optionalPermissions",
+ "requestedPermissions",
"sitePermissions",
"siteOrigin",
"icons",
@@ -1426,6 +1427,21 @@ AddonWrapper = class {
return addon.location.name == KEY_APP_PROFILE;
}
+ /**
+ * Returns true if the addon is configured to be installed
+ * by enterprise policy.
+ */
+ get isInstalledByEnterprisePolicy() {
+ const policySettings = Services.policies?.getExtensionSettings(this.id);
+ return ["force_installed", "normal_installed"].includes(
+ policySettings?.installation_mode
+ );
+ }
+
+ /**
+ * Required permissions that extension has access to based on its manifest.
+ * In mv3 this doesn't include host_permissions.
+ */
get userPermissions() {
return addonFor(this).userPermissions;
}
@@ -1434,6 +1450,49 @@ AddonWrapper = class {
return addonFor(this).optionalPermissions;
}
+ /**
+ * Additional permissions that extension is requesting in its manifest.
+ * Currently this is host_permissions in MV3.
+ */
+ get requestedPermissions() {
+ return addonFor(this).requestedPermissions;
+ }
+
+ /**
+ * A helper that returns all permissions for the install prompt.
+ */
+ get installPermissions() {
+ let required = this.userPermissions;
+ if (!required) {
+ return null;
+ }
+ let requested = this.requestedPermissions;
+ // Currently this can't result in duplicates, but if logic of what goes
+ // into these lists changes, make sure to check for dupes.
+ let perms = {
+ origins: required.origins.concat(requested?.origins ?? []),
+ permissions: required.permissions.concat(requested?.permissions ?? []),
+ };
+ return perms;
+ }
+
+ get optionalOriginsNormalized() {
+ const { permissions } = this.userPermissions;
+ const { origins } = this.optionalPermissions;
+
+ const { patterns } = new MatchPatternSet(origins, {
+ restrictSchemes: !(
+ this.isPrivileged && permissions?.includes("mozillaAddons")
+ ),
+ ignorePath: true,
+ });
+
+ // De-dup the normalized host permission patterns.
+ return patterns
+ ? [...new Set(patterns.map(matcher => matcher.pattern))]
+ : [];
+ }
+
isCompatibleWith(aAppVersion, aPlatformVersion) {
return addonFor(this).isCompatibleWith(aAppVersion, aPlatformVersion);
}
diff --git a/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs b/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
index 4a26785da8..5bb81a5f60 100644
--- a/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.sys.mjs
@@ -97,7 +97,6 @@ const PREF_XPI_FILE_WHITELISTED = "xpinstall.whitelist.fileRequest";
const PREF_XPI_WHITELIST_REQUIRED = "xpinstall.whitelist.required";
const PREF_XPI_WEAK_SIGNATURES_ALLOWED =
"xpinstall.signatures.weakSignaturesTemporarilyAllowed";
-const PREF_XPI_WEAK_SIGNATURES_ALLOWED_DEFAULT = true;
const PREF_SELECTED_THEME = "extensions.activeThemeID";
@@ -543,8 +542,9 @@ async function loadManifestFromWebManifest(aPackage, aLocation) {
// WebExtensions don't use iconURLs
addon.iconURL = null;
addon.icons = manifest.icons || {};
- addon.userPermissions = extension.manifestPermissions;
+ addon.userPermissions = extension.getRequiredPermissions();
addon.optionalPermissions = extension.manifestOptionalPermissions;
+ addon.requestedPermissions = extension.getRequestedPermissions();
addon.applyBackgroundUpdates = AddonManager.AUTOUPDATE_DEFAULT;
function getLocale(aLocale) {
@@ -1664,12 +1664,13 @@ class AddonInstall {
this.addon.signedDate &&
!hasStrongSignature(this.addon)
) {
- const addonAllowedByPolicies = Services.policies.getExtensionSettings(
- this.addon.id
- )?.temporarily_allow_weak_signatures;
+ const addonAllowedByPolicies =
+ Services.policies?.getExtensionSettings(
+ this.addon.id
+ )?.temporarily_allow_weak_signatures;
const globallyAllowedByPolicies =
- Services.policies.getExtensionSettings(
+ Services.policies?.getExtensionSettings(
"*"
)?.temporarily_allow_weak_signatures;
@@ -4411,10 +4412,7 @@ export var XPIInstall = {
},
isWeakSignatureInstallAllowed() {
- return Services.prefs.getBoolPref(
- PREF_XPI_WEAK_SIGNATURES_ALLOWED,
- PREF_XPI_WEAK_SIGNATURES_ALLOWED_DEFAULT
- );
+ return Services.prefs.getBoolPref(PREF_XPI_WEAK_SIGNATURES_ALLOWED, false);
},
getWeakSignatureInstallPrefName() {
diff --git a/toolkit/mozapps/extensions/jar.mn b/toolkit/mozapps/extensions/jar.mn
index a881ed3843..fde7b1fa23 100644
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -11,9 +11,6 @@ toolkit.jar:
content/mozapps/extensions/aboutaddonsCommon.js (content/aboutaddonsCommon.js)
content/mozapps/extensions/aboutaddons.css (content/aboutaddons.css)
content/mozapps/extensions/abuse-reports.js (content/abuse-reports.js)
- content/mozapps/extensions/abuse-report-frame.html (content/abuse-report-frame.html)
- content/mozapps/extensions/abuse-report-panel.css (content/abuse-report-panel.css)
- content/mozapps/extensions/abuse-report-panel.js (content/abuse-report-panel.js)
content/mozapps/extensions/drag-drop-addon-installer.js (content/drag-drop-addon-installer.js)
content/mozapps/extensions/shortcuts.css (content/shortcuts.css)
content/mozapps/extensions/shortcuts.js (content/shortcuts.js)
diff --git a/toolkit/mozapps/extensions/metrics.yaml b/toolkit/mozapps/extensions/metrics.yaml
index d5935ae436..0e36368b56 100644
--- a/toolkit/mozapps/extensions/metrics.yaml
+++ b/toolkit/mozapps/extensions/metrics.yaml
@@ -208,50 +208,6 @@ addons_manager:
type: quantity
expires: 132
- report:
- type: event
- description: An abuse report submitted by a user for a given extension.
- bugs:
- - https://bugzilla.mozilla.org/1544927
- - https://bugzilla.mozilla.org/1580561
- - https://bugzilla.mozilla.org/1590736
- - https://bugzilla.mozilla.org/1630596
- - https://bugzilla.mozilla.org/1672570
- - https://bugzilla.mozilla.org/1714251
- - https://bugzilla.mozilla.org/1749878
- - https://bugzilla.mozilla.org/1780746
- - https://bugzilla.mozilla.org/1781974
- - https://bugzilla.mozilla.org/1817100
- - https://bugzilla.mozilla.org/1820153
- data_reviews:
- - https://bugzilla.mozilla.org/show_bug.cgi?id=1820153#c3
- notification_emails:
- - addons-dev-internal@mozilla.com
- extra_keys:
- addon_id:
- description: Id of the addon being reported.
- type: string
- addon_type:
- description: |
- The type of the add-on being reported (missing on
- ERROR_ADDON_NOT_FOUND, ERROR_AMODETAILS_NOTFOUND
- and ERROR_AMODETAILS_FAILURE).
- type: string
- entry_point:
- description: |
- Report entry point, one of: amo, menu,
- toolbar_context_menu, unified_context_menu, uninstall.
- type: string
- error_type:
- description: |
- AbuseReport Error Type (included in case of submission
- failures). The error types include ERROR_ABORTED_SUBMIT,
- ERROR_ADDON_NOT_FOUND, ERROR_CLIENT, ERROR_NETWORK,
- ERROR_UNKNOWN, ERROR_RECENT_SUBMIT, ERROR_SERVER,
- ERROR_AMODETAILS_NOTFOUND, ERROR_AMODETAILS_FAILURE.
- type: string
- expires: 132
-
report_suspicious_site:
type: event
description: |
@@ -343,6 +299,9 @@ blocklist:
- rwu@mozilla.com
expires: 132
telemetry_mirror: BLOCKLIST_MLBF_SOURCE
+ no_lint:
+ - GIFFT_NON_PING_LIFETIME
+
mlbf_generation_time:
type: datetime
description: >
diff --git a/toolkit/mozapps/extensions/test/browser/browser.toml b/toolkit/mozapps/extensions/test/browser/browser.toml
index 6556b95584..b43064da16 100644
--- a/toolkit/mozapps/extensions/test/browser/browser.toml
+++ b/toolkit/mozapps/extensions/test/browser/browser.toml
@@ -55,6 +55,7 @@ prefs = [
["browser_addon_list_reordering.js"]
["browser_amo_abuse_report.js"]
+support-files = ["head_abuse_report.js"]
["browser_bug572561.js"]
@@ -77,12 +78,6 @@ skip-if = ["true"] # Bug 1626824
["browser_history_navigation.js"]
https_first_disabled = true
-["browser_html_abuse_report.js"]
-support-files = ["head_abuse_report.js"]
-
-["browser_html_abuse_report_dialog.js"]
-support-files = ["head_abuse_report.js"]
-
["browser_html_detail_permissions.js"]
["browser_html_detail_view.js"]
@@ -100,10 +95,10 @@ support-files = ["head_disco.js"]
["browser_html_list_view_recommendations.js"]
skip-if = ["a11y_checks"] # Bug 1858037 to investigate intermittent a11y_checks results for .popup-notification-primary-button.primary.footer-button
-["browser_html_message_bar.js"]
-
["browser_html_options_ui.js"]
+["browser_html_options_ui_dark_theme.js"]
+
["browser_html_options_ui_in_tab.js"]
["browser_html_pending_updates.js"]
@@ -169,9 +164,6 @@ https_first_disabled = true
["browser_webapi.js"]
-["browser_webapi_abuse_report.js"]
-support-files = ["head_abuse_report.js"]
-
["browser_webapi_access.js"]
https_first_disabled = true
diff --git a/toolkit/mozapps/extensions/test/browser/browser_amo_abuse_report.js b/toolkit/mozapps/extensions/test/browser/browser_amo_abuse_report.js
index b470cf2d82..7cd2d5d57d 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_amo_abuse_report.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_amo_abuse_report.js
@@ -15,26 +15,7 @@ add_setup(async () => {
],
});
- // Explicitly flip the amoFormEnabled pref on builds where the pref is
- // expected to not be set to true by default.
- if (AppConstants.MOZ_APP_NAME != "firefox") {
- await SpecialPowers.pushPrefEnv({
- set: [["extensions.abuseReport.amoFormEnabled", true]],
- });
- }
-
- const { AbuseReporter } = ChromeUtils.importESModule(
- "resource://gre/modules/AbuseReporter.sys.mjs"
- );
-
- Assert.equal(
- AbuseReporter.amoFormEnabled,
- true,
- "Expect AMO abuse report form to be enabled"
- );
-
- // Setting up MockProvider to mock various addon types
- // as installed.
+ // Setting up MockProvider to mock various addon types as installed.
await AbuseReportTestUtils.setup();
});
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report.js b/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report.js
deleted file mode 100644
index f20e5b357b..0000000000
--- a/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report.js
+++ /dev/null
@@ -1,1091 +0,0 @@
-/* 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/. */
-/* eslint max-len: ["error", 80] */
-
-loadTestSubscript("head_abuse_report.js");
-
-add_setup(async function () {
- // Make sure the integrated abuse report panel is the one enabled
- // while this test file runs (instead of the AMO hosted form).
- // NOTE: behaviors expected when amoFormEnabled is true are tested
- // in the separate browser_amo_abuse_report.js test file.
- await SpecialPowers.pushPrefEnv({
- set: [["extensions.abuseReport.amoFormEnabled", false]],
- });
- await AbuseReportTestUtils.setup();
-});
-
-/**
- * Base tests on abuse report panel webcomponents.
- */
-
-// This test case verified that the abuse report panels contains a radio
-// button for all the expected "abuse report reasons", they are grouped
-// together under the same form field named "reason".
-add_task(async function test_abusereport_issuelist() {
- const extension = await installTestExtension();
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(extension.id);
-
- const reasonsPanel = abuseReportEl._reasonsPanel;
- const radioButtons = reasonsPanel.querySelectorAll("[type=radio]");
- const selectedRadios = reasonsPanel.querySelectorAll("[type=radio]:checked");
-
- is(selectedRadios.length, 1, "Expect only one radio button selected");
- is(
- selectedRadios[0],
- radioButtons[0],
- "Expect the first radio button to be selected"
- );
-
- is(
- abuseReportEl.reason,
- radioButtons[0].value,
- `The reason property has the expected value: ${radioButtons[0].value}`
- );
-
- const reasons = Array.from(radioButtons).map(el => el.value);
- Assert.deepEqual(
- reasons.sort(),
- AbuseReportTestUtils.getReasons(abuseReportEl).sort(),
- `Got a radio button for the expected reasons`
- );
-
- for (const radio of radioButtons) {
- const reasonInfo = AbuseReportTestUtils.getReasonInfo(
- abuseReportEl,
- radio.value
- );
- const expectExampleHidden =
- reasonInfo && reasonInfo.isExampleHidden("extension");
- is(
- radio.parentNode.querySelector(".reason-example").hidden,
- expectExampleHidden,
- `Got expected visibility on the example for reason "${radio.value}"`
- );
- }
-
- info("Change the selected reason to " + radioButtons[3].value);
- radioButtons[3].checked = true;
- is(
- abuseReportEl.reason,
- radioButtons[3].value,
- "The reason property has the expected value"
- );
-
- await extension.unload();
- await closeAboutAddons();
-});
-
-// This test case verifies that the abuse report panel:
-// - switches from its "reasons list" mode to its "submit report" mode when the
-// "next" button is clicked
-// - goes back to the "reasons list" mode when the "go back" button is clicked
-// - the abuse report panel is closed when the "close" icon is clicked
-add_task(async function test_abusereport_submitpanel() {
- const extension = await installTestExtension();
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(extension.id);
-
- ok(
- !abuseReportEl._reasonsPanel.hidden,
- "The list of abuse reasons is the currently visible"
- );
- ok(
- abuseReportEl._submitPanel.hidden,
- "The submit panel is the currently hidden"
- );
-
- let onceUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "submit"
- );
- const MozButtonGroup =
- abuseReportEl.ownerGlobal.customElements.get("moz-button-group");
-
- ok(MozButtonGroup, "Expect MozButtonGroup custom element to be defined");
-
- const assertButtonInMozButtonGroup = (
- btnEl,
- { expectPrimary = false } = {}
- ) => {
- // Let's include the l10n id into the assertion messages,
- // to make it more likely to be immediately clear which
- // button hit a failure if any of the following assertion
- // fails.
- let l10nId = btnEl.getAttribute("data-l10n-id");
- is(
- btnEl.classList.contains("primary"),
- expectPrimary,
- `Expect button ${l10nId} to have${
- expectPrimary ? "" : " NOT"
- } the primary class set`
- );
-
- ok(
- btnEl.parentElement instanceof MozButtonGroup,
- `Expect button ${l10nId} to be slotted inside the expected custom element`
- );
-
- is(
- btnEl.getAttribute("slot"),
- expectPrimary ? "primary" : null,
- `Expect button ${l10nId} slot to ${
- expectPrimary ? "" : "NOT "
- } be set to primary`
- );
- };
-
- // Verify button group from the initial panel.
- assertButtonInMozButtonGroup(abuseReportEl._btnNext, { expectPrimary: true });
- assertButtonInMozButtonGroup(abuseReportEl._btnCancel, {
- expectPrimary: false,
- });
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnNext);
- await onceUpdated;
- // Verify button group from the submit panel mode.
- assertButtonInMozButtonGroup(abuseReportEl._btnSubmit, {
- expectPrimary: true,
- });
- assertButtonInMozButtonGroup(abuseReportEl._btnGoBack, {
- expectPrimary: false,
- });
- onceUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "reasons"
- );
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnGoBack);
- await onceUpdated;
-
- const onceReportClosed =
- AbuseReportTestUtils.promiseReportClosed(abuseReportEl);
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnCancel);
- await onceReportClosed;
-
- await extension.unload();
- await closeAboutAddons();
-});
-
-// This test case verifies that the abuse report panel sends the expected data
-// in the "abuse-report:submit" event detail.
-add_task(async function test_abusereport_submit() {
- // Reset the timestamp of the last report between tests.
- AbuseReporter._lastReportTimestamp = null;
- const extension = await installTestExtension();
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(extension.id);
-
- ok(
- !abuseReportEl._reasonsPanel.hidden,
- "The list of abuse reasons is the currently visible"
- );
-
- let onceUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "submit"
- );
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnNext);
- await onceUpdated;
-
- is(abuseReportEl.message, "", "The abuse report message is initially empty");
-
- info("Test typing a message in the abuse report submit panel textarea");
- const typedMessage = "Description of the extension abuse report";
-
- EventUtils.synthesizeComposition(
- {
- data: typedMessage,
- type: "compositioncommit",
- },
- abuseReportEl.ownerGlobal
- );
-
- is(
- abuseReportEl.message,
- typedMessage,
- "Got the expected typed message in the abuse report"
- );
-
- const expectedDetail = {
- addonId: extension.id,
- };
-
- const expectedReason = abuseReportEl.reason;
- const expectedMessage = abuseReportEl.message;
-
- function handleSubmitRequest({ request, response }) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json", false);
- response.write("{}");
- }
-
- let reportSubmitted;
- const onReportSubmitted = AbuseReportTestUtils.promiseReportSubmitHandled(
- ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- }
- );
-
- const onceReportClosed =
- AbuseReportTestUtils.promiseReportClosed(abuseReportEl);
-
- const onMessageBarsCreated = AbuseReportTestUtils.promiseMessageBars(2);
-
- const onceSubmitEvent = BrowserTestUtils.waitForEvent(
- abuseReportEl,
- "abuse-report:submit"
- );
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnSubmit);
- const submitEvent = await onceSubmitEvent;
-
- const actualDetail = {
- addonId: submitEvent.detail.addonId,
- };
- Assert.deepEqual(
- actualDetail,
- expectedDetail,
- "Got the expected detail in the abuse-report:submit event"
- );
-
- ok(
- submitEvent.detail.report,
- "Got a report object in the abuse-report:submit event detail"
- );
-
- // Verify that, when the "abuse-report:submit" has been sent,
- // the abuse report panel has been hidden, the report has been
- // submitted and the expected message bar is created in the
- // HTML about:addons page.
- info("Wait the report to be submitted to the api server");
- await onReportSubmitted;
- info("Wait the report panel to be closed");
- await onceReportClosed;
-
- is(
- reportSubmitted.addon,
- ADDON_ID,
- "Got the expected addon in the submitted report"
- );
- is(
- reportSubmitted.reason,
- expectedReason,
- "Got the expected reason in the submitted report"
- );
- is(
- reportSubmitted.message,
- expectedMessage,
- "Got the expected message in the submitted report"
- );
- is(
- reportSubmitted.report_entry_point,
- REPORT_ENTRY_POINT,
- "Got the expected report_entry_point in the submitted report"
- );
-
- info("Waiting the expected message bars to be created");
- const barDetails = await onMessageBarsCreated;
- is(barDetails.length, 2, "Expect two message bars to have been created");
- is(
- barDetails[0].definitionId,
- "submitting",
- "Got a submitting message bar as expected"
- );
- is(
- barDetails[1].definitionId,
- "submitted",
- "Got a submitted message bar as expected"
- );
-
- await extension.unload();
- await closeAboutAddons();
-});
-
-// This helper does verify that the abuse report panel contains the expected
-// suggestions when the selected reason requires it (and urls are being set
-// on the links elements included in the suggestions when expected).
-async function test_abusereport_suggestions(addonId) {
- const addon = await AddonManager.getAddonByID(addonId);
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(addonId);
-
- const {
- _btnNext,
- _btnGoBack,
- _reasonsPanel,
- _submitPanel,
- _submitPanel: { _suggestions },
- } = abuseReportEl;
-
- for (const reason of AbuseReportTestUtils.getReasons(abuseReportEl)) {
- const reasonInfo = AbuseReportTestUtils.getReasonInfo(
- abuseReportEl,
- reason
- );
-
- // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based
- // implementation is also removed.
- const addonType =
- addon.type === "sitepermission-deprecated"
- ? "sitepermission"
- : addon.type;
-
- if (reasonInfo.isReasonHidden(addonType)) {
- continue;
- }
-
- info(`Test suggestions for abuse reason "${reason}"`);
-
- // Select a reason with suggestions.
- let radioEl = abuseReportEl.querySelector(`#abuse-reason-${reason}`);
- ok(radioEl, `Found radio button for "${reason}"`);
- radioEl.checked = true;
-
- // Make sure the element localization is completed before
- // checking the content isn't empty.
- await document.l10n.translateFragment(radioEl);
-
- // Verify each radio button has a non-empty localized string.
- const localizedRadioContent = Array.from(
- radioEl.closest("label").querySelectorAll("[data-l10n-id]")
- ).filter(el => !el.hidden);
-
- for (let el of localizedRadioContent) {
- isnot(
- el.textContent,
- "",
- `Fluent string id '${el.getAttribute("data-l10n-id")}' missing`
- );
- }
-
- // Switch to the submit form with the current reason radio selected.
- let oncePanelUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "submit"
- );
- await AbuseReportTestUtils.clickPanelButton(_btnNext);
- await oncePanelUpdated;
-
- const localizedSuggestionsContent = Array.from(
- _suggestions.querySelectorAll("[data-l10n-id]")
- ).filter(el => !el.hidden);
-
- is(
- !_suggestions.hidden,
- !!reasonInfo.hasSuggestions,
- `Suggestions block has the expected visibility for "${reason}"`
- );
- if (reasonInfo.hasSuggestions) {
- ok(
- !!localizedSuggestionsContent.length,
- `Category suggestions should not be empty for "${reason}"`
- );
- } else {
- Assert.strictEqual(
- localizedSuggestionsContent.length,
- 0,
- `Category suggestions should be empty for "${reason}"`
- );
- }
-
- const extSupportLink = _suggestions.querySelector(
- ".extension-support-link"
- );
- if (extSupportLink) {
- is(
- extSupportLink.getAttribute("href"),
- BASE_TEST_MANIFEST.homepage_url,
- "Got the expected extension-support-url"
- );
- }
-
- const learnMoreLinks = [];
- learnMoreLinks.push(
- ..._suggestions.querySelectorAll(
- 'a[is="moz-support-link"], .abuse-policy-learnmore'
- )
- );
-
- if (learnMoreLinks.length) {
- is(
- _suggestions.querySelectorAll(
- 'a[is="moz-support-link"]:not([support-page])'
- ).length,
- 0,
- "Every SUMO link should point to a specific page"
- );
- ok(
- learnMoreLinks.every(el => el.getAttribute("target") === "_blank"),
- "All the learn more links have target _blank"
- );
- ok(
- learnMoreLinks.every(el => el.hasAttribute("href")),
- "All the learn more links have a url set"
- );
- }
-
- oncePanelUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "reasons"
- );
- await AbuseReportTestUtils.clickPanelButton(_btnGoBack);
- await oncePanelUpdated;
- ok(!_reasonsPanel.hidden, "Reasons panel should be visible");
- ok(_submitPanel.hidden, "Submit panel should be hidden");
- }
-
- await closeAboutAddons();
-}
-
-add_task(async function test_abusereport_suggestions_extension() {
- const EXT_ID = "test-extension-suggestions@mochi.test";
- const extension = await installTestExtension(EXT_ID);
- await test_abusereport_suggestions(EXT_ID);
- await extension.unload();
-});
-
-add_task(async function test_abusereport_suggestions_theme() {
- const THEME_ID = "theme@mochi.test";
- const theme = await installTestExtension(THEME_ID, "theme");
- await test_abusereport_suggestions(THEME_ID);
- await theme.unload();
-});
-
-// TODO(Bug 1789718): adapt to SitePermAddonProvider implementation.
-add_task(async function test_abusereport_suggestions_sitepermission() {
- const SITEPERM_ADDON_ID = "webmidi@mochi.test";
- const sitePermAddon = await installTestExtension(
- SITEPERM_ADDON_ID,
- "sitepermission-deprecated"
- );
- await test_abusereport_suggestions(SITEPERM_ADDON_ID);
- await sitePermAddon.unload();
-});
-
-// This test case verifies the message bars created on other
-// scenarios (e.g. report creation and submissions errors).
-//
-// TODO(Bug 1789718): adapt to SitePermAddonProvider implementation.
-add_task(async function test_abusereport_messagebars() {
- const EXT_ID = "test-extension-report@mochi.test";
- const EXT_ID2 = "test-extension-report-2@mochi.test";
- const THEME_ID = "test-theme-report@mochi.test";
- const SITEPERM_ADDON_ID = "webmidi-report@mochi.test";
- const extension = await installTestExtension(EXT_ID);
- const extension2 = await installTestExtension(EXT_ID2);
- const theme = await installTestExtension(THEME_ID, "theme");
- const sitePermAddon = await installTestExtension(
- SITEPERM_ADDON_ID,
- "sitepermission-deprecated"
- );
-
- async function assertMessageBars(
- expectedMessageBarIds,
- testSetup,
- testMessageBarDetails
- ) {
- await openAboutAddons();
- const expectedLength = expectedMessageBarIds.length;
- const onMessageBarsCreated =
- AbuseReportTestUtils.promiseMessageBars(expectedLength);
- // Reset the timestamp of the last report between tests.
- AbuseReporter._lastReportTimestamp = null;
- await testSetup();
- info(`Waiting for ${expectedLength} message-bars to be created`);
- const barDetails = await onMessageBarsCreated;
- Assert.deepEqual(
- barDetails.map(d => d.definitionId),
- expectedMessageBarIds,
- "Got the expected message bars"
- );
- if (testMessageBarDetails) {
- await testMessageBarDetails(barDetails);
- }
- await closeAboutAddons();
- }
-
- function setTestRequestHandler(responseStatus, responseData) {
- AbuseReportTestUtils.promiseReportSubmitHandled(({ request, response }) => {
- response.setStatusLine(request.httpVersion, responseStatus, "Error");
- response.write(responseData);
- });
- }
-
- await assertMessageBars(["ERROR_ADDON_NOTFOUND"], async () => {
- info("Test message bars on addon not found");
- AbuseReportTestUtils.triggerNewReport(
- "non-existend-addon-id@mochi.test",
- REPORT_ENTRY_POINT
- );
- });
-
- await assertMessageBars(["submitting", "ERROR_RECENT_SUBMIT"], async () => {
- info("Test message bars on recent submission");
- const promiseRendered = AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await promiseRendered;
- AbuseReporter.updateLastReportTimestamp();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- await assertMessageBars(["submitting", "ERROR_ABORTED_SUBMIT"], async () => {
- info("Test message bars on aborted submission");
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await AbuseReportTestUtils.promiseReportRendered();
- const { _report } = AbuseReportTestUtils.getReportPanel();
- _report.abort();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- await assertMessageBars(["submitting", "ERROR_SERVER"], async () => {
- info("Test message bars on server error");
- setTestRequestHandler(500);
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- await assertMessageBars(["submitting", "ERROR_CLIENT"], async () => {
- info("Test message bars on client error");
- setTestRequestHandler(400);
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- await assertMessageBars(["submitting", "ERROR_UNKNOWN"], async () => {
- info("Test message bars on unexpected status code");
- setTestRequestHandler(604);
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- await assertMessageBars(["submitting", "ERROR_UNKNOWN"], async () => {
- info("Test message bars on invalid json in the response data");
- setTestRequestHandler(200, "");
- AbuseReportTestUtils.triggerNewReport(EXT_ID, REPORT_ENTRY_POINT);
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- // Verify message bar on add-on without perm_can_uninstall.
- await assertMessageBars(
- ["submitting", "submitted-no-remove-action"],
- async () => {
- info("Test message bars on report submitted on an addon without remove");
- setTestRequestHandler(200, "{}");
- AbuseReportTestUtils.triggerNewReport(THEME_NO_UNINSTALL_ID, "menu");
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- }
- );
-
- // Verify the 3 expected entry points:
- // menu, toolbar_context_menu and uninstall
- // (See https://addons-server.readthedocs.io/en/latest/topics/api/abuse.html).
- await assertMessageBars(["submitting", "submitted"], async () => {
- info("Test message bars on report opened from addon options menu");
- setTestRequestHandler(200, "{}");
- AbuseReportTestUtils.triggerNewReport(EXT_ID, "menu");
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- });
-
- for (const extId of [EXT_ID, THEME_ID]) {
- await assertMessageBars(
- ["submitting", "submitted"],
- async () => {
- info(`Test message bars on ${extId} reported from toolbar contextmenu`);
- setTestRequestHandler(200, "{}");
- AbuseReportTestUtils.triggerNewReport(extId, "toolbar_context_menu");
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- },
- ([, submittedDetails]) => {
- const buttonsL10nId = Array.from(
- submittedDetails.messagebar.querySelectorAll("button")
- ).map(el => el.getAttribute("data-l10n-id"));
- if (extId === THEME_ID) {
- ok(
- buttonsL10nId.every(id => id.endsWith("-theme")),
- "submitted bar actions should use the Fluent id for themes"
- );
- } else {
- ok(
- buttonsL10nId.every(id => id.endsWith("-extension")),
- "submitted bar actions should use the Fluent id for extensions"
- );
- }
- }
- );
- }
-
- for (const extId of [EXT_ID2, THEME_ID, SITEPERM_ADDON_ID]) {
- const testFn = async () => {
- info(`Test message bars on ${extId} reported opened from addon removal`);
- setTestRequestHandler(200, "{}");
- AbuseReportTestUtils.triggerNewReport(extId, "uninstall");
- await AbuseReportTestUtils.promiseReportRendered();
- const addon = await AddonManager.getAddonByID(extId);
- // Ensure that the test extension is pending uninstall as it would be
- // when a user trigger this scenario on an actual addon uninstall.
- await addon.uninstall(true);
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- };
- const assertMessageBarDetails = async ([, submittedDetails]) =>
- AbuseReportTestUtils.assertFluentStrings(submittedDetails.messagebar);
- await assertMessageBars(
- ["submitting", "submitted-and-removed"],
- testFn,
- assertMessageBarDetails
- );
- }
-
- // Verify message bar on sitepermission add-on type.
- await assertMessageBars(
- ["submitting", "submitted"],
- async () => {
- info(
- "Test message bars for report submitted on an sitepermission addon type"
- );
- setTestRequestHandler(200, "{}");
- AbuseReportTestUtils.triggerNewReport(SITEPERM_ADDON_ID, "menu");
- await AbuseReportTestUtils.promiseReportRendered();
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
- },
- ([, submittedDetails]) =>
- AbuseReportTestUtils.assertFluentStrings(submittedDetails.messagebar)
- );
-
- await extension.unload();
- await extension2.unload();
- await theme.unload();
- await sitePermAddon.unload();
-});
-
-add_task(async function test_abusereport_from_aboutaddons_menu() {
- const EXT_ID = "test-report-from-aboutaddons-menu@mochi.test";
- const extension = await installTestExtension(EXT_ID);
-
- await openAboutAddons();
-
- AbuseReportTestUtils.assertReportPanelHidden();
-
- const addonCard = gManagerWindow.document.querySelector(
- `addon-list addon-card[addon-id="${extension.id}"]`
- );
- ok(addonCard, "Got the addon-card for the test extension");
-
- const reportButton = addonCard.querySelector("[action=report]");
- ok(reportButton, "Got the report action for the test extension");
-
- info("Click the report action and wait for the 'abuse-report:new' event");
-
- let onceReportOpened = AbuseReportTestUtils.promiseReportOpened({
- addonId: extension.id,
- reportEntryPoint: "menu",
- });
- reportButton.click();
- const panelEl = await onceReportOpened;
-
- await AbuseReportTestUtils.closeReportPanel(panelEl);
-
- await closeAboutAddons();
- await extension.unload();
-});
-
-add_task(async function test_abusereport_from_aboutaddons_remove() {
- const EXT_ID = "test-report-from-aboutaddons-remove@mochi.test";
-
- // Test on a theme addon to cover the report checkbox included in the
- // uninstall dialog also on a theme.
- const extension = await installTestExtension(EXT_ID, "theme");
-
- await openAboutAddons("theme");
-
- AbuseReportTestUtils.assertReportPanelHidden();
-
- const addonCard = gManagerWindow.document.querySelector(
- `addon-list addon-card[addon-id="${extension.id}"]`
- );
- ok(addonCard, "Got the addon-card for the test theme extension");
-
- const removeButton = addonCard.querySelector("[action=remove]");
- ok(removeButton, "Got the remove action for the test theme extension");
-
- // Prepare the mocked prompt service.
- const promptService = mockPromptService();
- promptService.confirmEx = createPromptConfirmEx({
- remove: true,
- report: true,
- });
-
- info("Click the report action and wait for the 'abuse-report:new' event");
-
- const onceReportOpened = AbuseReportTestUtils.promiseReportOpened({
- addonId: extension.id,
- reportEntryPoint: "uninstall",
- });
- removeButton.click();
- const panelEl = await onceReportOpened;
-
- await AbuseReportTestUtils.closeReportPanel(panelEl);
-
- await closeAboutAddons();
- await extension.unload();
-});
-
-add_task(async function test_abusereport_from_browserAction_remove() {
- const EXT_ID = "test-report-from-browseraction-remove@mochi.test";
- const xpiFile = AddonTestUtils.createTempWebExtensionFile({
- manifest: {
- ...BASE_TEST_MANIFEST,
- browser_action: {
- default_area: "navbar",
- },
- browser_specific_settings: { gecko: { id: EXT_ID } },
- },
- });
- const addon = await AddonManager.installTemporaryAddon(xpiFile);
-
- const buttonId = `${makeWidgetId(EXT_ID)}-browser-action`;
-
- async function promiseAnimationFrame() {
- await new Promise(resolve => window.requestAnimationFrame(resolve));
-
- let { tm } = Services;
- return new Promise(resolve => tm.dispatchToMainThread(resolve));
- }
-
- async function reportFromContextMenuRemove() {
- const menu = document.getElementById("toolbar-context-menu");
- const node = document.getElementById(CSS.escape(buttonId));
- const shown = BrowserTestUtils.waitForEvent(
- menu,
- "popupshown",
- "Wair for contextmenu popup"
- );
-
- // Wait for an animation frame as we do for the other mochitest-browser
- // tests related to the browserActions.
- await promiseAnimationFrame();
- EventUtils.synthesizeMouseAtCenter(node, { type: "contextmenu" });
- await shown;
-
- info(`Clicking on "Remove Extension" context menu item`);
- let removeExtension = menu.querySelector(
- ".customize-context-removeExtension"
- );
- removeExtension.click();
-
- return menu;
- }
-
- // Prepare the mocked prompt service.
- const promptService = mockPromptService();
- promptService.confirmEx = createPromptConfirmEx({
- remove: true,
- report: true,
- });
-
- await BrowserTestUtils.withNewTab("about:blank", async function () {
- info(`Open browserAction context menu in toolbar context menu`);
- let promiseMenu = reportFromContextMenuRemove();
-
- // Wait about:addons to be loaded.
- let browser = gBrowser.selectedBrowser;
- await BrowserTestUtils.browserLoaded(browser);
-
- let onceReportOpened = AbuseReportTestUtils.promiseReportOpened({
- addonId: EXT_ID,
- reportEntryPoint: "uninstall",
- managerWindow: browser.contentWindow,
- });
-
- is(
- browser.currentURI.spec,
- "about:addons",
- "about:addons tab currently selected"
- );
-
- let menu = await promiseMenu;
- menu.hidePopup();
-
- let panelEl = await onceReportOpened;
-
- await AbuseReportTestUtils.closeReportPanel(panelEl);
-
- let onceExtStarted = AddonTestUtils.promiseWebExtensionStartup(EXT_ID);
- addon.cancelUninstall();
- await onceExtStarted;
-
- // Reload the tab to verify Bug 1559124 didn't regressed.
- browser.contentWindow.location.reload();
- await BrowserTestUtils.browserLoaded(browser);
- is(
- browser.currentURI.spec,
- "about:addons",
- "about:addons tab currently selected"
- );
-
- onceReportOpened = AbuseReportTestUtils.promiseReportOpened({
- addonId: EXT_ID,
- reportEntryPoint: "uninstall",
- managerWindow: browser.contentWindow,
- });
-
- menu = await reportFromContextMenuRemove();
- info("Wait for the report panel");
- panelEl = await onceReportOpened;
-
- info("Wait for the report panel to be closed");
- await AbuseReportTestUtils.closeReportPanel(panelEl);
-
- menu.hidePopup();
-
- onceExtStarted = AddonTestUtils.promiseWebExtensionStartup(EXT_ID);
- addon.cancelUninstall();
- await onceExtStarted;
- });
-
- await addon.uninstall();
-});
-
-/*
- * Test report action hidden on non-supported extension types.
- */
-add_task(async function test_report_action_hidden_on_builtin_addons() {
- await openAboutAddons("theme");
- await AbuseReportTestUtils.assertReportActionHidden(
- gManagerWindow,
- DEFAULT_BUILTIN_THEME_ID
- );
- await closeAboutAddons();
-});
-
-add_task(async function test_report_action_hidden_on_system_addons() {
- await openAboutAddons("extension");
- await AbuseReportTestUtils.assertReportActionHidden(
- gManagerWindow,
- EXT_SYSTEM_ADDON_ID
- );
- await closeAboutAddons();
-});
-
-add_task(async function test_report_action_hidden_on_dictionary_addons() {
- await openAboutAddons("dictionary");
- await AbuseReportTestUtils.assertReportActionHidden(
- gManagerWindow,
- EXT_DICTIONARY_ADDON_ID
- );
- await closeAboutAddons();
-});
-
-add_task(async function test_report_action_hidden_on_langpack_addons() {
- await openAboutAddons("locale");
- await AbuseReportTestUtils.assertReportActionHidden(
- gManagerWindow,
- EXT_LANGPACK_ADDON_ID
- );
- await closeAboutAddons();
-});
-
-// This test verifies that triggering a report that would be immediately
-// cancelled (e.g. because abuse reports for that extension type are not
-// supported) the abuse report is being hidden as expected.
-add_task(async function test_report_hidden_on_report_unsupported_addontype() {
- await openAboutAddons();
-
- let onceCreateReportFailed = AbuseReportTestUtils.promiseMessageBars(1);
-
- AbuseReportTestUtils.triggerNewReport(EXT_UNSUPPORTED_TYPE_ADDON_ID, "menu");
-
- await onceCreateReportFailed;
-
- ok(!AbuseReporter.getOpenDialog(), "report dialog should not be open");
-
- await closeAboutAddons();
-});
-
-/*
- * Test regression fixes.
- */
-
-add_task(async function test_no_broken_suggestion_on_missing_supportURL() {
- const EXT_ID = "test-no-author@mochi.test";
- const extension = await installTestExtension(EXT_ID, "extension", {
- homepage_url: undefined,
- });
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(EXT_ID);
-
- info("Select broken as the abuse reason");
- abuseReportEl.querySelector("#abuse-reason-broken").checked = true;
-
- let oncePanelUpdated = AbuseReportTestUtils.promiseReportUpdated(
- abuseReportEl,
- "submit"
- );
- await AbuseReportTestUtils.clickPanelButton(abuseReportEl._btnNext);
- await oncePanelUpdated;
-
- const suggestionEl = abuseReportEl.querySelector(
- "abuse-report-reason-suggestions"
- );
- is(suggestionEl.reason, "broken", "Got the expected suggestion element");
- ok(suggestionEl.hidden, "suggestion element should be empty");
-
- await closeAboutAddons();
- await extension.unload();
-});
-
-// This test verify that the abuse report panel is opening the
-// author link using a null triggeringPrincipal.
-add_task(async function test_abusereport_open_author_url() {
- const abuseReportEl = await AbuseReportTestUtils.openReport(
- EXT_WITH_PRIVILEGED_URL_ID
- );
-
- const authorLink = abuseReportEl._linkAddonAuthor;
- ok(authorLink, "Got the author link element");
- is(
- authorLink.href,
- "about:config",
- "Got a privileged url in the link element"
- );
-
- SimpleTest.waitForExplicitFinish();
- let waitForConsole = new Promise(resolve => {
- SimpleTest.monitorConsole(resolve, [
- {
- message:
- // eslint-disable-next-line max-len
- /Security Error: Content at moz-nullprincipal:{.*} may not load or link to about:config/,
- },
- ]);
- });
-
- let tabSwitched = BrowserTestUtils.waitForEvent(gBrowser, "TabSwitchDone");
- authorLink.click();
- await tabSwitched;
-
- is(
- gBrowser.selectedBrowser.currentURI.spec,
- "about:blank",
- "Got about:blank loaded in the new tab"
- );
-
- SimpleTest.endMonitorConsole();
- await waitForConsole;
-
- BrowserTestUtils.removeTab(gBrowser.selectedTab);
- await closeAboutAddons();
-});
-
-add_task(async function test_no_report_checkbox_for_unsupported_addon_types() {
- async function test_report_checkbox_hidden(addon) {
- await openAboutAddons(addon.type);
-
- const addonCard = gManagerWindow.document.querySelector(
- `addon-list addon-card[addon-id="${addon.id}"]`
- );
- ok(addonCard, "Got the addon-card for the test extension");
-
- const removeButton = addonCard.querySelector("[action=remove]");
- ok(removeButton, "Got the remove action for the test extension");
-
- // Prepare the mocked prompt service.
- const promptService = mockPromptService();
- promptService.confirmEx = createPromptConfirmEx({
- remove: true,
- report: false,
- expectCheckboxHidden: true,
- });
-
- info("Click the report action and wait for the addon to be removed");
- const promiseCardRemoved = BrowserTestUtils.waitForEvent(
- addonCard.closest("addon-list"),
- "remove"
- );
- removeButton.click();
- await promiseCardRemoved;
-
- await closeAboutAddons();
- }
-
- const reportNotSupportedAddons = [
- {
- id: "fake-langpack-to-remove@mochi.test",
- name: "This is a fake langpack",
- version: "1.1",
- type: "locale",
- },
- {
- id: "fake-dictionary-to-remove@mochi.test",
- name: "This is a fake dictionary",
- version: "1.1",
- type: "dictionary",
- },
- ];
-
- AbuseReportTestUtils.createMockAddons(reportNotSupportedAddons);
-
- for (const { id } of reportNotSupportedAddons) {
- const addon = await AddonManager.getAddonByID(id);
- await test_report_checkbox_hidden(addon);
- }
-});
-
-add_task(async function test_author_hidden_when_missing() {
- const EXT_ID = "test-no-author@mochi.test";
- const extension = await installTestExtension(EXT_ID, "extension", {
- author: undefined,
- });
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(EXT_ID);
-
- const addon = await AddonManager.getAddonByID(EXT_ID);
-
- ok(!addon.creator, "addon.creator should not be undefined");
- ok(
- abuseReportEl._addonAuthorContainer.hidden,
- "author container should be hidden"
- );
-
- await closeAboutAddons();
- await extension.unload();
-});
-
-// Verify addon.siteOrigin is used as a fallback when homepage_url/developer.url
-// or support url are missing.
-//
-// TODO(Bug 1789718): adapt to SitePermAddonProvider implementation.
-add_task(async function test_siteperm_siteorigin_fallback() {
- const SITEPERM_ADDON_ID = "webmidi-site-origin@mochi.test";
- const sitePermAddon = await installTestExtension(
- SITEPERM_ADDON_ID,
- "sitepermission-deprecated",
- {
- homepage_url: undefined,
- }
- );
-
- const abuseReportEl = await AbuseReportTestUtils.openReport(
- SITEPERM_ADDON_ID
- );
- const addon = await AddonManager.getAddonByID(SITEPERM_ADDON_ID);
-
- ok(addon.siteOrigin, "addon.siteOrigin should not be undefined");
- ok(!addon.supportURL, "addon.supportURL should not be set");
- ok(!addon.homepageURL, "addon.homepageURL should not be set");
- is(
- abuseReportEl.supportURL,
- addon.siteOrigin,
- "Got the expected support_url"
- );
-
- await closeAboutAddons();
- await sitePermAddon.unload();
-});
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report_dialog.js b/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report_dialog.js
deleted file mode 100644
index 1efb28add3..0000000000
--- a/toolkit/mozapps/extensions/test/browser/browser_html_abuse_report_dialog.js
+++ /dev/null
@@ -1,185 +0,0 @@
-/* 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/. */
-/* eslint max-len: ["error", 80] */
-
-loadTestSubscript("head_abuse_report.js");
-
-add_setup(async function () {
- // Make sure the integrated abuse report panel is the one enabled
- // while this test file runs (instead of the AMO hosted form).
- // NOTE: behaviors expected when amoFormEnabled is true are tested
- // in the separate browser_amo_abuse_report.js test file.
- await SpecialPowers.pushPrefEnv({
- set: [["extensions.abuseReport.amoFormEnabled", false]],
- });
- await AbuseReportTestUtils.setup();
-});
-
-/**
- * Test tasks specific to the abuse report opened in its own dialog window.
- */
-
-add_task(async function test_close_icon_button_hidden_when_dialog() {
- const addonId = "addon-to-report@mochi.test";
- const extension = await installTestExtension(addonId);
-
- const reportDialog = await AbuseReporter.openDialog(
- addonId,
- "menu",
- gBrowser.selectedBrowser
- );
- await AbuseReportTestUtils.promiseReportDialogRendered();
-
- const panelEl = await reportDialog.promiseReportPanel;
-
- let promiseClosedWindow = waitClosedWindow();
-
- EventUtils.synthesizeKey("VK_RETURN", {}, panelEl.ownerGlobal);
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
-
- await promiseClosedWindow;
- ok(
- await reportDialog.promiseReport,
- "expect the report to not be cancelled by pressing enter"
- );
-
- await extension.unload();
-});
-
-add_task(async function test_report_triggered_when_report_dialog_is_open() {
- const addonId = "addon-to-report@mochi.test";
- const extension = await installTestExtension(addonId);
-
- const reportDialog = await AbuseReporter.openDialog(
- addonId,
- "menu",
- gBrowser.selectedBrowser
- );
- await AbuseReportTestUtils.promiseReportDialogRendered();
-
- let promiseClosedWindow = waitClosedWindow();
-
- const reportDialog2 = await AbuseReporter.openDialog(
- addonId,
- "menu",
- gBrowser.selectedBrowser
- );
-
- await promiseClosedWindow;
-
- // Trigger the report submit and check that the second report is
- // resolved as expected.
- await AbuseReportTestUtils.promiseReportDialogRendered();
-
- ok(
- !reportDialog.window || reportDialog.window.closed,
- "expect the first dialog to be closed"
- );
- ok(!!reportDialog2.window, "expect the second dialog to be open");
-
- is(
- reportDialog2.window,
- AbuseReportTestUtils.getReportDialog(),
- "Got a report dialog as expected"
- );
-
- AbuseReportTestUtils.triggerSubmit("fake-reason", "fake-message");
-
- // promiseReport is resolved to undefined if the report has been
- // cancelled, otherwise it is resolved to a report object.
- ok(
- !(await reportDialog.promiseReport),
- "expect the first report to be cancelled"
- );
- ok(
- !!(await reportDialog2.promiseReport),
- "expect the second report to be resolved"
- );
-
- await extension.unload();
-});
-
-add_task(async function test_report_dialog_window_closed_by_user() {
- const addonId = "addon-to-report@mochi.test";
- const extension = await installTestExtension(addonId);
-
- const reportDialog = await AbuseReporter.openDialog(
- addonId,
- "menu",
- gBrowser.selectedBrowser
- );
- await AbuseReportTestUtils.promiseReportDialogRendered();
-
- let promiseClosedWindow = waitClosedWindow();
-
- reportDialog.close();
-
- await promiseClosedWindow;
-
- ok(
- !(await reportDialog.promiseReport),
- "expect promiseReport to be resolved as user cancelled"
- );
-
- await extension.unload();
-});
-
-add_task(async function test_amo_details_for_not_installed_addon() {
- const addonId = "not-installed-addon@mochi.test";
- const fakeAMODetails = {
- name: "fake name",
- current_version: { version: "1.0" },
- type: "extension",
- icon_url: "http://test.addons.org/asserts/fake-icon-url.png",
- homepage: "http://fake.url/homepage",
- support_url: "http://fake.url/support",
- authors: [
- { name: "author1", url: "http://fake.url/author1" },
- { name: "author2", url: "http://fake.url/author2" },
- ],
- is_recommended: true,
- };
-
- AbuseReportTestUtils.amoAddonDetailsMap.set(addonId, fakeAMODetails);
- registerCleanupFunction(() =>
- AbuseReportTestUtils.amoAddonDetailsMap.clear()
- );
-
- const reportDialog = await AbuseReporter.openDialog(
- addonId,
- "menu",
- gBrowser.selectedBrowser
- );
-
- const reportEl = await reportDialog.promiseReportPanel;
-
- // Assert that the panel has been able to retrieve from AMO
- // all the addon details needed to render the panel correctly.
- is(reportEl.addonId, addonId, "Got the expected addonId");
- is(reportEl.addonName, fakeAMODetails.name, "Got the expected addon name");
- is(reportEl.addonType, fakeAMODetails.type, "Got the expected addon type");
- is(
- reportEl.authorName,
- fakeAMODetails.authors[0].name,
- "Got the first author name as expected"
- );
- is(
- reportEl.authorURL,
- fakeAMODetails.authors[0].url,
- "Got the first author url as expected"
- );
- is(reportEl.iconURL, fakeAMODetails.icon_url, "Got the expected icon url");
- is(
- reportEl.supportURL,
- fakeAMODetails.support_url,
- "Got the expected support url"
- );
- is(
- reportEl.homepageURL,
- fakeAMODetails.homepage,
- "Got the expected homepage url"
- );
-
- reportDialog.close();
-});
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js b/toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js
index 32543f3bc9..130375020d 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_detail_permissions.js
@@ -419,7 +419,11 @@ async function runTest(options) {
}
}
-async function testPermissionsView({ manifestV3enabled, manifest_version }) {
+async function testPermissionsView({
+ manifestV3enabled,
+ manifest_version,
+ expectGranted,
+}) {
await SpecialPowers.pushPrefEnv({
set: [["extensions.manifestV3.enabled", manifestV3enabled]],
});
@@ -438,7 +442,16 @@ async function testPermissionsView({ manifestV3enabled, manifest_version }) {
extension: extensions["addon1@mochi.test"],
permissions: ["<all_urls>", "tabs", "webNavigation"],
});
- } else {
+ }
+ if (manifest_version >= 3 && expectGranted) {
+ await runTest({
+ extension: extensions["addon1@mochi.test"],
+ permissions: ["tabs", "webNavigation"],
+ optional_permissions: ["<all_urls>"],
+ optional_enabled: ["<all_urls>"],
+ });
+ }
+ if (manifest_version >= 3 && !expectGranted) {
await runTest({
extension: extensions["addon1@mochi.test"],
permissions: ["tabs", "webNavigation"],
@@ -544,8 +557,28 @@ add_task(async function testPermissionsView_MV2_manifestV3enabled() {
await testPermissionsView({ manifestV3enabled: true, manifest_version: 2 });
});
+add_task(async function testPermissionsView_MV3_noInstallPrompt() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.originControls.grantByDefault", false]],
+ });
+ await testPermissionsView({
+ manifestV3enabled: true,
+ manifest_version: 3,
+ expectGranted: false,
+ });
+ await SpecialPowers.popPrefEnv();
+});
+
add_task(async function testPermissionsView_MV3() {
- await testPermissionsView({ manifestV3enabled: true, manifest_version: 3 });
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.originControls.grantByDefault", true]],
+ });
+ await testPermissionsView({
+ manifestV3enabled: true,
+ manifest_version: 3,
+ expectGranted: true,
+ });
+ await SpecialPowers.popPrefEnv();
});
add_task(async function testPermissionsViewStates() {
@@ -623,7 +656,10 @@ add_task(async function testPermissionsViewStates() {
add_task(async function testAllUrlsNotGrantedUnconditionally_MV3() {
await SpecialPowers.pushPrefEnv({
- set: [["extensions.manifestV3.enabled", true]],
+ set: [
+ ["extensions.manifestV3.enabled", true],
+ ["extensions.originControls.grantByDefault", false],
+ ],
});
const extension = ExtensionTestUtils.loadExtension({
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js b/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js
index 76e7f2b255..8930d6dede 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_detail_view.js
@@ -1260,7 +1260,7 @@ add_task(async function testGoBackButtonIsDisabledWhenHistoryIsEmpty() {
// When we have a fresh new tab, `about:addons` is opened in it.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, null);
// Simulate a click on "Manage extension" from a context menu.
- let win = await BrowserOpenAddonsMgr(viewID);
+ let win = await BrowserAddonUI.openAddonsMgr(viewID);
await assertBackButtonIsDisabled(win);
BrowserTestUtils.removeTab(tab);
@@ -1288,7 +1288,7 @@ add_task(async function testGoBackButtonIsDisabledWhenHistoryIsEmptyInNewTab() {
true
);
// Simulate a click on "Manage extension" from a context menu.
- let win = await BrowserOpenAddonsMgr(viewID);
+ let win = await BrowserAddonUI.openAddonsMgr(viewID);
let addonsTab = await addonsTabLoaded;
await assertBackButtonIsDisabled(win);
@@ -1309,7 +1309,7 @@ add_task(async function testGoBackButtonIsDisabledAfterBrowserBackButton() {
// When we have a fresh new tab, `about:addons` is opened in it.
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, null);
// Simulate a click on "Manage extension" from a context menu.
- let win = await BrowserOpenAddonsMgr(viewID);
+ let win = await BrowserAddonUI.openAddonsMgr(viewID);
await assertBackButtonIsDisabled(win);
// Navigate to the extensions list.
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js b/toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js
index 30cb45dc60..fdb0fabfd4 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js
@@ -653,11 +653,16 @@ add_task(async function checkDiscopaneNotice() {
"moz-message-bar.discopane-notice"
);
ok(messageBar, "Recommended notice should exist in extensions view");
+ is(
+ messageBar.getAttribute("role"),
+ "alert",
+ "Recommended notice is an alert"
+ );
await switchToDiscoView(win);
messageBar = win.document.querySelector("moz-message-bar.discopane-notice");
ok(messageBar, "Recommended notice should exist in disco view");
- messageBar.closeButtonEl.click();
+ messageBar.closeButton.click();
messageBar = win.document.querySelector("moz-message-bar.discopane-notice");
ok(!messageBar, "Recommended notice should not exist in disco view");
await switchToNonDiscoView(win);
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_message_bar.js b/toolkit/mozapps/extensions/test/browser/browser_html_message_bar.js
deleted file mode 100644
index b60baf8799..0000000000
--- a/toolkit/mozapps/extensions/test/browser/browser_html_message_bar.js
+++ /dev/null
@@ -1,185 +0,0 @@
-/* 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/. */
-
-/* eslint max-len: ["error", 80] */
-
-let htmlAboutAddonsWindow;
-
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-
-function clickElement(el) {
- el.dispatchEvent(new CustomEvent("click"));
-}
-
-function createMessageBar(messageBarStack, { attrs, children, onclose } = {}) {
- const win = messageBarStack.ownerGlobal;
- const messageBar = win.document.createElementNS(HTML_NS, "message-bar");
- if (attrs) {
- for (const [k, v] of Object.entries(attrs)) {
- messageBar.setAttribute(k, v);
- }
- }
- if (children) {
- if (Array.isArray(children)) {
- messageBar.append(...children);
- } else {
- messageBar.append(children);
- }
- }
- messageBar.addEventListener("message-bar:close", onclose, { once: true });
- messageBarStack.append(messageBar);
- return messageBar;
-}
-
-add_setup(async function () {
- htmlAboutAddonsWindow = await loadInitialView("extension");
- registerCleanupFunction(() => closeView(htmlAboutAddonsWindow));
-});
-
-add_task(async function test_message_bar_stack() {
- const win = htmlAboutAddonsWindow;
-
- let messageBarStack = win.document.getElementById("abuse-reports-messages");
-
- ok(messageBarStack, "Got a message-bar-stack in HTML about:addons page");
-
- is(
- messageBarStack.maxMessageBarCount,
- 3,
- "Got the expected max-message-bar-count property"
- );
-
- is(
- messageBarStack.childElementCount,
- 0,
- "message-bar-stack is initially empty"
- );
-});
-
-add_task(async function test_create_message_bar_create_and_onclose() {
- const win = htmlAboutAddonsWindow;
- const messageBarStack = win.document.getElementById("abuse-reports-messages");
-
- let messageEl = win.document.createElementNS(HTML_NS, "span");
- messageEl.textContent = "A message bar text";
- let buttonEl = win.document.createElementNS(HTML_NS, "button");
- buttonEl.textContent = "An action button";
-
- let messageBar;
- let onceMessageBarClosed = new Promise(resolve => {
- messageBar = createMessageBar(messageBarStack, {
- children: [messageEl, buttonEl],
- onclose: resolve,
- });
- });
-
- is(
- messageBarStack.childElementCount,
- 1,
- "message-bar-stack has a child element"
- );
- is(
- messageBarStack.firstElementChild,
- messageBar,
- "newly created message-bar added as message-bar-stack child element"
- );
-
- const slot = messageBar.shadowRoot.querySelector("slot");
- is(
- slot.assignedNodes()[0],
- messageEl,
- "Got the expected span element assigned to the message-bar slot"
- );
- is(
- slot.assignedNodes()[1],
- buttonEl,
- "Got the expected button element assigned to the message-bar slot"
- );
-
- let dismissed = BrowserTestUtils.waitForEvent(
- messageBar,
- "message-bar:user-dismissed"
- );
- info("Click the close icon on the newly created message-bar");
- clickElement(messageBar.closeButton);
- await dismissed;
-
- info("Expect the onclose function to be called");
- await onceMessageBarClosed;
-
- is(
- messageBarStack.childElementCount,
- 0,
- "message-bar-stack has no child elements"
- );
-});
-
-add_task(async function test_max_message_bar_count() {
- const win = htmlAboutAddonsWindow;
- const messageBarStack = win.document.getElementById("abuse-reports-messages");
-
- info("Create a new message-bar");
- let messageElement = document.createElementNS(HTML_NS, "span");
- messageElement = "message bar label";
-
- let onceMessageBarClosed = new Promise(resolve => {
- createMessageBar(messageBarStack, {
- children: messageElement,
- onclose: resolve,
- });
- });
-
- is(
- messageBarStack.childElementCount,
- 1,
- "message-bar-stack has the expected number of children"
- );
-
- info("Create 3 more message bars");
- const allBarsPromises = [];
- for (let i = 2; i <= 4; i++) {
- allBarsPromises.push(
- new Promise(resolve => {
- createMessageBar(messageBarStack, {
- attrs: { dismissable: "" },
- children: [messageElement, i],
- onclose: resolve,
- });
- })
- );
- }
-
- info("Expect first message-bar to closed automatically");
- await onceMessageBarClosed;
-
- is(
- messageBarStack.childElementCount,
- 3,
- "message-bar-stack has the expected number of children"
- );
-
- info("Click on close icon for the second message-bar");
- clickElement(messageBarStack.firstElementChild.closeButton);
-
- info("Expect the second message-bar to be closed");
- await allBarsPromises[0];
-
- is(
- messageBarStack.childElementCount,
- 2,
- "message-bar-stack has the expected number of children"
- );
-
- info("Clear the entire message-bar-stack content");
- messageBarStack.textContent = "";
-
- info("Expect all the created message-bar to be closed automatically");
- await Promise.all(allBarsPromises);
-
- is(
- messageBarStack.childElementCount,
- 0,
- "message-bar-stack has no child elements"
- );
-});
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_options_ui_dark_theme.js b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui_dark_theme.js
new file mode 100644
index 0000000000..9261fa0a7e
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_options_ui_dark_theme.js
@@ -0,0 +1,173 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { AddonTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/AddonTestUtils.sys.mjs"
+);
+
+AddonTestUtils.initMochitest(this);
+
+const LIGHT_SCHEME_BG = "rgb(255, 255, 255)";
+const LIGHT_SCHEME_FG = "rgb(0, 0, 0)";
+
+// "browser.display.background_color.dark" pref value ("#1C1B22") maps to:
+const DARK_SCHEME_BG = "rgb(28, 27, 34)";
+const DARK_SCHEME_FG = "rgb(251, 251, 254)";
+
+async function getColorsForOptionsUI({ browser_style, open_in_tab }) {
+ let extension = ExtensionTestUtils.loadExtension({
+ useAddonManager: "temporary",
+ manifest: {
+ options_ui: {
+ browser_style,
+ page: "options.html",
+ open_in_tab,
+ },
+ },
+ background() {
+ browser.test.onMessage.addListener(msg => {
+ browser.test.assertEq("openOptionsPage", msg, "expect openOptionsPage");
+ browser.runtime.openOptionsPage();
+ });
+ },
+ files: {
+ "options.html": `<style>:root { color-scheme: dark light; }</style>
+ <script src="options.js"></script>`,
+ "options.js": () => {
+ window.onload = () => {
+ browser.test.sendMessage("options_ui_opened");
+ };
+ },
+ },
+ });
+
+ await extension.startup();
+ extension.sendMessage("openOptionsPage");
+ await extension.awaitMessage("options_ui_opened");
+
+ const tab = gBrowser.selectedTab;
+ let optionsBrowser;
+ if (open_in_tab) {
+ optionsBrowser = tab.linkedBrowser;
+ is(
+ optionsBrowser.currentURI.spec,
+ `moz-extension://${extension.uuid}/options.html`,
+ "With open_in_tab=true, should open options.html in tab"
+ );
+ } else {
+ // When not opening in a new tab, the inline options page is used.
+ is(
+ tab.linkedBrowser.currentURI.spec,
+ "about:addons",
+ "Without open_in_tab, should open about:addons"
+ );
+ optionsBrowser = tab.linkedBrowser.contentDocument.getElementById(
+ "addon-inline-options"
+ );
+ is(
+ optionsBrowser.currentURI.spec,
+ `moz-extension://${extension.uuid}/options.html`,
+ "Found options.html in inline options browser"
+ );
+ }
+
+ let colors = await SpecialPowers.spawn(optionsBrowser, [], () => {
+ let style = content.getComputedStyle(content.document.body);
+ // Note: cannot use style.backgroundColor because it defaults to
+ // "transparent" (aka rgba(0, 0, 0, 0)) which is meaningless.
+ // So we have to use windowUtils.canvasBackgroundColor instead.
+ return {
+ bgColor: content.windowUtils.canvasBackgroundColor,
+ fgColor: style.color,
+ };
+ });
+
+ if (colors.bgColor === "rgba(0, 0, 0, 0)") {
+ // windowUtils.canvasBackgroundColor may still report a "transparent"
+ // background color when the options page is rendered inline in a <browser>
+ // at about:addons. In that case, the background color of the container
+ // element (i.e. the <browser>) is used to render the contents.
+ Assert.ok(!open_in_tab, "Background only transparent without open_in_tab");
+ let style = optionsBrowser.ownerGlobal.getComputedStyle(optionsBrowser);
+ colors.bgColor = style.backgroundColor;
+ }
+
+ BrowserTestUtils.removeTab(tab);
+
+ await extension.unload();
+ return colors;
+}
+
+add_setup(async () => {
+ // The test calls openOptionsPage, which may end up re-using an existing blank
+ // tab. Upon closing that, the browser window of the test would close and the
+ // test would get stuck. To avoid that, make sure that there is a dummy tab
+ // around that keeps the window open.
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "data:,");
+ registerCleanupFunction(() => {
+ BrowserTestUtils.removeTab(tab);
+ });
+});
+
+add_task(async function options_ui_open_in_tab_light() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 0]] });
+ // Note: browser_style:true should be no-op when open_in_tab:true.
+ // Therefore the result should be equivalent to the color of a normal web
+ // page, instead of options_ui_inline_light.
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: true, open_in_tab: true }),
+ { bgColor: LIGHT_SCHEME_BG, fgColor: LIGHT_SCHEME_FG }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function options_ui_open_in_tab_dark() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 1]] });
+ // Note: browser_style:true should be no-op when open_in_tab:true.
+ // Therefore the result should be equivalent to the color of a normal web
+ // page, instead of options_ui_inline_dark.
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: true, open_in_tab: true }),
+ { bgColor: DARK_SCHEME_BG, fgColor: DARK_SCHEME_FG }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function options_ui_light() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 0]] });
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: false, open_in_tab: false }),
+ { bgColor: LIGHT_SCHEME_BG, fgColor: LIGHT_SCHEME_FG }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function options_ui_dark() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 1]] });
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: false, open_in_tab: false }),
+ { bgColor: DARK_SCHEME_BG, fgColor: DARK_SCHEME_FG }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function options_ui_browser_style_true_light() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 0]] });
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: true, open_in_tab: false }),
+ // rgb(34, 36, 38) = color: #222426 from extension.css
+ { bgColor: LIGHT_SCHEME_BG, fgColor: "rgb(34, 36, 38)" }
+ );
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function options_ui_browser_style_true_dark() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.systemUsesDarkTheme", 1]] });
+ Assert.deepEqual(
+ await getColorsForOptionsUI({ browser_style: true, open_in_tab: false }),
+ { bgColor: DARK_SCHEME_BG, fgColor: DARK_SCHEME_FG }
+ );
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/toolkit/mozapps/extensions/test/browser/browser_html_scroll_restoration.js b/toolkit/mozapps/extensions/test/browser/browser_html_scroll_restoration.js
index e4d88bc19a..76471007ec 100644
--- a/toolkit/mozapps/extensions/test/browser/browser_html_scroll_restoration.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_html_scroll_restoration.js
@@ -91,11 +91,15 @@ async function waitForStableLayout(win) {
}
function isLayoutStable(win) {
- // <message-bar> elements may affect the layout of a page, and therefore we
- // should check whether its embedded style sheet has finished loading.
- for (let bar of win.document.querySelectorAll("message-bar")) {
- // Check for the existence of a CSS property from message-bar.css.
- if (!win.getComputedStyle(bar).getPropertyValue("--message-bar-icon-url")) {
+ // <moz-message-bar> elements may affect the layout of a page, and therefore
+ // we should check whether its embedded style sheet has finished loading.
+ for (let bar of win.document.querySelectorAll("moz-message-bar")) {
+ // Check for the existence of a CSS property from moz-message-bar.css.
+ if (
+ !win
+ .getComputedStyle(bar)
+ .getPropertyValue("--message-bar-background-color")
+ ) {
return false;
}
}
diff --git a/toolkit/mozapps/extensions/test/browser/browser_webapi_abuse_report.js b/toolkit/mozapps/extensions/test/browser/browser_webapi_abuse_report.js
deleted file mode 100644
index b9ea0f6a93..0000000000
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_abuse_report.js
+++ /dev/null
@@ -1,375 +0,0 @@
-/* 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/. */
-/* eslint max-len: ["error", 80] */
-
-loadTestSubscript("head_abuse_report.js");
-
-const TESTPAGE = `${SECURE_TESTROOT}webapi_checkavailable.html`;
-const TELEMETRY_EVENTS_FILTERS = {
- category: "addonsManager",
- method: "report",
-};
-const REPORT_PROP_NAMES = [
- "addon",
- "addon_signature",
- "reason",
- "message",
- "report_entry_point",
-];
-
-function getObjectProps(obj, propNames) {
- const res = {};
- for (const k of propNames) {
- res[k] = obj[k];
- }
- return res;
-}
-
-async function assertSubmittedReport(expectedReportProps) {
- let reportSubmitted;
- const onReportSubmitted = AbuseReportTestUtils.promiseReportSubmitHandled(
- ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- }
- );
-
- let panelEl = await AbuseReportTestUtils.promiseReportDialogRendered();
-
- let promiseWinClosed = waitClosedWindow();
- let promisePanelUpdated = AbuseReportTestUtils.promiseReportUpdated(
- panelEl,
- "submit"
- );
- panelEl._form.elements.reason.value = expectedReportProps.reason;
- AbuseReportTestUtils.clickPanelButton(panelEl._btnNext);
- await promisePanelUpdated;
-
- panelEl._form.elements.message.value = expectedReportProps.message;
- // Reset the timestamp of the last report between tests.
- AbuseReporter._lastReportTimestamp = null;
- AbuseReportTestUtils.clickPanelButton(panelEl._btnSubmit);
- await Promise.all([onReportSubmitted, promiseWinClosed]);
-
- ok(!panelEl.ownerGlobal, "Report dialog window is closed");
- Assert.deepEqual(
- getObjectProps(reportSubmitted, REPORT_PROP_NAMES),
- expectedReportProps,
- "Got the expected report data submitted"
- );
-}
-
-add_setup(async function () {
- await AbuseReportTestUtils.setup();
-
- await SpecialPowers.pushPrefEnv({
- set: [
- ["extensions.webapi.testing", true],
- ["extensions.abuseReport.amWebAPI.enabled", true],
- // Make sure the integrated abuse report panel is the one enabled
- // while this test file runs (instead of the AMO hosted form).
- // NOTE: behaviors expected when amoFormEnabled is true are tested
- // in the separate browser_amo_abuse_report.js test file.
- ["extensions.abuseReport.amoFormEnabled", false],
- ],
- });
-});
-
-add_task(async function test_report_installed_addon_cancelled() {
- Services.telemetry.clearEvents();
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- const extension = await installTestExtension(ADDON_ID);
-
- let reportEnabled = await SpecialPowers.spawn(browser, [], () => {
- return content.navigator.mozAddonManager.abuseReportPanelEnabled;
- });
-
- is(reportEnabled, true, "Expect abuseReportPanelEnabled to be true");
-
- info("Test reportAbuse result on user cancelled report");
-
- let promiseNewWindow = waitForNewWindow();
- let promiseWebAPIResult = SpecialPowers.spawn(
- browser,
- [ADDON_ID],
- addonId => content.navigator.mozAddonManager.reportAbuse(addonId)
- );
-
- let win = await promiseNewWindow;
- is(win, AbuseReportTestUtils.getReportDialog(), "Got the report dialog");
-
- let panelEl = await AbuseReportTestUtils.promiseReportDialogRendered();
-
- let promiseWinClosed = waitClosedWindow();
- AbuseReportTestUtils.clickPanelButton(panelEl._btnCancel);
- let reportResult = await promiseWebAPIResult;
- is(
- reportResult,
- false,
- "Expect reportAbuse to resolve to false on user cancelled report"
- );
- await promiseWinClosed;
- ok(!panelEl.ownerGlobal, "Report dialog window is closed");
-
- await extension.unload();
- });
-
- // Expect no telemetry events collected for user cancelled reports.
- TelemetryTestUtils.assertEvents([], TELEMETRY_EVENTS_FILTERS);
-});
-
-add_task(async function test_report_installed_addon_submitted() {
- Services.telemetry.clearEvents();
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- const extension = await installTestExtension(ADDON_ID);
-
- let promiseNewWindow = waitForNewWindow();
- let promiseWebAPIResult = SpecialPowers.spawn(browser, [ADDON_ID], id =>
- content.navigator.mozAddonManager.reportAbuse(id)
- );
- let win = await promiseNewWindow;
- is(win, AbuseReportTestUtils.getReportDialog(), "Got the report dialog");
-
- await assertSubmittedReport({
- addon: ADDON_ID,
- addon_signature: "missing",
- message: "fake report message",
- reason: "unwanted",
- report_entry_point: "amo",
- });
-
- let reportResult = await promiseWebAPIResult;
- is(
- reportResult,
- true,
- "Expect reportAbuse to resolve to false on user cancelled report"
- );
-
- await extension.unload();
- });
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: "amo",
- value: ADDON_ID,
- extra: { addon_type: "extension" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-});
-
-add_task(async function test_report_unknown_not_installed_addon() {
- const addonId = "unknown-addon@mochi.test";
- Services.telemetry.clearEvents();
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- let promiseWebAPIResult = SpecialPowers.spawn(browser, [addonId], id =>
- content.navigator.mozAddonManager.reportAbuse(id).catch(err => {
- return { name: err.name, message: err.message };
- })
- );
-
- await Assert.deepEqual(
- await promiseWebAPIResult,
- { name: "Error", message: "Error creating abuse report" },
- "Got the expected rejected error on reporting unknown addon"
- );
-
- ok(!AbuseReportTestUtils.getReportDialog(), "No report dialog is open");
- });
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: "amo",
- value: addonId,
- extra: { error_type: "ERROR_AMODETAILS_NOTFOUND" },
- },
- {
- object: "amo",
- value: addonId,
- extra: { error_type: "ERROR_ADDON_NOTFOUND" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-});
-
-add_task(async function test_report_not_installed_addon() {
- const addonId = "not-installed-addon@mochi.test";
- Services.telemetry.clearEvents();
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- const fakeAMODetails = {
- name: "fake name",
- current_version: { version: "1.0" },
- type: "extension",
- icon_url: "http://test.addons.org/asserts/fake-icon-url.png",
- homepage: "http://fake.url/homepage",
- authors: [{ name: "author1", url: "http://fake.url/author1" }],
- is_recommended: false,
- };
-
- AbuseReportTestUtils.amoAddonDetailsMap.set(addonId, fakeAMODetails);
- registerCleanupFunction(() =>
- AbuseReportTestUtils.amoAddonDetailsMap.clear()
- );
-
- let promiseNewWindow = waitForNewWindow();
-
- let promiseWebAPIResult = SpecialPowers.spawn(browser, [addonId], id =>
- content.navigator.mozAddonManager.reportAbuse(id)
- );
- let win = await promiseNewWindow;
- is(win, AbuseReportTestUtils.getReportDialog(), "Got the report dialog");
-
- await assertSubmittedReport({
- addon: addonId,
- addon_signature: "unknown",
- message: "fake report message",
- reason: "other",
- report_entry_point: "amo",
- });
-
- let reportResult = await promiseWebAPIResult;
- is(
- reportResult,
- true,
- "Expect reportAbuse to resolve to true on submitted report"
- );
- });
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: "amo",
- value: addonId,
- extra: { addon_type: "extension" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-});
-
-add_task(async function test_amo_report_on_report_already_inprogress() {
- const extension = await installTestExtension(ADDON_ID);
- const reportDialog = await AbuseReporter.openDialog(
- ADDON_ID,
- "menu",
- gBrowser.selectedBrowser
- );
- await AbuseReportTestUtils.promiseReportDialogRendered();
- ok(reportDialog.window, "Got an open report dialog");
-
- let promiseWinClosed = waitClosedWindow();
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- const promiseAMOResult = SpecialPowers.spawn(browser, [ADDON_ID], id =>
- content.navigator.mozAddonManager.reportAbuse(id)
- );
-
- await promiseWinClosed;
- ok(reportDialog.window.closed, "previous report dialog should be closed");
-
- is(
- await reportDialog.promiseAMOResult,
- undefined,
- "old report cancelled after AMO called mozAddonManager.reportAbuse"
- );
-
- const panelEl = await AbuseReportTestUtils.promiseReportDialogRendered();
-
- const { report } = AbuseReportTestUtils.getReportDialogParams();
- Assert.deepEqual(
- {
- reportEntryPoint: report.reportEntryPoint,
- addonId: report.addon.id,
- },
- {
- reportEntryPoint: "amo",
- addonId: ADDON_ID,
- },
- "Got the expected report from the opened report dialog"
- );
-
- promiseWinClosed = waitClosedWindow();
- AbuseReportTestUtils.clickPanelButton(panelEl._btnCancel);
- await promiseWinClosed;
-
- is(
- await promiseAMOResult,
- false,
- "AMO report request resolved to false on cancel button clicked"
- );
- });
-
- await extension.unload();
-});
-
-add_task(async function test_reject_on_unsupported_addon_types() {
- const addonId = "not-supported-addon-type@mochi.test";
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- const fakeAMODetails = {
- name: "fake name",
- current_version: { version: "1.0" },
- type: "fake-unsupported-addon-type",
- };
-
- AbuseReportTestUtils.amoAddonDetailsMap.set(addonId, fakeAMODetails);
- registerCleanupFunction(() =>
- AbuseReportTestUtils.amoAddonDetailsMap.clear()
- );
-
- let webAPIResult = await SpecialPowers.spawn(browser, [addonId], id =>
- content.navigator.mozAddonManager.reportAbuse(id).then(
- res => ({ gotRejection: false, result: res }),
- err => ({ gotRejection: true, message: err.message })
- )
- );
-
- Assert.deepEqual(
- webAPIResult,
- { gotRejection: true, message: "Error creating abuse report" },
- "Got the expected rejection from mozAddonManager.reportAbuse"
- );
- });
-});
-
-add_task(async function test_report_on_disabled_webapi() {
- await SpecialPowers.pushPrefEnv({
- set: [["extensions.abuseReport.amWebAPI.enabled", false]],
- });
-
- await BrowserTestUtils.withNewTab(TESTPAGE, async browser => {
- let reportEnabled = await SpecialPowers.spawn(browser, [], () => {
- return content.navigator.mozAddonManager.abuseReportPanelEnabled;
- });
-
- is(reportEnabled, false, "Expect abuseReportPanelEnabled to be false");
-
- info("Test reportAbuse result on report webAPI disabled");
-
- let promiseWebAPIResult = SpecialPowers.spawn(
- browser,
- ["an-addon@mochi.test"],
- addonId =>
- content.navigator.mozAddonManager.reportAbuse(addonId).catch(err => {
- return { name: err.name, message: err.message };
- })
- );
-
- Assert.deepEqual(
- await promiseWebAPIResult,
- { name: "Error", message: "amWebAPI reportAbuse not supported" },
- "Got the expected rejected error"
- );
- });
-
- await SpecialPowers.popPrefEnv();
-});
diff --git a/toolkit/mozapps/extensions/test/browser/head_abuse_report.js b/toolkit/mozapps/extensions/test/browser/head_abuse_report.js
index 78c9206e0a..173f6ab7ea 100644
--- a/toolkit/mozapps/extensions/test/browser/head_abuse_report.js
+++ b/toolkit/mozapps/extensions/test/browser/head_abuse_report.js
@@ -3,35 +3,16 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint max-len: ["error", 80] */
-/* exported installTestExtension, addCommonAbuseReportTestTasks,
- * createPromptConfirmEx, DEFAULT_BUILTIN_THEME_ID,
- * gManagerWindow, handleSubmitRequest, makeWidgetId,
- * waitForNewWindow, waitClosedWindow, AbuseReporter,
- * AbuseReporterTestUtils, AddonTestUtils
+/* exported AbuseReportTestUtils, openAboutAddons, closeAboutAddons,
+ * gManagerWindow
*/
/* global MockProvider, loadInitialView, closeView */
-const { AbuseReporter } = ChromeUtils.importESModule(
- "resource://gre/modules/AbuseReporter.sys.mjs"
-);
const { AddonTestUtils } = ChromeUtils.importESModule(
"resource://testing-common/AddonTestUtils.sys.mjs"
);
-const { ExtensionCommon } = ChromeUtils.importESModule(
- "resource://gre/modules/ExtensionCommon.sys.mjs"
-);
-const { makeWidgetId } = ExtensionCommon;
-
-const ADDON_ID = "test-extension-to-report@mochi.test";
-const REPORT_ENTRY_POINT = "menu";
-const BASE_TEST_MANIFEST = {
- name: "Fake extension to report",
- author: "Fake author",
- homepage_url: "https://fake.extension.url/",
-};
-const DEFAULT_BUILTIN_THEME_ID = "default-theme@mozilla.org";
const EXT_DICTIONARY_ADDON_ID = "fake-dictionary@mochi.test";
const EXT_LANGPACK_ADDON_ID = "fake-langpack@mochi.test";
const EXT_WITH_PRIVILEGED_URL_ID = "ext-with-privileged-url@mochi.test";
@@ -54,110 +35,6 @@ async function closeAboutAddons() {
}
}
-function waitForNewWindow() {
- return new Promise(resolve => {
- let listener = win => {
- Services.obs.removeObserver(listener, "toplevel-window-ready");
- resolve(win);
- };
-
- Services.obs.addObserver(listener, "toplevel-window-ready");
- });
-}
-
-function waitClosedWindow(win) {
- return new Promise(resolve => {
- function onWindowClosed() {
- if (win && !win.closed) {
- // If a specific window reference has been passed, then check
- // that the window is closed before resolving the promise.
- return;
- }
- Services.obs.removeObserver(onWindowClosed, "xul-window-destroyed");
- resolve();
- }
- Services.obs.addObserver(onWindowClosed, "xul-window-destroyed");
- });
-}
-
-async function installTestExtension(
- id = ADDON_ID,
- type = "extension",
- manifest = {}
-) {
- let additionalProps = {
- icons: {
- 32: "test-icon.png",
- },
- };
-
- switch (type) {
- case "theme":
- additionalProps = {
- ...additionalProps,
- theme: {
- colors: {
- frame: "#a14040",
- tab_background_text: "#fac96e",
- },
- },
- };
- break;
-
- // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based
- // implementation is also removed.
- case "sitepermission-deprecated":
- additionalProps = {
- name: "WebMIDI test addon for https://mochi.test",
- install_origins: ["https://mochi.test"],
- site_permissions: ["midi"],
- };
- break;
- case "extension":
- break;
- default:
- throw new Error(`Unexpected addon type: ${type}`);
- }
-
- const extensionOpts = {
- manifest: {
- ...BASE_TEST_MANIFEST,
- ...additionalProps,
- ...manifest,
- browser_specific_settings: { gecko: { id } },
- },
- useAddonManager: "temporary",
- };
-
- // TODO(Bug 1789718): Remove after the deprecated XPIProvider-based
- // implementation is also removed.
- if (type === "sitepermission-deprecated") {
- const xpi = AddonTestUtils.createTempWebExtensionFile(extensionOpts);
- const addon = await AddonManager.installTemporaryAddon(xpi);
- // The extension object that ExtensionTestUtils.loadExtension returns for
- // mochitest is pretty tight to the Extension class, and so for now this
- // returns a more minimal `extension` test object which only provides the
- // `unload` method.
- //
- // For the purpose of the abuse reports tests that are using this helper
- // this should be already enough.
- return {
- addon,
- unload: () => addon.uninstall(),
- };
- }
-
- const extension = ExtensionTestUtils.loadExtension(extensionOpts);
- await extension.startup();
- return extension;
-}
-
-function handleSubmitRequest({ request, response }) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json", false);
- response.write("{}");
-}
-
const AbuseReportTestUtils = {
_mockProvider: null,
_mockServer: null,
@@ -166,228 +43,14 @@ const AbuseReportTestUtils = {
// Mock addon details API endpoint.
amoAddonDetailsMap: new Map(),
- // Setup the test environment by setting the expected prefs and
- // initializing MockProvider and the mock AMO server.
+ // Setup the test environment by setting the expected prefs and initializing
+ // MockProvider.
async setup() {
- // Enable html about:addons and the abuse reporting and
- // set the api endpoints url to the mock service.
await SpecialPowers.pushPrefEnv({
- set: [
- ["extensions.abuseReport.enabled", true],
- ["extensions.abuseReport.url", "http://test.addons.org/api/report/"],
- [
- "extensions.abuseReport.amoDetailsURL",
- "http://test.addons.org/api/addons/addon/",
- ],
- ],
+ set: [["extensions.abuseReport.enabled", true]],
});
this._setupMockProvider();
- this._setupMockServer();
- },
-
- // Returns the currently open abuse report dialog window (if any).
- getReportDialog() {
- return Services.ww.getWindowByName("addons-abuse-report-dialog");
- },
-
- // Returns the parameters related to the report dialog (if any).
- getReportDialogParams() {
- const win = this.getReportDialog();
- return win && win.arguments[0] && win.arguments[0].wrappedJSObject;
- },
-
- // Returns a reference to the addon-abuse-report element from the currently
- // open abuse report.
- getReportPanel() {
- const win = this.getReportDialog();
- ok(win, "Got an abuse report dialog open");
- return win && win.document.querySelector("addon-abuse-report");
- },
-
- // Returns the list of abuse report reasons.
- getReasons(abuseReportEl) {
- return Object.keys(abuseReportEl.ownerGlobal.ABUSE_REPORT_REASONS);
- },
-
- // Returns the info related to a given abuse report reason.
- getReasonInfo(abuseReportEl, reason) {
- return abuseReportEl.ownerGlobal.ABUSE_REPORT_REASONS[reason];
- },
-
- async promiseReportOpened({ addonId, reportEntryPoint }) {
- let abuseReportEl;
-
- if (!this.getReportDialog()) {
- info("Wait for the report dialog window");
- const dialog = await waitForNewWindow();
- is(dialog, this.getReportDialog(), "Report dialog opened");
- }
-
- info("Wait for the abuse report panel render");
- abuseReportEl = await AbuseReportTestUtils.promiseReportDialogRendered();
-
- ok(abuseReportEl, "Got an abuse report panel");
- is(
- abuseReportEl.addon && abuseReportEl.addon.id,
- addonId,
- "Abuse Report panel rendered for the expected addonId"
- );
- is(
- abuseReportEl._report && abuseReportEl._report.reportEntryPoint,
- reportEntryPoint,
- "Abuse Report panel rendered for the expected reportEntryPoint"
- );
-
- return abuseReportEl;
- },
-
- // Return a promise resolved when the currently open report panel
- // is closed.
- // Also asserts that a specific report panel element has been closed,
- // if one has been provided through the optional panel parameter.
- async promiseReportClosed(panel) {
- const win = panel ? panel.ownerGlobal : this.getReportDialog();
- if (!win || win.closed) {
- throw Error("Expected report dialog not found or already closed");
- }
-
- await waitClosedWindow(win);
- // Assert that the panel has been closed (if the caller has passed it).
- if (panel) {
- ok(!panel.ownerGlobal, "abuse report dialog closed");
- }
- },
-
- // Returns a promise resolved when the report panel has been rendered
- // (rejects is there is no dialog currently open).
- async promiseReportDialogRendered() {
- const params = this.getReportDialogParams();
- if (!params) {
- throw new Error("abuse report dialog not found");
- }
- return params.promiseReportPanel;
- },
-
- // Given a `requestHandler` function, an HTTP server handler function
- // to use to handle a report submit request received by the mock AMO server),
- // returns a promise resolved when the mock AMO server has received and
- // handled the report submit request.
- async promiseReportSubmitHandled(requestHandler) {
- if (typeof requestHandler !== "function") {
- throw new Error("requestHandler should be a function");
- }
- return new Promise((resolve, reject) => {
- this._abuseRequestHandlers.unshift({ resolve, reject, requestHandler });
- });
- },
-
- // Return a promise resolved to the abuse report panel element,
- // once its rendering is completed.
- // If abuseReportEl is undefined, it looks for the currently opened
- // report panel.
- async promiseReportRendered(abuseReportEl) {
- let el = abuseReportEl;
-
- if (!el) {
- const win = this.getReportDialog();
- if (!win) {
- await waitForNewWindow();
- }
-
- el = await this.promiseReportDialogRendered();
- ok(el, "Got an abuse report panel");
- }
-
- return el._radioCheckedReason
- ? el
- : BrowserTestUtils.waitForEvent(
- el,
- "abuse-report:updated",
- "Wait the abuse report panel to be rendered"
- ).then(() => el);
- },
-
- // A promise resolved when the given abuse report panel element
- // has been rendered. If a panel name ("reasons" or "submit") is
- // passed as a second parameter, it also asserts that the panel is
- // updated to the expected view mode.
- async promiseReportUpdated(abuseReportEl, panel) {
- const evt = await BrowserTestUtils.waitForEvent(
- abuseReportEl,
- "abuse-report:updated",
- "Wait abuse report panel update"
- );
-
- if (panel) {
- is(evt.detail.panel, panel, `Got a "${panel}" update event`);
-
- const el = abuseReportEl;
- switch (evt.detail.panel) {
- case "reasons":
- ok(!el._reasonsPanel.hidden, "Reasons panel should be visible");
- ok(el._submitPanel.hidden, "Submit panel should be hidden");
- break;
- case "submit":
- ok(el._reasonsPanel.hidden, "Reasons panel should be hidden");
- ok(!el._submitPanel.hidden, "Submit panel should be visible");
- break;
- }
- }
- },
-
- // Returns a promise resolved once the expected number of abuse report
- // message bars have been created.
- promiseMessageBars(expectedMessageBarCount) {
- return new Promise(resolve => {
- const details = [];
- function listener(evt) {
- details.push(evt.detail);
- if (details.length >= expectedMessageBarCount) {
- cleanup();
- resolve(details);
- }
- }
- function cleanup() {
- if (gManagerWindow) {
- gManagerWindow.document.removeEventListener(
- "abuse-report:new-message-bar",
- listener
- );
- }
- }
- gManagerWindow.document.addEventListener(
- "abuse-report:new-message-bar",
- listener
- );
- });
- },
-
- async assertFluentStrings(containerEl) {
- // Make sure all localized elements have defined Fluent strings.
- let localizedEls = Array.from(
- containerEl.querySelectorAll("[data-l10n-id]")
- );
- if (containerEl.getAttribute("data-l10n-id")) {
- localizedEls.push(containerEl);
- }
- ok(localizedEls.length, "Got localized elements");
- for (let el of localizedEls) {
- const l10nId = el.getAttribute("data-l10n-id");
- const l10nAttrs = el.getAttribute("data-l10n-attrs");
- if (!l10nAttrs) {
- await TestUtils.waitForCondition(
- () => el.textContent !== "",
- `Element with Fluent id '${l10nId}' should not be empty`
- );
- } else {
- await TestUtils.waitForCondition(
- () => el.message !== "",
- `Message attribute of the element with Fluent id '${l10nId}'
- should not be empty`
- );
- }
- }
},
// Assert that the report action visibility on the addon card
@@ -419,68 +82,6 @@ const AbuseReportTestUtils = {
return this.assertReportActionVisibility(gManagerWindow, extId, true);
},
- // Assert that the report panel is hidden (or closed if the report
- // panel is opened in its own dialog window).
- async assertReportPanelHidden() {
- const win = this.getReportDialog();
- ok(!win, "Abuse Report dialog should be initially hidden");
- },
-
- createMockAddons(mockProviderAddons) {
- this._mockProvider.createAddons(mockProviderAddons);
- },
-
- async clickPanelButton(buttonEl, { label = undefined } = {}) {
- info(`Clicking the '${buttonEl.textContent.trim() || label}' button`);
- // NOTE: ideally this should synthesize the mouse event,
- // we call the click method to prevent intermittent timeouts
- // due to the mouse event not received by the target element.
- buttonEl.click();
- },
-
- triggerNewReport(addonId, reportEntryPoint) {
- gManagerWindow.openAbuseReport({ addonId, reportEntryPoint });
- },
-
- triggerSubmit(reason, message) {
- const reportEl =
- this.getReportDialog().document.querySelector("addon-abuse-report");
- reportEl._form.elements.message.value = message;
- reportEl._form.elements.reason.value = reason;
- reportEl.submit();
- },
-
- async openReport(addonId, reportEntryPoint = REPORT_ENTRY_POINT) {
- // Close the current about:addons window if it has been leaved open from
- // a previous test case failure.
- if (gManagerWindow) {
- await closeAboutAddons();
- }
-
- await openAboutAddons();
-
- let promiseReportPanel = waitForNewWindow().then(() =>
- this.promiseReportDialogRendered()
- );
-
- this.triggerNewReport(addonId, reportEntryPoint);
-
- const panelEl = await promiseReportPanel;
- await this.promiseReportRendered(panelEl);
- is(panelEl.addonId, addonId, `Got Abuse Report panel for ${addonId}`);
-
- return panelEl;
- },
-
- async closeReportPanel(panelEl) {
- const onceReportClosed = AbuseReportTestUtils.promiseReportClosed(panelEl);
-
- info("Cancel report and wait the dialog to be closed");
- panelEl.dispatchEvent(new CustomEvent("abuse-report:cancel"));
-
- await onceReportClosed;
- },
-
// Internal helper methods.
_setupMockProvider() {
@@ -529,87 +130,4 @@ const AbuseReportTestUtils = {
},
]);
},
-
- _setupMockServer() {
- if (this._mockServer) {
- return;
- }
-
- // Init test report api server.
- const server = AddonTestUtils.createHttpServer({
- hosts: ["test.addons.org"],
- });
- this._mockServer = server;
-
- server.registerPathHandler("/api/report/", (request, response) => {
- const stream = request.bodyInputStream;
- const buffer = NetUtil.readInputStream(stream, stream.available());
- const data = new TextDecoder().decode(buffer);
- const promisedHandler = this._abuseRequestHandlers.pop();
- if (promisedHandler) {
- const { requestHandler, resolve, reject } = promisedHandler;
- try {
- requestHandler({ data, request, response });
- resolve();
- } catch (err) {
- ok(false, `Unexpected requestHandler error ${err} ${err.stack}\n`);
- reject(err);
- }
- } else {
- ok(false, `Unexpected request: ${request.path} ${data}`);
- }
- });
-
- server.registerPrefixHandler("/api/addons/addon/", (request, response) => {
- const addonId = request.path.split("/").pop();
- if (!this.amoAddonDetailsMap.has(addonId)) {
- response.setStatusLine(request.httpVersion, 404, "Not Found");
- response.write(JSON.stringify({ detail: "Not found." }));
- } else {
- response.setStatusLine(request.httpVersion, 200, "Success");
- response.write(JSON.stringify(this.amoAddonDetailsMap.get(addonId)));
- }
- });
- server.registerPathHandler(
- "/assets/fake-icon-url.png",
- (request, response) => {
- response.setStatusLine(request.httpVersion, 200, "Success");
- response.write("");
- response.finish();
- }
- );
- },
};
-
-function createPromptConfirmEx({
- remove = false,
- report = false,
- expectCheckboxHidden = false,
-} = {}) {
- return (...args) => {
- const checkboxState = args.pop();
- const checkboxMessage = args.pop();
- is(
- checkboxState && checkboxState.value,
- false,
- "checkboxState should be initially false"
- );
- if (expectCheckboxHidden) {
- ok(
- !checkboxMessage,
- "Should not have a checkboxMessage in promptService.confirmEx call"
- );
- } else {
- ok(
- checkboxMessage,
- "Got a checkboxMessage in promptService.confirmEx call"
- );
- }
-
- // Report checkbox selected.
- checkboxState.value = report;
-
- // Remove accepted.
- return remove ? 0 : 1;
- };
-}
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js b/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js
index c94ef29e31..4fe0821c70 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_AbuseReporter.js
@@ -2,88 +2,21 @@
* http://creativecommons.org/publicdomain/zero/1.0/
*/
-const { AbuseReporter, AbuseReportError } = ChromeUtils.importESModule(
+const { AbuseReporter } = ChromeUtils.importESModule(
"resource://gre/modules/AbuseReporter.sys.mjs"
);
const { ClientID } = ChromeUtils.importESModule(
"resource://gre/modules/ClientID.sys.mjs"
);
-const { TelemetryController } = ChromeUtils.importESModule(
- "resource://gre/modules/TelemetryController.sys.mjs"
-);
-const { TelemetryTestUtils } = ChromeUtils.importESModule(
- "resource://testing-common/TelemetryTestUtils.sys.mjs"
-);
const APPNAME = "XPCShell";
const APPVERSION = "1";
const ADDON_ID = "test-addon@tests.mozilla.org";
-const ADDON_ID2 = "test-addon2@tests.mozilla.org";
const FAKE_INSTALL_INFO = {
source: "fake-Install:Source",
method: "fake:install method",
};
-const PREF_REQUIRED_LOCALE = "intl.locale.requested";
-const REPORT_OPTIONS = { reportEntryPoint: "menu" };
-const TELEMETRY_EVENTS_FILTERS = {
- category: "addonsManager",
- method: "report",
-};
-
-const FAKE_AMO_DETAILS = {
- name: {
- "en-US": "fake name",
- "it-IT": "fake it-IT name",
- },
- current_version: { version: "1.0" },
- type: "extension",
- is_recommended: true,
-};
-
-createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
-
-const server = createHttpServer({ hosts: ["test.addons.org"] });
-
-// Mock abuse report API endpoint.
-let apiRequestHandler;
-server.registerPathHandler("/api/report/", (request, response) => {
- const stream = request.bodyInputStream;
- const buffer = NetUtil.readInputStream(stream, stream.available());
- const data = new TextDecoder().decode(buffer);
- apiRequestHandler({ data, request, response });
-});
-
-// Mock addon details API endpoint.
-const amoAddonDetailsMap = new Map();
-server.registerPrefixHandler("/api/addons/addon/", (request, response) => {
- const addonId = request.path.split("/").pop();
- if (!amoAddonDetailsMap.has(addonId)) {
- response.setStatusLine(request.httpVersion, 404, "Not Found");
- response.write(JSON.stringify({ detail: "Not found." }));
- } else {
- response.setStatusLine(request.httpVersion, 200, "Success");
- response.write(JSON.stringify(amoAddonDetailsMap.get(addonId)));
- }
-});
-
-function getProperties(obj, propNames) {
- return propNames.reduce((acc, el) => {
- acc[el] = obj[el];
- return acc;
- }, {});
-}
-
-function handleSubmitRequest({ request, response }) {
- response.setStatusLine(request.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "application/json", false);
- response.write("{}");
-}
-
-function clearAbuseReportState() {
- // Clear the timestamp of the last submission.
- AbuseReporter._lastReportTimestamp = null;
-}
async function installTestExtension(overrideOptions = {}) {
const extOptions = {
@@ -104,34 +37,6 @@ async function installTestExtension(overrideOptions = {}) {
return { extension, addon };
}
-async function assertRejectsAbuseReportError(promise, errorType, errorInfo) {
- let error;
-
- await Assert.rejects(
- promise,
- err => {
- // Log the actual error to make investigating test failures easier.
- Cu.reportError(err);
- error = err;
- return err instanceof AbuseReportError;
- },
- `Got an AbuseReportError`
- );
-
- equal(error.errorType, errorType, "Got the expected errorType");
- equal(error.errorInfo, errorInfo, "Got the expected errorInfo");
- ok(
- error.message.includes(errorType),
- "errorType should be included in the error message"
- );
- if (errorInfo) {
- ok(
- error.message.includes(errorInfo),
- "errorInfo should be included in the error message"
- );
- }
-}
-
async function assertBaseReportData({ reportData, addon }) {
// Report properties related to addon metadata.
equal(reportData.addon, ADDON_ID, "Got expected 'addon'");
@@ -191,42 +96,10 @@ async function assertBaseReportData({ reportData, addon }) {
);
}
-add_task(async function test_setup() {
- Services.prefs.setCharPref(
- "extensions.abuseReport.url",
- "http://test.addons.org/api/report/"
- );
-
- Services.prefs.setCharPref(
- "extensions.abuseReport.amoDetailsURL",
- "http://test.addons.org/api/addons/addon"
- );
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "49");
+add_setup(async () => {
await promiseStartupManager();
- // Telemetry test setup needed to ensure that the builtin events are defined
- // and they can be collected and verified.
- await TelemetryController.testSetup();
-
- // This is actually only needed on Android, because it does not properly support unified telemetry
- // and so, if not enabled explicitly here, it would make these tests to fail when running on a
- // non-Nightly build.
- const oldCanRecordBase = Services.telemetry.canRecordBase;
- Services.telemetry.canRecordBase = true;
- registerCleanupFunction(() => {
- Services.telemetry.canRecordBase = oldCanRecordBase;
- });
-
- // Register a fake it-IT locale (used to test localized AMO details in some
- // of the test case defined in this test file).
- L10nRegistry.getInstance().registerSources([
- L10nFileSource.createMock(
- "mock",
- "app",
- ["it-IT", "fr-FR"],
- "resource://fake/locales/{locale}",
- []
- ),
- ]);
});
add_task(async function test_addon_report_data() {
@@ -260,37 +133,6 @@ add_task(async function test_addon_report_data() {
await extension3.unload();
});
-add_task(async function test_report_on_not_installed_addon() {
- Services.telemetry.clearEvents();
-
- // Make sure that the AMO addons details API endpoint is going to
- // return a 404 status for the not installed addon.
- amoAddonDetailsMap.delete(ADDON_ID);
-
- await assertRejectsAbuseReportError(
- AbuseReporter.createAbuseReport(ADDON_ID, REPORT_OPTIONS),
- "ERROR_ADDON_NOTFOUND"
- );
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: REPORT_OPTIONS.reportEntryPoint,
- value: ADDON_ID,
- extra: { error_type: "ERROR_AMODETAILS_NOTFOUND" },
- },
- {
- object: REPORT_OPTIONS.reportEntryPoint,
- value: ADDON_ID,
- extra: { error_type: "ERROR_ADDON_NOTFOUND" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-
- Services.telemetry.clearEvents();
-});
-
// This tests verifies how the addon installTelemetryInfo values are being
// normalized into the addon_install_source and addon_install_method
// expected by the API endpoint.
@@ -379,526 +221,3 @@ add_task(async function test_normalized_addon_install_source_and_method() {
await assertAddonInstallMethod(test, expect);
}
});
-
-add_task(async function test_report_create_and_submit() {
- Services.telemetry.clearEvents();
-
- // Override the test api server request handler, to be able to
- // intercept the submittions to the test api server.
- let reportSubmitted;
- apiRequestHandler = ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- };
-
- const { addon, extension } = await installTestExtension();
-
- const reportEntryPoint = "menu";
- const report = await AbuseReporter.createAbuseReport(ADDON_ID, {
- reportEntryPoint,
- });
-
- equal(report.addon, addon, "Got the expected addon property");
- equal(
- report.reportEntryPoint,
- reportEntryPoint,
- "Got the expected reportEntryPoint"
- );
-
- const baseReportData = await AbuseReporter.getReportData(addon);
- const reportProperties = {
- message: "test message",
- reason: "test-reason",
- };
-
- info("Submitting report");
- report.setMessage(reportProperties.message);
- report.setReason(reportProperties.reason);
- await report.submit();
-
- const expectedEntries = Object.entries({
- report_entry_point: reportEntryPoint,
- ...baseReportData,
- ...reportProperties,
- });
-
- for (const [expectedKey, expectedValue] of expectedEntries) {
- equal(
- reportSubmitted[expectedKey],
- expectedValue,
- `Got the expected submitted value for "${expectedKey}"`
- );
- }
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: reportEntryPoint,
- value: ADDON_ID,
- extra: { addon_type: "extension" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-
- await extension.unload();
-});
-
-add_task(async function test_error_recent_submit() {
- Services.telemetry.clearEvents();
- clearAbuseReportState();
-
- let reportSubmitted;
- apiRequestHandler = ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- };
-
- const { extension } = await installTestExtension();
- const report = await AbuseReporter.createAbuseReport(ADDON_ID, {
- reportEntryPoint: "uninstall",
- });
-
- const { extension: extension2 } = await installTestExtension({
- manifest: {
- browser_specific_settings: { gecko: { id: ADDON_ID2 } },
- name: "Test Extension2",
- },
- });
- const report2 = await AbuseReporter.createAbuseReport(
- ADDON_ID2,
- REPORT_OPTIONS
- );
-
- // Submit the two reports in fast sequence.
- report.setReason("reason1");
- report2.setReason("reason2");
- await report.submit();
- await assertRejectsAbuseReportError(report2.submit(), "ERROR_RECENT_SUBMIT");
- equal(
- reportSubmitted.reason,
- "reason1",
- "Server only received the data from the first submission"
- );
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: "uninstall",
- value: ADDON_ID,
- extra: { addon_type: "extension" },
- },
- {
- object: REPORT_OPTIONS.reportEntryPoint,
- value: ADDON_ID2,
- extra: {
- addon_type: "extension",
- error_type: "ERROR_RECENT_SUBMIT",
- },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-
- await extension.unload();
- await extension2.unload();
-});
-
-add_task(async function test_submission_server_error() {
- const { extension } = await installTestExtension();
-
- async function testErrorCode({
- responseStatus,
- responseText = "",
- expectedErrorType,
- expectedErrorInfo,
- expectRequest = true,
- }) {
- info(
- `Test expected AbuseReportError on response status "${responseStatus}"`
- );
- Services.telemetry.clearEvents();
- clearAbuseReportState();
-
- let requestReceived = false;
- apiRequestHandler = ({ request, response }) => {
- requestReceived = true;
- response.setStatusLine(request.httpVersion, responseStatus, "Error");
- response.write(responseText);
- };
-
- const report = await AbuseReporter.createAbuseReport(
- ADDON_ID,
- REPORT_OPTIONS
- );
- report.setReason("a-reason");
- const promiseSubmit = report.submit();
- if (typeof expectedErrorType === "string") {
- // Assert a specific AbuseReportError errorType.
- await assertRejectsAbuseReportError(
- promiseSubmit,
- expectedErrorType,
- expectedErrorInfo
- );
- } else {
- // Assert on a given Error class.
- await Assert.rejects(promiseSubmit, expectedErrorType);
- }
- equal(
- requestReceived,
- expectRequest,
- `${expectRequest ? "" : "Not "}received a request as expected`
- );
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: REPORT_OPTIONS.reportEntryPoint,
- value: ADDON_ID,
- extra: {
- addon_type: "extension",
- error_type:
- typeof expectedErrorType === "string"
- ? expectedErrorType
- : "ERROR_UNKNOWN",
- },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
- }
-
- await testErrorCode({
- responseStatus: 500,
- responseText: "A server error",
- expectedErrorType: "ERROR_SERVER",
- expectedErrorInfo: JSON.stringify({
- status: 500,
- responseText: "A server error",
- }),
- });
- await testErrorCode({
- responseStatus: 404,
- responseText: "Not found error",
- expectedErrorType: "ERROR_CLIENT",
- expectedErrorInfo: JSON.stringify({
- status: 404,
- responseText: "Not found error",
- }),
- });
- // Test response with unexpected status code.
- await testErrorCode({
- responseStatus: 604,
- responseText: "An unexpected status code",
- expectedErrorType: "ERROR_UNKNOWN",
- expectedErrorInfo: JSON.stringify({
- status: 604,
- responseText: "An unexpected status code",
- }),
- });
- // Test response status 200 with invalid json data.
- await testErrorCode({
- responseStatus: 200,
- expectedErrorType: /SyntaxError: JSON.parse/,
- });
-
- // Test on invalid url.
- Services.prefs.setCharPref(
- "extensions.abuseReport.url",
- "invalid-protocol://abuse-report"
- );
- await testErrorCode({
- expectedErrorType: "ERROR_NETWORK",
- expectRequest: false,
- });
-
- await extension.unload();
-});
-
-add_task(async function set_test_abusereport_url() {
- Services.prefs.setCharPref(
- "extensions.abuseReport.url",
- "http://test.addons.org/api/report/"
- );
-});
-
-add_task(async function test_submission_aborting() {
- Services.telemetry.clearEvents();
- clearAbuseReportState();
-
- const { extension } = await installTestExtension();
-
- // override the api request handler with one that is never going to reply.
- let receivedRequestsCount = 0;
- let resolvePendingResponses;
- const waitToReply = new Promise(
- resolve => (resolvePendingResponses = resolve)
- );
-
- const onRequestReceived = new Promise(resolve => {
- apiRequestHandler = ({ request, response }) => {
- response.processAsync();
- response.setStatusLine(request.httpVersion, 200, "OK");
- receivedRequestsCount++;
- resolve();
-
- // Keep the request pending until resolvePendingResponses have been
- // called.
- waitToReply.then(() => {
- response.finish();
- });
- };
- });
-
- const report = await AbuseReporter.createAbuseReport(
- ADDON_ID,
- REPORT_OPTIONS
- );
- report.setReason("a-reason");
- const promiseResult = report.submit();
-
- await onRequestReceived;
-
- Assert.greater(
- receivedRequestsCount,
- 0,
- "Got the expected number of requests"
- );
- Assert.strictEqual(
- await Promise.race([promiseResult, Promise.resolve("pending")]),
- "pending",
- "Submission fetch request should still be pending"
- );
-
- report.abort();
-
- await assertRejectsAbuseReportError(promiseResult, "ERROR_ABORTED_SUBMIT");
-
- TelemetryTestUtils.assertEvents(
- [
- {
- object: REPORT_OPTIONS.reportEntryPoint,
- value: ADDON_ID,
- extra: { addon_type: "extension", error_type: "ERROR_ABORTED_SUBMIT" },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-
- await extension.unload();
-
- // Unblock pending requests on the server request handler side, so that the
- // test file can shutdown (otherwise the test run will be stuck after this
- // task completed).
- resolvePendingResponses();
-});
-
-add_task(async function test_truncated_string_properties() {
- const generateString = len => new Array(len).fill("a").join("");
-
- const LONG_STRINGS_ADDON_ID = "addon-with-long-strings-props@mochi.test";
- const { extension } = await installTestExtension({
- manifest: {
- name: generateString(400),
- description: generateString(400),
- browser_specific_settings: { gecko: { id: LONG_STRINGS_ADDON_ID } },
- },
- });
-
- // Override the test api server request handler, to be able to
- // intercept the properties actually submitted.
- let reportSubmitted;
- apiRequestHandler = ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- };
-
- const report = await AbuseReporter.createAbuseReport(
- LONG_STRINGS_ADDON_ID,
- REPORT_OPTIONS
- );
-
- report.setMessage("fake-message");
- report.setReason("fake-reason");
- await report.submit();
-
- const expected = {
- addon_name: generateString(255),
- addon_summary: generateString(255),
- };
-
- Assert.deepEqual(
- {
- addon_name: reportSubmitted.addon_name,
- addon_summary: reportSubmitted.addon_summary,
- },
- expected,
- "Got the long strings truncated as expected"
- );
-
- await extension.unload();
-});
-
-add_task(async function test_report_recommended() {
- const NON_RECOMMENDED_ADDON_ID = "non-recommended-addon@mochi.test";
- const RECOMMENDED_ADDON_ID = "recommended-addon@mochi.test";
-
- const now = Date.now();
- const not_before = new Date(now - 3600000).toISOString();
- const not_after = new Date(now + 3600000).toISOString();
-
- const { extension: nonRecommended } = await installTestExtension({
- manifest: {
- name: "Fake non recommended addon",
- browser_specific_settings: { gecko: { id: NON_RECOMMENDED_ADDON_ID } },
- },
- });
-
- const { extension: recommended } = await installTestExtension({
- manifest: {
- name: "Fake recommended addon",
- browser_specific_settings: { gecko: { id: RECOMMENDED_ADDON_ID } },
- },
- files: {
- "mozilla-recommendation.json": {
- addon_id: RECOMMENDED_ADDON_ID,
- states: ["recommended"],
- validity: { not_before, not_after },
- },
- },
- });
-
- // Override the test api server request handler, to be able to
- // intercept the properties actually submitted.
- let reportSubmitted;
- apiRequestHandler = ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- };
-
- async function checkReportedSignature(addonId, expectedAddonSignature) {
- clearAbuseReportState();
- const report = await AbuseReporter.createAbuseReport(
- addonId,
- REPORT_OPTIONS
- );
- report.setMessage("fake-message");
- report.setReason("fake-reason");
- await report.submit();
- equal(
- reportSubmitted.addon_signature,
- expectedAddonSignature,
- `Got the expected addon_signature for ${addonId}`
- );
- }
-
- await checkReportedSignature(NON_RECOMMENDED_ADDON_ID, "signed");
- await checkReportedSignature(RECOMMENDED_ADDON_ID, "curated");
-
- await nonRecommended.unload();
- await recommended.unload();
-});
-
-add_task(async function test_query_amo_details() {
- async function assertReportOnAMODetails({ addonId, expectedReport } = {}) {
- // Clear last report timestamp and any telemetry event recorded so far.
- clearAbuseReportState();
- Services.telemetry.clearEvents();
-
- const report = await AbuseReporter.createAbuseReport(addonId, {
- reportEntryPoint: "menu",
- });
-
- let reportSubmitted;
- apiRequestHandler = ({ data, request, response }) => {
- reportSubmitted = JSON.parse(data);
- handleSubmitRequest({ request, response });
- };
-
- report.setMessage("fake message");
- report.setReason("reason1");
- await report.submit();
-
- Assert.deepEqual(
- expectedReport,
- getProperties(reportSubmitted, Object.keys(expectedReport)),
- "Got the expected report properties"
- );
-
- // Telemetry recorded for the successfully submitted report.
- TelemetryTestUtils.assertEvents(
- [
- {
- object: "menu",
- value: addonId,
- extra: { addon_type: FAKE_AMO_DETAILS.type },
- },
- ],
- TELEMETRY_EVENTS_FILTERS
- );
-
- clearAbuseReportState();
- }
-
- // Add the expected AMO addons details.
- const addonId = "not-installed-addon@mochi.test";
- amoAddonDetailsMap.set(addonId, FAKE_AMO_DETAILS);
-
- // Test on the default en-US locale.
- Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "en-US");
- let locale = Services.locale.appLocaleAsBCP47;
- equal(locale, "en-US", "Got the expected app locale set");
-
- let expectedReport = {
- addon: addonId,
- addon_name: FAKE_AMO_DETAILS.name[locale],
- addon_version: FAKE_AMO_DETAILS.current_version.version,
- addon_install_source: "not_installed",
- addon_install_method: null,
- addon_signature: "curated",
- };
-
- await assertReportOnAMODetails({ addonId, expectedReport });
-
- // Test with a non-default locale also available in the AMO details.
- Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "it-IT");
- locale = Services.locale.appLocaleAsBCP47;
- equal(locale, "it-IT", "Got the expected app locale set");
-
- expectedReport = {
- ...expectedReport,
- addon_name: FAKE_AMO_DETAILS.name[locale],
- };
- await assertReportOnAMODetails({ addonId, expectedReport });
-
- // Test with a non-default locale not available in the AMO details.
- Services.prefs.setCharPref(PREF_REQUIRED_LOCALE, "fr-FR");
- locale = Services.locale.appLocaleAsBCP47;
- equal(locale, "fr-FR", "Got the expected app locale set");
-
- expectedReport = {
- ...expectedReport,
- // Fallbacks on en-US for non available locales.
- addon_name: FAKE_AMO_DETAILS.name["en-US"],
- };
- await assertReportOnAMODetails({ addonId, expectedReport });
-
- Services.prefs.clearUserPref(PREF_REQUIRED_LOCALE);
-
- amoAddonDetailsMap.clear();
-});
-
-add_task(async function test_statictheme_normalized_into_type_theme() {
- const themeId = "not-installed-statictheme@mochi.test";
- amoAddonDetailsMap.set(themeId, {
- ...FAKE_AMO_DETAILS,
- type: "statictheme",
- });
-
- const report = await AbuseReporter.createAbuseReport(themeId, REPORT_OPTIONS);
-
- equal(report.addon.id, themeId, "Got a report for the expected theme id");
- equal(report.addon.type, "theme", "Got the expected addon type");
-
- amoAddonDetailsMap.clear();
-});
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js
index 1f52c8a8bc..bcb777e135 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_dictionary_webextension.js
@@ -10,7 +10,7 @@ XPCOMUtils.defineLazyServiceGetter(
"mozISpellCheckingEngine"
);
-add_task(async function setup() {
+add_setup(async function setup() {
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "61", "61");
// Initialize the URLPreloader so that we can load the built-in
@@ -18,6 +18,12 @@ add_task(async function setup() {
AddonTestUtils.initializeURLPreloader();
await promiseStartupManager();
+ // Sanity check, builtin dictionaries should be registered.
+ Assert.deepEqual(
+ spellCheck.getDictionaryList(),
+ ["en-US"],
+ "Expect en-US builtin dictionary to be registered"
+ );
// Starts collecting the Addon Manager Telemetry events.
AddonTestUtils.hookAMTelemetryEvents();
@@ -218,6 +224,13 @@ add_task(
const WORD = "Flehgragh";
add_task(async function test_registration() {
+ // Sanity check, builtin dictionaries should be registered.
+ Assert.deepEqual(
+ spellCheck.getDictionaryList(),
+ ["en-US"],
+ "Expect en-US builtin dictionary to be registered"
+ );
+
spellCheck.dictionaries = ["en-US"];
ok(!spellCheck.check(WORD), "Word should not pass check before add-on loads");
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
index 913c802609..3551b2c8dd 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_install.js
@@ -1,5 +1,14 @@
+AddonTestUtils.overrideCertDB();
+AddonTestUtils.usePrivilegedSignatures = id => id.startsWith("privileged-");
+
+// Some tests in this test file can't run on android build because
+// theme addon type isn't supported.
+const skipOnAndroid = () => ({
+ skip_if: () => AppConstants.platform === "android",
+});
+
let profileDir;
-add_task(async function setup() {
+add_setup(async function setup() {
profileDir = gProfD.clone();
profileDir.append("extensions");
@@ -19,7 +28,7 @@ const IMPLICIT_ID_ID = "{46607a7b-1b2a-40ce-9afe-91cda52c46a6}";
// webext-implicit-id.xpi has a minimal manifest with no
// applications or browser_specific_settings, so its id comes
// from its signature, which should be the ID constant defined below.
-add_task(async function test_implicit_id() {
+add_task(skipOnAndroid(), async function test_implicit_id() {
let addon = await promiseAddonByID(IMPLICIT_ID_ID);
equal(addon, null, "Add-on is not installed");
@@ -34,7 +43,7 @@ add_task(async function test_implicit_id() {
// We should also be able to install webext-implicit-id.xpi temporarily
// and it should look just like the regular install (ie, the ID should
// come from the signature)
-add_task(async function test_implicit_id_temp() {
+add_task(skipOnAndroid(), async function test_implicit_id_temp() {
let addon = await promiseAddonByID(IMPLICIT_ID_ID);
equal(addon, null, "Add-on is not installed");
@@ -602,6 +611,187 @@ add_task(async function test_permissions_prompt() {
await IOUtils.remove(xpi.path);
});
+// Check normalized optional origins.
+add_task(async function test_normalized_optional_origins() {
+ const assertAddonWrapperPermissionsProperties = async (
+ manifest,
+ expected
+ ) => {
+ let xpi = ExtensionTestCommon.generateXPI({ manifest });
+
+ let install = await AddonManager.getInstallForFile(xpi);
+
+ let perminfo;
+ let installPromptCompletedDeferred = Promise.withResolvers();
+ let installPromptShownDeferred = Promise.withResolvers();
+ install.promptHandler = info => {
+ perminfo = info;
+ installPromptShownDeferred.resolve();
+ return installPromptCompletedDeferred.promise;
+ };
+
+ const promiseInstalled = install.install();
+ info("Wait for the install prompt");
+ await installPromptShownDeferred.promise;
+
+ equal(
+ await promiseAddonByID(perminfo.addon.id),
+ null,
+ "Extension should not be installed yet"
+ );
+ notEqual(perminfo, undefined, "Permission handler was invoked");
+ notEqual(perminfo.addon, null, "Permission info includes the new addon");
+ equal(
+ perminfo.addon.isPrivileged,
+ expected.isPrivileged,
+ `Expect the addon to be ${expected.isPrivileged ? "" : "non-"}privileged`
+ );
+ let addon = perminfo.addon;
+ deepEqual(
+ addon.userPermissions,
+ expected.userPermissions,
+ "userPermissions are correct"
+ );
+ deepEqual(
+ addon.optionalPermissions,
+ expected.optionalPermissions,
+ "optionalPermissions are correct"
+ );
+ info("Assert normalized optional origins on non-installed extension");
+ deepEqual(
+ addon.optionalOriginsNormalized,
+ expected.optionalOriginsNormalized,
+ "optionalOriginsNormalized are correct"
+ );
+
+ installPromptCompletedDeferred.resolve();
+ await promiseInstalled;
+ addon = await promiseAddonByID(perminfo.addon.id);
+ notEqual(addon, null, "Extension was installed successfully");
+
+ info("Assert normalized optional origins again on installed extension");
+ deepEqual(
+ addon.optionalOriginsNormalized,
+ expected.optionalOriginsNormalized,
+ "Normalized origin permissions are correct"
+ );
+
+ await addon.uninstall();
+ await IOUtils.remove(xpi.path);
+ };
+
+ info(
+ "Test normalized optional origins on non-privileged ManifestV2 extension"
+ );
+ const manifestV2 = {
+ name: "permissions test mv2",
+ manifest_version: 2,
+ // Include a permission that would trigger an install time prompt.
+ permissions: ["tabs"],
+ // Include optional origins to be normalized.
+ // NOTE: we expect the normalized origins to be deduplicated.
+ optional_permissions: [
+ "http://*.example.com/",
+ "http://*.example.com/somepath/",
+ "*://example.org/*",
+ "*://example.org/another-path/*",
+ "file://*/*",
+ ],
+ };
+
+ await assertAddonWrapperPermissionsProperties(manifestV2, {
+ isPrivileged: false,
+ userPermissions: { permissions: manifestV2.permissions, origins: [] },
+ optionalPermissions: {
+ permissions: [],
+ origins: manifestV2.optional_permissions,
+ },
+ optionalOriginsNormalized: [
+ "http://*.example.com/*",
+ "*://example.org/*",
+ "file://*/*",
+ ],
+ });
+
+ info(
+ "Test normalized optional origins on non-privileged ManifestV3 extension"
+ );
+ const manifestV3 = {
+ name: "permissions test mv3",
+ manifest_version: 3,
+ // Include a permission that would trigger an install time prompt.
+ permissions: ["tabs"],
+
+ // Content Scripts match patterns are also expected to be part
+ // of the optional host permissions (and to be deduplicated).
+ content_scripts: [
+ {
+ matches: ["*://*/*"],
+ js: ["script.js"],
+ },
+ {
+ matches: ["*://example.org/*"],
+ js: ["script2.js"],
+ },
+ ],
+
+ // Include optional origins to be normalized.
+ host_permissions: [
+ "http://*.example.com/",
+ "*://example.org/*",
+ "file://*/*",
+ ],
+ };
+
+ await assertAddonWrapperPermissionsProperties(manifestV3, {
+ isPrivileged: false,
+ userPermissions: { permissions: manifestV3.permissions, origins: [] },
+ optionalPermissions: {
+ permissions: [],
+ origins: [...manifestV3.host_permissions, "*://*/*"],
+ },
+ optionalOriginsNormalized: [
+ "http://*.example.com/*",
+ "*://example.org/*",
+ "file://*/*",
+ "*://*/*",
+ ],
+ });
+
+ info("Test normalized optional origins on privileged ManifestV2 extension");
+ const manifestV2Privileged = {
+ name: "permissions test privileged mv2",
+ manifest_version: 2,
+ browser_specific_settings: { gecko: { id: "privileged-mv2@ext" } },
+ // Include a permission that would trigger an install time prompt.
+ permissions: ["tabs", "mozillaAddons"],
+ optional_permissions: [
+ "http://*.example.com/",
+ "*://example.org/*",
+ "file://*/*",
+ "resource://gre/",
+ ],
+ };
+
+ await assertAddonWrapperPermissionsProperties(manifestV2Privileged, {
+ isPrivileged: true,
+ userPermissions: {
+ permissions: manifestV2Privileged.permissions,
+ origins: [],
+ },
+ optionalPermissions: {
+ permissions: [],
+ origins: manifestV2Privileged.optional_permissions,
+ },
+ optionalOriginsNormalized: [
+ "http://*.example.com/*",
+ "*://example.org/*",
+ "file://*/*",
+ "resource://gre/*",
+ ],
+ });
+});
+
// Check permissions prompt cancellation
add_task(async function test_permissions_prompt_cancel() {
const manifest = {
diff --git a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml
index e8d3807ab9..a342fad700 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.toml
@@ -1,5 +1,4 @@
[DEFAULT]
-skip-if = ["os == 'android'"]
tags = "addons"
head = "head_addons.js"
firefox-appdir = "browser"
@@ -11,252 +10,374 @@ support-files = [
"../xpinstall/amosigned-sha1only.xpi",
]
+# TODO: Most tests are skipped on Android but we should re-enable them,
+# cf. Bug 1872867.
+
["test_AMBrowserExtensionsImport.js"]
+skip-if = ["os == 'android'"]
["test_AbuseReporter.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository_appIsShuttingDown.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository_cache.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository_cache_locale.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository_langpacks.js"]
+skip-if = ["os == 'android'"]
["test_AddonRepository_paging.js"]
+skip-if = ["os == 'android'"]
["test_ProductAddonChecker.js"]
+skip-if = ["os == 'android'"]
["test_ProductAddonChecker_signatures.js"]
head = "head_addons.js head_cert_handling.js"
+skip-if = ["os == 'android'"]
["test_QuarantinedDomains_AMRemoteSettings.js"]
head = "head_addons.js head_amremotesettings.js ../../../../components/extensions/test/xpcshell/head_telemetry.js"
+skip-if = ["os == 'android'"]
["test_QuarantinedDomains_AddonWrapper.js"]
+skip-if = ["os == 'android'"]
["test_XPIStates.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
["test_XPIcancel.js"]
+skip-if = ["os == 'android'"]
["test_addonStartup.js"]
+skip-if = ["os == 'android'"]
["test_addon_manager_telemetry_events.js"]
+skip-if = ["os == 'android'"]
["test_amo_stats_telemetry.js"]
+skip-if = ["os == 'android'"]
["test_aom_startup.js"]
+skip-if = ["os == 'android'"]
["test_bad_json.js"]
+skip-if = ["os == 'android'"]
["test_badschema.js"]
+skip-if = ["os == 'android'"]
["test_builtin_location.js"]
+skip-if = ["os == 'android'"]
["test_cacheflush.js"]
+skip-if = ["os == 'android'"]
["test_childprocess.js"]
head = ""
+skip-if = ["os == 'android'"]
["test_colorways_builtin_theme_upgrades.js"]
-skip-if = ["appname == 'thunderbird'"] # Bug 1809438 - No colorways in Thunderbird
+skip-if = [
+ "appname == 'thunderbird'", # Bug 1809438 - No colorways in Thunderbird
+ "os == 'android'",
+]
["test_cookies.js"]
+skip-if = ["os == 'android'"]
["test_corrupt.js"]
+skip-if = ["os == 'android'"]
["test_crash_annotation_quoting.js"]
+skip-if = ["os == 'android'"]
["test_db_path.js"]
head = ""
+skip-if = ["os == 'android'"]
["test_delay_update_webextension.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_dependencies.js"]
+skip-if = ["os == 'android'"]
["test_dictionary_webextension.js"]
+skip-if = ["os == 'android'"]
["test_distribution.js"]
+skip-if = ["os == 'android'"]
["test_distribution_langpack.js"]
+skip-if = ["os == 'android'"]
["test_embedderDisabled.js"]
+skip-if = ["os == 'android'"]
["test_error.js"]
-skip-if = ["os == 'win'"] # Bug 1508482
+skip-if = [
+ "os == 'win'", # Bug 1508482
+ "os == 'android'",
+]
["test_ext_management.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_general.js"]
+skip-if = ["os == 'android'"]
["test_getInstallSourceFromHost.js"]
+skip-if = ["os == 'android'"]
["test_gmpProvider.js"]
-skip-if = ["appname != 'firefox'"]
+skip-if = [
+ "appname != 'firefox'",
+ "os == 'android'",
+]
["test_harness.js"]
+skip-if = ["os == 'android'"]
["test_hidden.js"]
+skip-if = ["os == 'android'"]
["test_install.js"]
+skip-if = ["os == 'android'"]
["test_installOrigins.js"]
+skip-if = ["os == 'android'"]
["test_install_cancel.js"]
+skip-if = ["os == 'android'"]
["test_install_file_change.js"]
+skip-if = ["os == 'android'"]
["test_install_icons.js"]
+skip-if = ["os == 'android'"]
["test_installtrigger_deprecation.js"]
head = "head_addons.js head_amremotesettings.js"
+skip-if = ["os == 'android'"]
["test_installtrigger_schemes.js"]
+skip-if = ["os == 'android'"]
["test_isDebuggable.js"]
+skip-if = ["os == 'android'"]
["test_isReady.js"]
+skip-if = ["os == 'android'"]
["test_loadManifest_isPrivileged.js"]
+skip-if = ["os == 'android'"]
["test_locale.js"]
+skip-if = ["os == 'android'"]
["test_moved_extension_metadata.js"]
skip-if = ["true"] # bug 1777900
["test_no_addons.js"]
+skip-if = ["os == 'android'"]
["test_nodisable_hidden.js"]
+skip-if = ["os == 'android'"]
["test_onPropertyChanged_appDisabled.js"]
head = "head_addons.js head_compat.js"
-skip-if = ["tsan"] # Times out, bug 1674773
+skip-if = [
+ "tsan", # Times out, bug 1674773
+ "os == 'android'",
+]
["test_permissions.js"]
+skip-if = ["os == 'android'"]
["test_permissions_prefs.js"]
+skip-if = ["os == 'android'"]
["test_pref_properties.js"]
+skip-if = ["os == 'android'"]
["test_provider_markSafe.js"]
+skip-if = ["os == 'android'"]
["test_provider_shutdown.js"]
+skip-if = ["os == 'android'"]
["test_provider_unsafe_access_shutdown.js"]
+skip-if = ["os == 'android'"]
["test_provider_unsafe_access_startup.js"]
+skip-if = ["os == 'android'"]
["test_proxies.js"]
-skip-if = ["require_signing"]
+skip-if = [
+ "require_signing",
+ "os == 'android'",
+]
["test_recommendations.js"]
-skip-if = ["require_signing"]
+skip-if = [
+ "require_signing",
+ "os == 'android'",
+]
["test_registerchrome.js"]
+skip-if = ["os == 'android'"]
["test_registry.js"]
run-if = ["os == 'win'"]
+skip-if = ["os == 'android'"]
["test_reinstall_disabled_addon.js"]
+skip-if = ["os == 'android'"]
["test_reload.js"]
-skip-if = ["os == 'win'"] # There's a problem removing a temp file without manually clearing the cache on Windows
+skip-if = [
+ "os == 'win'", # There's a problem removing a temp file without manually clearing the cache on Windows
+ "os == 'android'",
+]
tags = "webextensions"
["test_remote_pref_telemetry.js"]
+skip-if = ["os == 'android'"]
["test_safemode.js"]
+skip-if = ["os == 'android'"]
["test_schema_change.js"]
+skip-if = ["os == 'android'"]
["test_seen.js"]
+skip-if = ["os == 'android'"]
["test_shutdown.js"]
+skip-if = ["os == 'android'"]
["test_shutdown_barriers.js"]
+skip-if = ["os == 'android'"]
["test_shutdown_early.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
["test_sideload_scopes.js"]
head = "head_addons.js head_sideload.js"
skip-if = [
"os == 'linux'", # Bug 1613268
"condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
]
["test_sideloads.js"]
+skip-if = ["os == 'android'"]
["test_sideloads_after_rebuild.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
head = "head_addons.js head_sideload.js"
["test_signed_inject.js"]
skip-if = ["true"] # Bug 1394122
["test_signed_install.js"]
+skip-if = ["os == 'android'"]
["test_signed_langpack.js"]
+skip-if = ["os == 'android'"]
["test_signed_long.js"]
+skip-if = ["os == 'android'"]
["test_signed_updatepref.js"]
skip-if = [
"require_signing",
"!allow_legacy_extensions",
+ "os == 'android'",
]
["test_signed_verify.js"]
["test_sitePermsAddonProvider.js"]
-skip-if = ["appname == 'thunderbird'"] # Disabled in extensions.manifest
+skip-if = [
+ "appname == 'thunderbird'", # Disabled in extensions.manifest
+ "os == 'android'",
+]
["test_startup.js"]
head = "head_addons.js head_sideload.js"
skip-if = [
"os == 'linux'", # Bug 1613268
"condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
]
["test_startup_enable.js"]
+skip-if = ["os == 'android'"]
["test_startup_isPrivileged.js"]
+skip-if = ["os == 'android'"]
["test_startup_scan.js"]
head = "head_addons.js head_sideload.js"
+skip-if = ["os == 'android'"]
["test_strictcompatibility.js"]
head = "head_addons.js head_compat.js"
+skip-if = ["os == 'android'"]
["test_syncGUID.js"]
+skip-if = ["os == 'android'"]
["test_system_allowed.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_delay_update.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_profile_location.js"]
+skip-if = ["os == 'android'"]
["test_system_repository.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_reset.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_blank.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_checkSizeHash.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_custom.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_empty.js"]
head = "head_addons.js head_system_addons.js"
@@ -264,104 +385,148 @@ skip-if = ["true"] # Failing intermittently due to a race condition in the test,
["test_system_update_enterprisepolicy.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_fail.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_installTelemetryInfo.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_newset.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_overlapping.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_uninstall_check.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_update_upgrades.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_system_upgrades.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
head = "head_addons.js head_system_addons.js"
["test_systemaddomstartupprefs.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
head = "head_addons.js head_system_addons.js"
["test_temporary.js"]
-skip-if = ["os == 'win'"] # Bug 1469904
+skip-if = [
+ "os == 'win'", # Bug 1469904
+ "os == 'android'",
+]
tags = "webextensions"
["test_trash_directory.js"]
run-if = ["os == 'win'"]
+skip-if = ["os == 'android'"]
["test_types.js"]
["test_undouninstall.js"]
-skip-if = ["os == 'win'"] # Bug 1358846
+skip-if = [
+ "os == 'win'", # Bug 1358846
+ "os == 'android'",
+]
["test_update.js"]
+skip-if = ["os == 'android'"]
["test_updateCancel.js"]
+skip-if = ["os == 'android'"]
["test_update_addontype.js"]
+skip-if = ["os == 'android'"]
["test_update_compatmode.js"]
head = "head_addons.js head_compat.js"
+skip-if = ["os == 'android'"]
["test_update_ignorecompat.js"]
skip-if = ["true"] # Bug 676922 Bug 1437697
["test_update_isPrivileged.js"]
-skip-if = ["condprof"] # Bug 1769184 - by design for now
+skip-if = [
+ "condprof", # Bug 1769184 - by design for now
+ "os == 'android'",
+]
["test_update_noSystemAddonUpdate.js"]
head = "head_addons.js head_system_addons.js"
+skip-if = ["os == 'android'"]
["test_update_strictcompat.js"]
head = "head_addons.js head_compat.js"
+skip-if = ["os == 'android'"]
["test_update_theme.js"]
+skip-if = ["os == 'android'"]
["test_update_webextensions.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_updatecheck.js"]
+skip-if = ["os == 'android'"]
["test_updatecheck_errors.js"]
+skip-if = ["os == 'android'"]
["test_updatecheck_json.js"]
+skip-if = ["os == 'android'"]
["test_updateid.js"]
+skip-if = ["os == 'android'"]
["test_updateversion.js"]
+skip-if = ["os == 'android'"]
["test_upgrade.js"]
head = "head_addons.js head_compat.js"
run-sequentially = "Uses global XCurProcD dir."
+skip-if = ["os == 'android'"]
["test_upgrade_incompatible.js"]
+skip-if = ["os == 'android'"]
["test_webextension.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_webextension_events.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_webextension_icons.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_webextension_install.js"]
tags = "webextensions"
["test_webextension_install_syntax_error.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_webextension_langpack.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
["test_webextension_theme.js"]
tags = "webextensions"
+skip-if = ["os == 'android'"]
diff --git a/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js b/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js
index 6c8894699d..b74cff859f 100644
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_required_useractivation.js
@@ -17,6 +17,7 @@ async function runTestCase(spawnArgs, spawnFn, { expectInstall, clickLink }) {
set: [
// Make use the user activation requirements is enabled while running this test.
["xpinstall.userActivation.required", true],
+ ["dom.security.https_first", false],
],
});
await BrowserTestUtils.withNewTab(TESTROOT, async browser => {
diff --git a/toolkit/mozapps/installer/package-name.mk b/toolkit/mozapps/installer/package-name.mk
index 36239fe79f..c7be519725 100644
--- a/toolkit/mozapps/installer/package-name.mk
+++ b/toolkit/mozapps/installer/package-name.mk
@@ -110,6 +110,9 @@ GTEST_PACKAGE = $(PKG_BASENAME).gtest.tests.tar.gz
# `.xpt` artifacts: for use in artifact builds.
XPT_ARTIFACTS_ARCHIVE_BASENAME = $(PKG_BASENAME).xpt_artifacts
+ifeq (Darwin, $(OS_ARCH))
+UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME = $(PKG_BASENAME).update_framework_artifacts
+endif # Darwin
ifneq (,$(wildcard $(DIST)/bin/application.ini))
BUILDID = $(shell $(PYTHON3) $(MOZILLA_DIR)/config/printconfigsetting.py $(DIST)/bin/application.ini App BuildID)
diff --git a/toolkit/mozapps/installer/packager.mk b/toolkit/mozapps/installer/packager.mk
index 6d36d51375..f0572af6d8 100644
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -109,9 +109,17 @@ endif # Darwin
ifndef MOZ_ARTIFACT_BUILDS
@echo 'Generating XPT artifacts archive ($(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip)'
$(call py_action,zip $(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip,-C $(topobjdir)/config/makefiles/xpidl '$(ABS_DIST)/$(PKG_PATH)$(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip' '*.xpt')
+ifeq (Darwin, $(OS_ARCH))
+ @echo 'Generating update-related macOS framework artifacts archive ($(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip)'
+ $(call py_action,zip $(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip,-C '$(ABS_DIST)/update_framework_artifacts' '$(ABS_DIST)/$(PKG_PATH)$(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip' '*.framework')
+endif # Darwin
else
@echo 'Packaging existing XPT artifacts from artifact build into archive ($(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip)'
$(call py_action,zip $(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip,-C $(ABS_DIST)/xpt_artifacts '$(ABS_DIST)/$(PKG_PATH)$(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip' '*.xpt')
+ifeq (Darwin, $(OS_ARCH))
+ @echo 'Packaging existing update-related macOS framework artifacts from artifact build into archive ($(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip)'
+ $(call py_action,zip $(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip,-C $(ABS_DIST)/update_framework_artifacts '$(ABS_DIST)/$(PKG_PATH)$(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip' '*.framework')
+endif # Darwin
endif # MOZ_ARTIFACT_BUILDS
prepare-package: stage-package
@@ -119,6 +127,7 @@ prepare-package: stage-package
make-package-internal: prepare-package make-sourcestamp-file
@echo 'Compressing...'
$(call MAKE_PACKAGE,$(DIST))
+ echo $(PACKAGE) > $(ABS_DIST)/package_name.txt
make-package: FORCE
$(MAKE) make-package-internal
diff --git a/toolkit/mozapps/installer/upload-files.mk b/toolkit/mozapps/installer/upload-files.mk
index 6e1283e238..817f782772 100644
--- a/toolkit/mozapps/installer/upload-files.mk
+++ b/toolkit/mozapps/installer/upload-files.mk
@@ -394,6 +394,10 @@ endif
# Upload `.xpt` artifacts for use in artifact builds.
UPLOAD_FILES += $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(XPT_ARTIFACTS_ARCHIVE_BASENAME).zip)
+# Upload update-related macOS framework artifacts for use in artifact builds.
+ifeq ($(OS_ARCH),Darwin)
+UPLOAD_FILES += $(call QUOTED_WILDCARD,$(DIST)/$(PKG_PATH)$(UPDATE_FRAMEWORK_ARTIFACTS_ARCHIVE_BASENAME).zip)
+endif # Darwin
ifndef MOZ_PKG_SRCDIR
MOZ_PKG_SRCDIR = $(topsrcdir)
diff --git a/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/Makefile.in b/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/Makefile.in
new file mode 100644
index 0000000000..5dcb0821cb
--- /dev/null
+++ b/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/Makefile.in
@@ -0,0 +1,25 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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 $(topsrcdir)/config/rules.mk
+
+# In a compile build, the moz.build stanzas produce a binary named
+# `ChannelPrefs-localbuild`. We need to produce
+# `dist/update_framework_artifacts/ChannelPrefs-localbuild.framework/ChannelPrefs`
+# for consumption by artifact builds.
+#
+# In an artifact build, we already have upstream artifacts in
+# `dist/update_framework_artifacts/ChannelPrefs-localbuild.framework`.
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+libs::
+ifneq (,$(COMPILE_ENVIRONMENT))
+ rm -rf $(FINAL_TARGET)/ChannelPrefs-localbuild.framework
+ $(NSINSTALL) -D $(FINAL_TARGET)/ChannelPrefs-localbuild.framework
+ cp $(FINAL_TARGET)/ChannelPrefs-localbuild ChannelPrefs
+ $(NSINSTALL) ChannelPrefs $(FINAL_TARGET)/ChannelPrefs-localbuild.framework
+endif # COMPILE_ENVIRONMENT
+ $(NSINSTALL) $(srcdir)/../ChannelPrefs/Info.plist $(FINAL_TARGET)/ChannelPrefs-localbuild.framework/Resources
+endif # MOZ_WIDGET_TOOLKIT
diff --git a/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/moz.build b/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/moz.build
new file mode 100644
index 0000000000..ff91833ad3
--- /dev/null
+++ b/toolkit/mozapps/macos-frameworks/ChannelPrefs-localbuild/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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Application Update")
+
+Framework("ChannelPrefs-localbuild")
+FINAL_TARGET = "dist/update_framework_artifacts"
+
+DEFINES["MOZ_UPDATE_CHANNEL_OVERRIDE"] = "default"
+
+UNIFIED_SOURCES += [
+ "../ChannelPrefs/ChannelPrefs.mm",
+]
+
+OS_LIBS += [
+ "-framework Foundation",
+]
diff --git a/toolkit/mozapps/macos-frameworks/ChannelPrefs/ChannelPrefs.mm b/toolkit/mozapps/macos-frameworks/ChannelPrefs/ChannelPrefs.mm
index f437bb857d..0428bbe8ab 100644
--- a/toolkit/mozapps/macos-frameworks/ChannelPrefs/ChannelPrefs.mm
+++ b/toolkit/mozapps/macos-frameworks/ChannelPrefs/ChannelPrefs.mm
@@ -6,7 +6,13 @@
#include "mozilla/HelperMacros.h"
+#ifdef MOZ_UPDATE_CHANNEL_OVERRIDE
+# define CHANNEL MOZ_UPDATE_CHANNEL_OVERRIDE
+#else
+# define CHANNEL MOZ_UPDATE_CHANNEL
+#endif
+
NSString* ChannelPrefsGetChannel() {
- return [NSString stringWithCString:MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)
+ return [NSString stringWithCString:MOZ_STRINGIFY(CHANNEL)
encoding:NSUTF8StringEncoding];
}
diff --git a/toolkit/mozapps/macos-frameworks/ChannelPrefs/Makefile.in b/toolkit/mozapps/macos-frameworks/ChannelPrefs/Makefile.in
index 43552a771e..7f3bfd1cc5 100644
--- a/toolkit/mozapps/macos-frameworks/ChannelPrefs/Makefile.in
+++ b/toolkit/mozapps/macos-frameworks/ChannelPrefs/Makefile.in
@@ -5,10 +5,23 @@
include $(topsrcdir)/config/rules.mk
+# In a compile build, the moz.build stanzas produce a binary named
+# `ChannelPrefs`. We need to produce
+# `dist/bin/ChannelPrefs.framework/ChannelPrefs` for consumption by the
+# build.
+#
+# In an artifact build, we copy upstream artifacts from
+# `dist/update_framework_artifacts/ChannelPrefs-localbuild.framework`
+
ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
libs::
- rm -rf $(DIST)/bin/ChannelPrefs.framework
-
- $(NSINSTALL) $(DIST)/bin/ChannelPrefs $(DIST)/bin/ChannelPrefs.framework
- $(NSINSTALL) $(srcdir)/Info.plist $(DIST)/bin/ChannelPrefs.framework/Resources
-endif
+ rm -rf $(FINAL_TARGET)/ChannelPrefs.framework
+ $(NSINSTALL) -D $(FINAL_TARGET)/ChannelPrefs.framework
+ifneq (,$(COMPILE_ENVIRONMENT))
+ $(NSINSTALL) $(FINAL_TARGET)/ChannelPrefs $(FINAL_TARGET)/ChannelPrefs.framework
+endif # COMPILE_ENVIRONMENT
+ifneq (,$(MOZ_ARTIFACT_BUILDS))
+ $(NSINSTALL) $(DIST)/update_framework_artifacts/ChannelPrefs-localbuild.framework/ChannelPrefs $(FINAL_TARGET)/ChannelPrefs.framework
+endif # MOZ_ARTIFACT_BUILDS
+ $(NSINSTALL) $(srcdir)/Info.plist $(FINAL_TARGET)/ChannelPrefs.framework/Resources
+endif # MOZ_WIDGET_TOOLKIT
diff --git a/toolkit/mozapps/macos-frameworks/moz.build b/toolkit/mozapps/macos-frameworks/moz.build
index b1a315500c..f7ad2d314f 100644
--- a/toolkit/mozapps/macos-frameworks/moz.build
+++ b/toolkit/mozapps/macos-frameworks/moz.build
@@ -11,6 +11,7 @@ FINAL_LIBRARY = "xul"
DIRS += [
"ChannelPrefs",
+ "ChannelPrefs-localbuild",
]
EXPORTS += [
diff --git a/toolkit/mozapps/update/BackgroundUpdate.sys.mjs b/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
index 3022c9ffde..1435c0fa2b 100644
--- a/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
+++ b/toolkit/mozapps/update/BackgroundUpdate.sys.mjs
@@ -69,18 +69,6 @@ function taskNameVersion(taskVersion) {
return 2;
}
-async function deleteTasksInRange(installedVersion, currentVersion) {
- for (
- let taskVersion = installedVersion;
- taskVersion <= currentVersion;
- taskVersion++
- ) {
- await lazy.TaskScheduler.deleteTask(this.taskId, {
- nameVersion: taskNameVersion(taskVersion),
- });
- }
-}
-
export var BackgroundUpdate = {
QueryInterface: ChromeUtils.generateQI([
"nsINamed",
@@ -102,6 +90,24 @@ export var BackgroundUpdate = {
return taskId;
},
+ async deleteTasksInRange(installedVersion, currentVersion) {
+ for (
+ let taskVersion = installedVersion;
+ taskVersion <= currentVersion;
+ taskVersion++
+ ) {
+ try {
+ await lazy.TaskScheduler.deleteTask(this.taskId, {
+ nameVersion: taskNameVersion(taskVersion),
+ });
+ } catch (e) {
+ lazy.log.error(
+ `deleteTasksInRange: Error deleting task ${taskVersion}: ${e}`
+ );
+ }
+ }
+ },
+
/**
* Whether this installation has an App and a GRE omnijar.
*
@@ -583,7 +589,10 @@ export var BackgroundUpdate = {
TASK_INSTALLED_VERSION_PREF,
TASK_DEF_CURRENT_VERSION
);
- await deleteTasksInRange(installedVersion, TASK_DEF_CURRENT_VERSION);
+ await this.deleteTasksInRange(
+ installedVersion,
+ TASK_DEF_CURRENT_VERSION
+ );
lazy.log.debug(
`${SLUG}: witnessed falling (enabled -> disabled) edge; deleted task ${this.taskId}.`
);
@@ -616,7 +625,10 @@ export var BackgroundUpdate = {
TASK_INSTALLED_VERSION_PREF,
TASK_DEF_CURRENT_VERSION
);
- await deleteTasksInRange(installedVersion, TASK_DEF_CURRENT_VERSION);
+ await this.deleteTasksInRange(
+ installedVersion,
+ TASK_DEF_CURRENT_VERSION
+ );
} catch (e) {
lazy.log.error(`${SLUG}: Error removing old task: ${e}`);
}
diff --git a/toolkit/mozapps/update/UpdateService.sys.mjs b/toolkit/mozapps/update/UpdateService.sys.mjs
index 34bf1b9e19..21ecb3db48 100644
--- a/toolkit/mozapps/update/UpdateService.sys.mjs
+++ b/toolkit/mozapps/update/UpdateService.sys.mjs
@@ -2082,69 +2082,10 @@ function pollForStagingEnd() {
lazy.setTimeout(pollingFn, pollingIntervalMs);
}
-/**
- * Update Patch
- * @param patch
- * A <patch> element to initialize this object with
- * @throws if patch has a size of 0
- * @constructor
- */
-function UpdatePatch(patch) {
- this._properties = {};
- this.errorCode = 0;
- this.finalURL = null;
- this.state = STATE_NONE;
-
- for (let i = 0; i < patch.attributes.length; ++i) {
- var attr = patch.attributes.item(i);
- // If an undefined value is saved to the xml file it will be a string when
- // it is read from the xml file.
- if (attr.value == "undefined") {
- continue;
- }
- switch (attr.name) {
- case "xmlns":
- // Don't save the XML namespace.
- break;
- case "selected":
- this.selected = attr.value == "true";
- break;
- case "size":
- if (0 == parseInt(attr.value)) {
- LOG("UpdatePatch:init - 0-sized patch!");
- throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
- }
- this[attr.name] = attr.value;
- break;
- case "errorCode":
- if (attr.value) {
- let val = parseInt(attr.value);
- // This will evaluate to false if the value is 0 but that's ok since
- // this.errorCode is set to the default of 0 above.
- if (val) {
- this.errorCode = val;
- }
- }
- break;
- case "finalURL":
- case "state":
- case "type":
- case "URL":
- this[attr.name] = attr.value;
- break;
- default:
- if (!this._attrNames.includes(attr.name)) {
- // Set nsIPropertyBag properties that were read from the xml file.
- this.setProperty(attr.name, attr.value);
- }
- break;
- }
- }
-}
-UpdatePatch.prototype = {
+class UpdatePatch {
// nsIUpdatePatch attribute names used to prevent nsIWritablePropertyBag from
// over writing nsIUpdatePatch attributes.
- _attrNames: [
+ _attrNames = [
"errorCode",
"finalURL",
"selected",
@@ -2152,12 +2093,71 @@ UpdatePatch.prototype = {
"state",
"type",
"URL",
- ],
+ ];
+
+ /**
+ * @param patch
+ * A <patch> element to initialize this object with
+ * @throws if patch has a size of 0
+ * @constructor
+ */
+ constructor(patch) {
+ this._properties = {};
+ this.errorCode = 0;
+ this.finalURL = null;
+ this.state = STATE_NONE;
+
+ for (let i = 0; i < patch.attributes.length; ++i) {
+ var attr = patch.attributes.item(i);
+ // If an undefined value is saved to the xml file it will be a string when
+ // it is read from the xml file.
+ if (attr.value == "undefined") {
+ continue;
+ }
+ switch (attr.name) {
+ case "xmlns":
+ // Don't save the XML namespace.
+ break;
+ case "selected":
+ this.selected = attr.value == "true";
+ break;
+ case "size":
+ if (0 == parseInt(attr.value)) {
+ LOG("UpdatePatch:init - 0-sized patch!");
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+ this[attr.name] = attr.value;
+ break;
+ case "errorCode":
+ if (attr.value) {
+ let val = parseInt(attr.value);
+ // This will evaluate to false if the value is 0 but that's ok since
+ // this.errorCode is set to the default of 0 above.
+ if (val) {
+ this.errorCode = val;
+ }
+ }
+ break;
+ case "finalURL":
+ case "state":
+ case "type":
+ case "URL":
+ this[attr.name] = attr.value;
+ break;
+ default:
+ if (!this._attrNames.includes(attr.name)) {
+ // Set nsIPropertyBag properties that were read from the xml file.
+ this.setProperty(attr.name, attr.value);
+ }
+ break;
+ }
+ }
+ }
/**
* See nsIUpdateService.idl
*/
- serialize: function UpdatePatch_serialize(updates) {
+ serialize(updates) {
var patch = updates.createElementNS(URI_UPDATE_NS, "patch");
patch.setAttribute("size", this.size);
patch.setAttribute("type", this.type);
@@ -2185,12 +2185,12 @@ UpdatePatch.prototype = {
}
}
return patch;
- },
+ }
/**
* See nsIWritablePropertyBag.idl
*/
- setProperty: function UpdatePatch_setProperty(name, value) {
+ setProperty(name, value) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2201,12 +2201,12 @@ UpdatePatch.prototype = {
);
}
this._properties[name] = { data: value, present: true };
- },
+ }
/**
* See nsIWritablePropertyBag.idl
*/
- deleteProperty: function UpdatePatch_deleteProperty(name) {
+ deleteProperty(name) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2221,7 +2221,7 @@ UpdatePatch.prototype = {
} else {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
- },
+ }
/**
* See nsIPropertyBag.idl
@@ -2231,7 +2231,7 @@ UpdatePatch.prototype = {
*/
get enumerator() {
return this.enumerate();
- },
+ }
*enumerate() {
// An nsISupportsInterfacePointer is used so creating an array using
@@ -2239,7 +2239,7 @@ UpdatePatch.prototype = {
let ip = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance(
Ci.nsISupportsInterfacePointer
);
- let qi = ChromeUtils.generateQI(["nsIProperty"]);
+ let qi = ChromeUtils.generateQI([Ci.nsIProperty]);
for (let [name, value] of Object.entries(this._properties)) {
if (value.present && !this._attrNames.includes(name)) {
// The nsIPropertyBag enumerator returns a nsISimpleEnumerator whose
@@ -2251,7 +2251,7 @@ UpdatePatch.prototype = {
yield ip.data.QueryInterface(Ci.nsIProperty);
}
}
- },
+ }
/**
* See nsIPropertyBag.idl
@@ -2259,7 +2259,7 @@ UpdatePatch.prototype = {
* Note: returns null instead of throwing when the property doesn't exist to
* simplify code and to silence warnings in debug builds.
*/
- getProperty: function UpdatePatch_getProperty(name) {
+ getProperty(name) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2273,161 +2273,19 @@ UpdatePatch.prototype = {
return this._properties[name].data;
}
return null;
- },
-
- QueryInterface: ChromeUtils.generateQI([
- "nsIUpdatePatch",
- "nsIPropertyBag",
- "nsIWritablePropertyBag",
- ]),
-};
-
-/**
- * Update
- * Implements nsIUpdate
- * @param update
- * An <update> element to initialize this object with
- * @throws if the update contains no patches
- * @constructor
- */
-function Update(update) {
- this._patches = [];
- this._properties = {};
- this.isCompleteUpdate = false;
- this.channel = "default";
- this.promptWaitTime = Services.prefs.getIntPref(
- PREF_APP_UPDATE_PROMPTWAITTIME,
- 43200
- );
- this.unsupported = false;
-
- // Null <update>, assume this is a message container and do no
- // further initialization
- if (!update) {
- return;
- }
-
- for (let i = 0; i < update.childNodes.length; ++i) {
- let patchElement = update.childNodes.item(i);
- if (
- patchElement.nodeType != patchElement.ELEMENT_NODE ||
- patchElement.localName != "patch"
- ) {
- continue;
- }
-
- let patch;
- try {
- patch = new UpdatePatch(patchElement);
- } catch (e) {
- continue;
- }
- this._patches.push(patch);
- }
-
- if (!this._patches.length && !update.hasAttribute("unsupported")) {
- throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
- }
-
- // Set the installDate value with the current time. If the update has an
- // installDate attribute this will be replaced with that value if it doesn't
- // equal 0.
- this.installDate = new Date().getTime();
- this.patchCount = this._patches.length;
-
- for (let i = 0; i < update.attributes.length; ++i) {
- let attr = update.attributes.item(i);
- if (attr.name == "xmlns" || attr.value == "undefined") {
- // Don't save the XML namespace or undefined values.
- // If an undefined value is saved to the xml file it will be a string when
- // it is read from the xml file.
- continue;
- } else if (attr.name == "detailsURL") {
- this.detailsURL = attr.value;
- } else if (attr.name == "installDate" && attr.value) {
- let val = parseInt(attr.value);
- if (val) {
- this.installDate = val;
- }
- } else if (attr.name == "errorCode" && attr.value) {
- let val = parseInt(attr.value);
- if (val) {
- // Set the value of |_errorCode| instead of |errorCode| since
- // selectedPatch won't be available at this point and normally the
- // nsIUpdatePatch will provide the errorCode.
- this._errorCode = val;
- }
- } else if (attr.name == "isCompleteUpdate") {
- this.isCompleteUpdate = attr.value == "true";
- } else if (attr.name == "promptWaitTime") {
- if (!isNaN(attr.value)) {
- this.promptWaitTime = parseInt(attr.value);
- }
- } else if (attr.name == "unsupported") {
- this.unsupported = attr.value == "true";
- } else {
- switch (attr.name) {
- case "appVersion":
- case "buildID":
- case "channel":
- case "displayVersion":
- case "elevationFailure":
- case "name":
- case "previousAppVersion":
- case "serviceURL":
- case "statusText":
- case "type":
- this[attr.name] = attr.value;
- break;
- default:
- if (!this._attrNames.includes(attr.name)) {
- // Set nsIPropertyBag properties that were read from the xml file.
- this.setProperty(attr.name, attr.value);
- }
- break;
- }
- }
}
- if (!this.previousAppVersion) {
- this.previousAppVersion = Services.appinfo.version;
- }
-
- if (!this.elevationFailure) {
- this.elevationFailure = false;
- }
-
- if (!this.detailsURL) {
- try {
- // Try using a default details URL supplied by the distribution
- // if the update XML does not supply one.
- this.detailsURL = Services.urlFormatter.formatURLPref(
- PREF_APP_UPDATE_URL_DETAILS
- );
- } catch (e) {
- this.detailsURL = "";
- }
- }
-
- if (!this.displayVersion) {
- this.displayVersion = this.appVersion;
- }
-
- if (!this.name) {
- // When the update doesn't provide a name fallback to using
- // "<App Name> <Update App Version>"
- let brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
- let appName = brandBundle.GetStringFromName("brandShortName");
- this.name = lazy.gUpdateBundle.formatStringFromName("updateName", [
- appName,
- this.displayVersion,
- ]);
- }
+ QueryInterface = ChromeUtils.generateQI([
+ Ci.nsIUpdatePatch,
+ Ci.nsIPropertyBag,
+ Ci.nsIWritablePropertyBag,
+ ]);
}
-Update.prototype = {
+
+class Update {
// nsIUpdate attribute names used to prevent nsIWritablePropertyBag from over
// writing nsIUpdate attributes.
- _attrNames: [
+ _attrNames = [
"appVersion",
"buildID",
"channel",
@@ -2445,14 +2303,156 @@ Update.prototype = {
"statusText",
"type",
"unsupported",
- ],
+ ];
+
+ /**
+ * Implements nsIUpdate
+ * @param update
+ * An <update> element to initialize this object with
+ * @throws if the update contains no patches
+ * @constructor
+ */
+ constructor(update) {
+ this._patches = [];
+ this._properties = {};
+ this.isCompleteUpdate = false;
+ this.channel = "default";
+ this.promptWaitTime = Services.prefs.getIntPref(
+ PREF_APP_UPDATE_PROMPTWAITTIME,
+ 43200
+ );
+ this.unsupported = false;
+
+ // Null <update>, assume this is a message container and do no
+ // further initialization
+ if (!update) {
+ return;
+ }
+
+ for (let i = 0; i < update.childNodes.length; ++i) {
+ let patchElement = update.childNodes.item(i);
+ if (
+ patchElement.nodeType != patchElement.ELEMENT_NODE ||
+ patchElement.localName != "patch"
+ ) {
+ continue;
+ }
+
+ let patch;
+ try {
+ patch = new UpdatePatch(patchElement);
+ } catch (e) {
+ continue;
+ }
+ this._patches.push(patch);
+ }
+
+ if (!this._patches.length && !update.hasAttribute("unsupported")) {
+ throw Components.Exception("", Cr.NS_ERROR_ILLEGAL_VALUE);
+ }
+
+ // Set the installDate value with the current time. If the update has an
+ // installDate attribute this will be replaced with that value if it doesn't
+ // equal 0.
+ this.installDate = new Date().getTime();
+ this.patchCount = this._patches.length;
+
+ for (let i = 0; i < update.attributes.length; ++i) {
+ let attr = update.attributes.item(i);
+ if (attr.name == "xmlns" || attr.value == "undefined") {
+ // Don't save the XML namespace or undefined values.
+ // If an undefined value is saved to the xml file it will be a string when
+ // it is read from the xml file.
+ continue;
+ } else if (attr.name == "detailsURL") {
+ this.detailsURL = attr.value;
+ } else if (attr.name == "installDate" && attr.value) {
+ let val = parseInt(attr.value);
+ if (val) {
+ this.installDate = val;
+ }
+ } else if (attr.name == "errorCode" && attr.value) {
+ let val = parseInt(attr.value);
+ if (val) {
+ // Set the value of |_errorCode| instead of |errorCode| since
+ // selectedPatch won't be available at this point and normally the
+ // nsIUpdatePatch will provide the errorCode.
+ this._errorCode = val;
+ }
+ } else if (attr.name == "isCompleteUpdate") {
+ this.isCompleteUpdate = attr.value == "true";
+ } else if (attr.name == "promptWaitTime") {
+ if (!isNaN(attr.value)) {
+ this.promptWaitTime = parseInt(attr.value);
+ }
+ } else if (attr.name == "unsupported") {
+ this.unsupported = attr.value == "true";
+ } else {
+ switch (attr.name) {
+ case "appVersion":
+ case "buildID":
+ case "channel":
+ case "displayVersion":
+ case "elevationFailure":
+ case "name":
+ case "previousAppVersion":
+ case "serviceURL":
+ case "statusText":
+ case "type":
+ this[attr.name] = attr.value;
+ break;
+ default:
+ if (!this._attrNames.includes(attr.name)) {
+ // Set nsIPropertyBag properties that were read from the xml file.
+ this.setProperty(attr.name, attr.value);
+ }
+ break;
+ }
+ }
+ }
+
+ if (!this.previousAppVersion) {
+ this.previousAppVersion = Services.appinfo.version;
+ }
+
+ if (!this.elevationFailure) {
+ this.elevationFailure = false;
+ }
+
+ if (!this.detailsURL) {
+ try {
+ // Try using a default details URL supplied by the distribution
+ // if the update XML does not supply one.
+ this.detailsURL = Services.urlFormatter.formatURLPref(
+ PREF_APP_UPDATE_URL_DETAILS
+ );
+ } catch (e) {
+ this.detailsURL = "";
+ }
+ }
+
+ if (!this.displayVersion) {
+ this.displayVersion = this.appVersion;
+ }
+
+ if (!this.name) {
+ // When the update doesn't provide a name fallback to using
+ // "<App Name> <Update App Version>"
+ let brandBundle = Services.strings.createBundle(URI_BRAND_PROPERTIES);
+ let appName = brandBundle.GetStringFromName("brandShortName");
+ this.name = lazy.gUpdateBundle.formatStringFromName("updateName", [
+ appName,
+ this.displayVersion,
+ ]);
+ }
+ }
/**
* See nsIUpdateService.idl
*/
- getPatchAt: function Update_getPatchAt(index) {
+ getPatchAt(index) {
return this._patches[index];
- },
+ }
/**
* See nsIUpdateService.idl
@@ -2462,19 +2462,19 @@ Update.prototype = {
* active updates from the update manager for some reason but still have
* the update.status file to work with.
*/
- _state: "",
+ _state = "";
get state() {
if (this.selectedPatch) {
return this.selectedPatch.state;
}
return this._state;
- },
+ }
set state(state) {
if (this.selectedPatch) {
this.selectedPatch.state = state;
}
this._state = state;
- },
+ }
/**
* See nsIUpdateService.idl
@@ -2484,19 +2484,19 @@ Update.prototype = {
* active updates from the update manager for some reason but still have
* the update.status file to work with.
*/
- _errorCode: 0,
+ _errorCode = 0;
get errorCode() {
if (this.selectedPatch) {
return this.selectedPatch.errorCode;
}
return this._errorCode;
- },
+ }
set errorCode(errorCode) {
if (this.selectedPatch) {
this.selectedPatch.errorCode = errorCode;
}
this._errorCode = errorCode;
- },
+ }
/**
* See nsIUpdateService.idl
@@ -2508,12 +2508,12 @@ Update.prototype = {
}
}
return null;
- },
+ }
/**
* See nsIUpdateService.idl
*/
- serialize: function Update_serialize(updates) {
+ serialize(updates) {
// If appVersion isn't defined just return null. This happens when cleaning
// up invalid updates (e.g. incorrect channel).
if (!this.appVersion) {
@@ -2555,12 +2555,12 @@ Update.prototype = {
updates.documentElement.appendChild(update);
return update;
- },
+ }
/**
* See nsIWritablePropertyBag.idl
*/
- setProperty: function Update_setProperty(name, value) {
+ setProperty(name, value) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2571,12 +2571,12 @@ Update.prototype = {
);
}
this._properties[name] = { data: value, present: true };
- },
+ }
/**
* See nsIWritablePropertyBag.idl
*/
- deleteProperty: function Update_deleteProperty(name) {
+ deleteProperty(name) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2591,7 +2591,7 @@ Update.prototype = {
} else {
throw Components.Exception("", Cr.NS_ERROR_FAILURE);
}
- },
+ }
/**
* See nsIPropertyBag.idl
@@ -2601,7 +2601,7 @@ Update.prototype = {
*/
get enumerator() {
return this.enumerate();
- },
+ }
*enumerate() {
// An nsISupportsInterfacePointer is used so creating an array using
@@ -2609,7 +2609,7 @@ Update.prototype = {
let ip = Cc["@mozilla.org/supports-interface-pointer;1"].createInstance(
Ci.nsISupportsInterfacePointer
);
- let qi = ChromeUtils.generateQI(["nsIProperty"]);
+ let qi = ChromeUtils.generateQI([Ci.nsIProperty]);
for (let [name, value] of Object.entries(this._properties)) {
if (value.present && !this._attrNames.includes(name)) {
// The nsIPropertyBag enumerator returns a nsISimpleEnumerator whose
@@ -2621,14 +2621,14 @@ Update.prototype = {
yield ip.data.QueryInterface(Ci.nsIProperty);
}
}
- },
+ }
/**
* See nsIPropertyBag.idl
* Note: returns null instead of throwing when the property doesn't exist to
* simplify code and to silence warnings in debug builds.
*/
- getProperty: function Update_getProperty(name) {
+ getProperty(name) {
if (this._attrNames.includes(name)) {
throw Components.Exception(
"Illegal value '" +
@@ -2642,60 +2642,60 @@ Update.prototype = {
return this._properties[name].data;
}
return null;
- },
-
- QueryInterface: ChromeUtils.generateQI([
- "nsIUpdate",
- "nsIPropertyBag",
- "nsIWritablePropertyBag",
- ]),
-};
-
-/**
- * UpdateService
- * A Service for managing the discovery and installation of software updates.
- * @constructor
- */
-export function UpdateService() {
- LOG("Creating UpdateService");
- // The observor notification to shut down the service must be before
- // profile-before-change since nsIUpdateManager uses profile-before-change
- // to shutdown and write the update xml files.
- Services.obs.addObserver(this, "quit-application");
- lazy.UpdateLog.addConfigChangeListener(() => {
- this._logStatus();
- });
+ }
- this._logStatus();
+ QueryInterface = ChromeUtils.generateQI([
+ Ci.nsIUpdate,
+ Ci.nsIPropertyBag,
+ Ci.nsIWritablePropertyBag,
+ ]);
}
-UpdateService.prototype = {
+export class UpdateService {
/**
* The downloader we are using to download updates. There is only ever one of
* these.
*/
- _downloader: null,
+ _downloader = null;
/**
* Whether or not the service registered the "online" observer.
*/
- _registeredOnlineObserver: false,
+ _registeredOnlineObserver = false;
/**
* The current number of consecutive socket errors
*/
- _consecutiveSocketErrors: 0,
+ _consecutiveSocketErrors = 0;
/**
* A timer used to retry socket errors
*/
- _retryTimer: null,
+ _retryTimer = null;
/**
* Whether or not a background update check was initiated by the
* application update timer notification.
*/
- _isNotify: true,
+ _isNotify = true;
+
+ /**
+ * UpdateService
+ * A Service for managing the discovery and installation of software updates.
+ * @constructor
+ */
+ constructor() {
+ LOG("Creating UpdateService");
+ // The observor notification to shut down the service must be before
+ // profile-before-change since nsIUpdateManager uses profile-before-change
+ // to shutdown and write the update xml files.
+ Services.obs.addObserver(this, "quit-application");
+ lazy.UpdateLog.addConfigChangeListener(() => {
+ this._logStatus();
+ });
+
+ this._logStatus();
+ }
/**
* Handle Observer Service notifications
@@ -2706,7 +2706,7 @@ UpdateService.prototype = {
* @param data
* Additional data
*/
- observe: async function AUS_observe(subject, topic, data) {
+ async observe(subject, topic, data) {
switch (topic) {
case "post-update-processing":
// This pref was not cleared out of profiles after it stopped being used
@@ -2790,7 +2790,7 @@ UpdateService.prototype = {
}
break;
}
- },
+ }
/**
* The following needs to happen during the post-update-processing
@@ -2808,7 +2808,7 @@ UpdateService.prototype = {
* notify the user of install success.
*/
/* eslint-disable-next-line complexity */
- _postUpdateProcessing: async function AUS__postUpdateProcessing() {
+ async _postUpdateProcessing() {
if (this.disabled) {
// This function is a point when we can potentially enter the update
// system, even with update disabled. Make sure that we do not continue
@@ -3178,13 +3178,13 @@ UpdateService.prototype = {
// Something went wrong with the patch application process.
await handleFallbackToCompleteUpdate();
}
- },
+ }
/**
* Register an observer when the network comes online, so we can short-circuit
* the app.update.interval when there isn't connectivity
*/
- _registerOnlineObserver: function AUS__registerOnlineObserver() {
+ _registerOnlineObserver() {
if (this._registeredOnlineObserver) {
LOG(
"UpdateService:_registerOnlineObserver - observer already registered"
@@ -3199,12 +3199,12 @@ UpdateService.prototype = {
Services.obs.addObserver(this, "network:offline-status-changed");
this._registeredOnlineObserver = true;
- },
+ }
/**
* Called from the network:offline-status-changed observer.
*/
- _offlineStatusChanged: async function AUS__offlineStatusChanged(status) {
+ async _offlineStatusChanged(status) {
if (status !== "online") {
return;
}
@@ -3219,12 +3219,12 @@ UpdateService.prototype = {
// the background checker is contained in notify
await this._attemptResume();
- },
+ }
/**
* See nsIUpdateService.idl
*/
- onCheckComplete: async function AUS_onCheckComplete(result) {
+ async onCheckComplete(result) {
if (result.succeeded) {
await this._selectAndInstallUpdate(result.updates);
return;
@@ -3307,12 +3307,12 @@ UpdateService.prototype = {
);
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_GENERAL_ERROR_SILENT);
}
- },
+ }
/**
* Called when a connection should be resumed
*/
- _attemptResume: async function AUS_attemptResume() {
+ async _attemptResume() {
LOG("UpdateService:_attemptResume");
// If a download is in progress and we aren't already downloading it, then
// resume it.
@@ -3348,23 +3348,23 @@ UpdateService.prototype = {
let check = lazy.CheckSvc.checkForUpdates(lazy.CheckSvc.BACKGROUND_CHECK);
await this.onCheckComplete(await check.result);
})();
- },
+ }
/**
* Notified when a timer fires
* @param _timer
* The timer that fired
*/
- notify: function AUS_notify(_timer) {
+ notify(_timer) {
this._checkForBackgroundUpdates(true);
- },
+ }
/**
* See nsIUpdateService.idl
*/
- checkForBackgroundUpdates: function AUS_checkForBackgroundUpdates() {
+ checkForBackgroundUpdates() {
return this._checkForBackgroundUpdates(false);
- },
+ }
// The suffix used for background update check telemetry histogram ID's.
get _pingSuffix() {
@@ -3376,7 +3376,7 @@ UpdateService.prototype = {
return AUSTLMY.SUBSEQUENT;
}
return this._isNotify ? AUSTLMY.NOTIFY : AUSTLMY.EXTERNAL;
- },
+ }
/**
* Checks for updates in the background.
@@ -3384,9 +3384,7 @@ UpdateService.prototype = {
* Whether or not a background update check was initiated by the
* application update timer notification.
*/
- _checkForBackgroundUpdates: function AUS__checkForBackgroundUpdates(
- isNotify
- ) {
+ _checkForBackgroundUpdates(isNotify) {
if (!this.disabled && AppConstants.NIGHTLY_BUILD) {
// Scalar ID: update.suppress_prompts
AUSTLMY.pingSuppressPrompts();
@@ -3584,7 +3582,7 @@ UpdateService.prototype = {
})();
return true;
- },
+ }
/**
* Determine the update from the specified updates that should be offered.
@@ -3594,7 +3592,7 @@ UpdateService.prototype = {
* An array of available nsIUpdate items
* @return The nsIUpdate to offer.
*/
- selectUpdate: function AUS_selectUpdate(updates) {
+ selectUpdate(updates) {
if (!updates.length) {
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_NO_UPDATE_FOUND);
return null;
@@ -3753,7 +3751,7 @@ UpdateService.prototype = {
}
return update;
- },
+ }
/**
* Determine which of the specified updates should be installed and begin the
@@ -3761,7 +3759,7 @@ UpdateService.prototype = {
* @param updates
* An array of available updates
*/
- _selectAndInstallUpdate: async function AUS__selectAndInstallUpdate(updates) {
+ async _selectAndInstallUpdate(updates) {
// Return early if there's an active update. The user is already aware and
// is downloading or performed some user action to prevent notification.
if (lazy.UM.downloadingUpdate) {
@@ -3842,14 +3840,14 @@ UpdateService.prototype = {
cleanupDownloadingUpdate();
}
AUSTLMY.pingCheckCode(this._pingSuffix, AUSTLMY.CHK_DOWNLOAD_UPDATE);
- },
+ }
/**
* See nsIUpdateService.idl
*/
get isAppBaseDirWritable() {
return isAppBaseDirWritable();
- },
+ }
get disabledForTesting() {
return (
@@ -3858,7 +3856,7 @@ UpdateService.prototype = {
lazy.RemoteAgent.running) &&
Services.prefs.getBoolPref(PREF_APP_UPDATE_DISABLEDFORTESTING, false)
);
- },
+ }
/**
* See nsIUpdateService.idl
@@ -3869,7 +3867,7 @@ UpdateService.prototype = {
this.disabledForTesting ||
Services.sysinfo.getProperty("isPackagedApp")
);
- },
+ }
/**
* See nsIUpdateService.idl
@@ -3878,7 +3876,7 @@ UpdateService.prototype = {
return (
Services.policies && !Services.policies.isAllowed("autoAppUpdateChecking")
);
- },
+ }
/**
* See nsIUpdateService.idl
@@ -3912,7 +3910,7 @@ UpdateService.prototype = {
LOG("UpdateService.canUsuallyCheckForUpdates - able to check for updates");
return true;
- },
+ }
/**
* See nsIUpdateService.idl
@@ -3941,21 +3939,21 @@ UpdateService.prototype = {
LOG("UpdateService.canCheckForUpdates - able to check for updates");
return true;
- },
+ }
/**
* See nsIUpdateService.idl
*/
get elevationRequired() {
return getElevationRequired();
- },
+ }
/**
* See nsIUpdateService.idl
*/
get canUsuallyApplyUpdates() {
return getCanApplyUpdates();
- },
+ }
/**
* See nsIUpdateService.idl
@@ -3966,42 +3964,42 @@ UpdateService.prototype = {
hasUpdateMutex() &&
!isOtherInstanceRunning()
);
- },
+ }
/**
* See nsIUpdateService.idl
*/
get canUsuallyStageUpdates() {
return getCanStageUpdates(false);
- },
+ }
/**
* See nsIUpdateService.idl
*/
get canStageUpdates() {
return getCanStageUpdates();
- },
+ }
/**
* See nsIUpdateService.idl
*/
get canUsuallyUseBits() {
return getCanUseBits(false) == "CanUseBits";
- },
+ }
/**
* See nsIUpdateService.idl
*/
get canUseBits() {
return getCanUseBits() == "CanUseBits";
- },
+ }
/**
* See nsIUpdateService.idl
*/
get isOtherInstanceHandlingUpdates() {
return !hasUpdateMutex() || isOtherInstanceRunning();
- },
+ }
/**
* A set of download listeners to be notified by this._downloader when it
@@ -4010,12 +4008,12 @@ UpdateService.prototype = {
* These are stored on the UpdateService rather than on the Downloader,
* because they ought to persist across multiple Downloader instances.
*/
- _downloadListeners: new Set(),
+ _downloadListeners = new Set();
/**
* See nsIUpdateService.idl
*/
- addDownloadListener: function AUS_addDownloadListener(listener) {
+ addDownloadListener(listener) {
let oldSize = this._downloadListeners.size;
this._downloadListeners.add(listener);
@@ -4030,12 +4028,12 @@ UpdateService.prototype = {
if (this._downloader) {
this._downloader.onDownloadListenerAdded();
}
- },
+ }
/**
* See nsIUpdateService.idl
*/
- removeDownloadListener: function AUS_removeDownloadListener(listener) {
+ removeDownloadListener(listener) {
let elementRemoved = this._downloadListeners.delete(listener);
if (!elementRemoved) {
@@ -4049,29 +4047,29 @@ UpdateService.prototype = {
if (this._downloader) {
this._downloader.onDownloadListenerRemoved();
}
- },
+ }
/**
* Returns a boolean indicating whether there are any download listeners
*/
get hasDownloadListeners() {
return !!this._downloadListeners.length;
- },
+ }
/*
* Calls the provided function once with each download listener that is
* currently registered.
*/
- forEachDownloadListener: function AUS_forEachDownloadListener(fn) {
+ forEachDownloadListener(fn) {
// Make a shallow copy in case listeners remove themselves.
let listeners = new Set(this._downloadListeners);
listeners.forEach(fn);
- },
+ }
/**
* See nsIUpdateService.idl
*/
- downloadUpdate: async function AUS_downloadUpdate(update) {
+ async downloadUpdate(update) {
if (!update) {
throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER);
}
@@ -4133,12 +4131,12 @@ UpdateService.prototype = {
}
this._downloader = new Downloader(this);
return this._downloader.downloadUpdate(update);
- },
+ }
/**
* See nsIUpdateService.idl
*/
- stopDownload: async function AUS_stopDownload() {
+ async stopDownload() {
if (this.isDownloading) {
await this._downloader.cancel();
} else if (this._retryTimer) {
@@ -4154,7 +4152,7 @@ UpdateService.prototype = {
await this._downloader.cleanup();
}
this._downloader = null;
- },
+ }
/**
* Note that this is different from checking if `currentState` is
@@ -4163,9 +4161,9 @@ UpdateService.prototype = {
*/
get isDownloading() {
return this._downloader && this._downloader.isBusy;
- },
+ }
- _logStatus: function AUS__logStatus() {
+ _logStatus() {
if (!lazy.UpdateLog.enabled) {
return;
}
@@ -4211,21 +4209,21 @@ UpdateService.prototype = {
}
}
LOG("End of UpdateService status");
- },
+ }
/**
* See nsIUpdateService.idl
*/
get onlyDownloadUpdatesThisSession() {
return gOnlyDownloadUpdatesThisSession;
- },
+ }
/**
* See nsIUpdateService.idl
*/
set onlyDownloadUpdatesThisSession(newValue) {
gOnlyDownloadUpdatesThisSession = newValue;
- },
+ }
/**
* See nsIUpdateService.idl
@@ -4244,129 +4242,129 @@ UpdateService.prototype = {
return "STATE_SWAP";
}
return `[unknown update state: ${state}]`;
- },
+ }
/**
* See nsIUpdateService.idl
*/
get currentState() {
return gUpdateState;
- },
+ }
/**
* See nsIUpdateService.idl
*/
get stateTransition() {
return gStateTransitionPromise.promise;
- },
-
- classID: UPDATESERVICE_CID,
-
- QueryInterface: ChromeUtils.generateQI([
- "nsIApplicationUpdateService",
- "nsITimerCallback",
- "nsIObserver",
- ]),
-};
-
-/**
- * A service to manage active and past updates.
- * @constructor
- */
-export function UpdateManager() {
- // Load the active-update.xml file to see if there is an active update.
- let activeUpdates = this._loadXMLFileIntoArray(FILE_ACTIVE_UPDATE_XML);
- if (activeUpdates.length) {
- // Set the active update directly on the var used to cache the value.
- this._readyUpdate = activeUpdates[0];
- if (activeUpdates.length >= 2) {
- this._downloadingUpdate = activeUpdates[1];
- }
- let status = readStatusFile(getReadyUpdateDir());
- LOG(`UpdateManager:UpdateManager - status = "${status}"`);
- // This check is performed here since UpdateService:_postUpdateProcessing
- // won't be called when there isn't an update.status file.
- if (status == STATE_NONE) {
- // Under some edgecases such as Windows system restore the
- // active-update.xml will contain a pending update without the status
- // file. To recover from this situation clean the updates dir and move
- // the active update to the update history.
- LOG(
- "UpdateManager:UpdateManager - Found update data with no status " +
- "file. Cleaning up..."
- );
- this._readyUpdate.state = STATE_FAILED;
- this._readyUpdate.errorCode = ERR_UPDATE_STATE_NONE;
- this._readyUpdate.statusText =
- lazy.gUpdateBundle.GetStringFromName("statusFailed");
- let newStatus = STATE_FAILED + ": " + ERR_UPDATE_STATE_NONE;
- pingStateAndStatusCodes(this._readyUpdate, true, newStatus);
- this.addUpdateToHistory(this._readyUpdate);
- this._readyUpdate = null;
- this.saveUpdates();
- cleanUpReadyUpdateDir();
- cleanUpDownloadingUpdateDir();
- } else if (status == STATE_DOWNLOADING) {
- // The first update we read out of activeUpdates may not be the ready
- // update, it may be the downloading update.
- if (this._downloadingUpdate) {
- // If the first update we read is a downloading update, it's
- // unexpected to have read another active update. That would seem to
- // indicate that we were downloading two updates at once, which we don't
- // do.
- LOG(
- "UpdateManager:UpdateManager - Warning: Found and discarded a " +
- "second downloading update."
- );
- }
- this._downloadingUpdate = this._readyUpdate;
- this._readyUpdate = null;
- }
}
- LOG(
- "UpdateManager:UpdateManager - Initialized downloadingUpdate to " +
- this._downloadingUpdate
- );
- if (this._downloadingUpdate) {
- LOG(
- "UpdateManager:UpdateManager - Initialized downloadingUpdate state to " +
- this._downloadingUpdate.state
- );
- }
- LOG(
- "UpdateManager:UpdateManager - Initialized readyUpdate to " +
- this._readyUpdate
- );
- if (this._readyUpdate) {
- LOG(
- "UpdateManager:UpdateManager - Initialized readyUpdate state to " +
- this._readyUpdate.state
- );
- }
+ classID = UPDATESERVICE_CID;
+
+ QueryInterface = ChromeUtils.generateQI([
+ Ci.nsIApplicationUpdateService,
+ Ci.nsITimerCallback,
+ Ci.nsIObserver,
+ ]);
}
-UpdateManager.prototype = {
+export class UpdateManager {
/**
* The nsIUpdate object for the update that has been downloaded.
*/
- _readyUpdate: null,
+ _readyUpdate = null;
/**
* The nsIUpdate object for the update currently being downloaded.
*/
- _downloadingUpdate: null,
+ _downloadingUpdate = null;
/**
* Whether the update history stored in _updates has changed since it was
* loaded.
*/
- _updatesDirty: false,
+ _updatesDirty = false;
+
+ /**
+ * A service to manage active and past updates.
+ * @constructor
+ */
+ constructor() {
+ // Load the active-update.xml file to see if there is an active update.
+ let activeUpdates = this._loadXMLFileIntoArray(FILE_ACTIVE_UPDATE_XML);
+ if (activeUpdates.length) {
+ // Set the active update directly on the var used to cache the value.
+ this._readyUpdate = activeUpdates[0];
+ if (activeUpdates.length >= 2) {
+ this._downloadingUpdate = activeUpdates[1];
+ }
+ let status = readStatusFile(getReadyUpdateDir());
+ LOG(`UpdateManager:UpdateManager - status = "${status}"`);
+ // This check is performed here since UpdateService:_postUpdateProcessing
+ // won't be called when there isn't an update.status file.
+ if (status == STATE_NONE) {
+ // Under some edgecases such as Windows system restore the
+ // active-update.xml will contain a pending update without the status
+ // file. To recover from this situation clean the updates dir and move
+ // the active update to the update history.
+ LOG(
+ "UpdateManager:UpdateManager - Found update data with no status " +
+ "file. Cleaning up..."
+ );
+ this._readyUpdate.state = STATE_FAILED;
+ this._readyUpdate.errorCode = ERR_UPDATE_STATE_NONE;
+ this._readyUpdate.statusText =
+ lazy.gUpdateBundle.GetStringFromName("statusFailed");
+ let newStatus = STATE_FAILED + ": " + ERR_UPDATE_STATE_NONE;
+ pingStateAndStatusCodes(this._readyUpdate, true, newStatus);
+ this.addUpdateToHistory(this._readyUpdate);
+ this._readyUpdate = null;
+ this.saveUpdates();
+ cleanUpReadyUpdateDir();
+ cleanUpDownloadingUpdateDir();
+ } else if (status == STATE_DOWNLOADING) {
+ // The first update we read out of activeUpdates may not be the ready
+ // update, it may be the downloading update.
+ if (this._downloadingUpdate) {
+ // If the first update we read is a downloading update, it's
+ // unexpected to have read another active update. That would seem to
+ // indicate that we were downloading two updates at once, which we don't
+ // do.
+ LOG(
+ "UpdateManager:UpdateManager - Warning: Found and discarded a " +
+ "second downloading update."
+ );
+ }
+ this._downloadingUpdate = this._readyUpdate;
+ this._readyUpdate = null;
+ }
+ }
+
+ LOG(
+ "UpdateManager:UpdateManager - Initialized downloadingUpdate to " +
+ this._downloadingUpdate
+ );
+ if (this._downloadingUpdate) {
+ LOG(
+ "UpdateManager:UpdateManager - Initialized downloadingUpdate state to " +
+ this._downloadingUpdate.state
+ );
+ }
+ LOG(
+ "UpdateManager:UpdateManager - Initialized readyUpdate to " +
+ this._readyUpdate
+ );
+ if (this._readyUpdate) {
+ LOG(
+ "UpdateManager:UpdateManager - Initialized readyUpdate state to " +
+ this._readyUpdate.state
+ );
+ }
+ }
/**
* See nsIObserver.idl
*/
- observe: function UM_observe(subject, topic, data) {
+ observe(subject, topic, data) {
// Hack to be able to run and cleanup tests by reloading the update data.
if (topic == "um-reload-update-data") {
if (!Cu.isInAutomation) {
@@ -4431,7 +4429,7 @@ UpdateManager.prototype = {
);
}
}
- },
+ }
/**
* Loads an updates.xml formatted file into an array of nsIUpdate items.
@@ -4439,7 +4437,7 @@ UpdateManager.prototype = {
* The file name in the updates directory to load.
* @return The array of nsIUpdate items held in the file.
*/
- _loadXMLFileIntoArray: function UM__loadXMLFileIntoArray(fileName) {
+ _loadXMLFileIntoArray(fileName) {
let updates = [];
let file = getUpdateFile([fileName]);
if (!file.exists()) {
@@ -4528,7 +4526,7 @@ UpdateManager.prototype = {
}
}
return updates;
- },
+ }
/**
* Loads the update history from the updates.xml file into a cache.
@@ -4538,41 +4536,41 @@ UpdateManager.prototype = {
this._updatesCache = this._loadXMLFileIntoArray(FILE_UPDATES_XML);
}
return this._updatesCache;
- },
+ }
/**
* See nsIUpdateService.idl
*/
- getUpdateAt: function UM_getUpdateAt(aIndex) {
+ getUpdateAt(aIndex) {
return this._getUpdates()[aIndex];
- },
+ }
/**
* See nsIUpdateService.idl
*/
getUpdateCount() {
return this._getUpdates().length;
- },
+ }
/**
* See nsIUpdateService.idl
*/
get readyUpdate() {
return this._readyUpdate;
- },
+ }
set readyUpdate(aUpdate) {
this._readyUpdate = aUpdate;
- },
+ }
/**
* See nsIUpdateService.idl
*/
get downloadingUpdate() {
return this._downloadingUpdate;
- },
+ }
set downloadingUpdate(aUpdate) {
this._downloadingUpdate = aUpdate;
- },
+ }
/**
* See nsIUpdateService.idl
@@ -4583,7 +4581,7 @@ UpdateManager.prototype = {
updates.unshift(aUpdate);
// Limit the update history to 10 updates.
updates.splice(10);
- },
+ }
/**
* Serializes an array of updates to an XML file or removes the file if the
@@ -4594,10 +4592,7 @@ UpdateManager.prototype = {
* The file name in the updates directory to write to.
* @return true on success, false on error
*/
- _writeUpdatesToXMLFile: async function UM__writeUpdatesToXMLFile(
- updates,
- fileName
- ) {
+ async _writeUpdatesToXMLFile(updates, fileName) {
let file;
try {
file = getUpdateFile([fileName]);
@@ -4656,14 +4651,14 @@ UpdateManager.prototype = {
return false;
}
return true;
- },
+ }
- _updatesXMLSaver: null,
- _updatesXMLSaverCallback: null,
+ _updatesXMLSaver = null;
+ _updatesXMLSaverCallback = null;
/**
* See nsIUpdateService.idl
*/
- saveUpdates: function UM_saveUpdates() {
+ saveUpdates() {
if (!this._updatesXMLSaver) {
this._updatesXMLSaverCallback = () => this._updatesXMLSaver.finalize();
@@ -4680,13 +4675,13 @@ UpdateManager.prototype = {
}
this._updatesXMLSaver.arm();
- },
+ }
/**
* Saves the active-updates.xml and updates.xml when the updates history has
* been modified files.
*/
- _saveUpdatesXML: function UM__saveUpdatesXML() {
+ _saveUpdatesXML() {
// This mechanism for how we store the updates might seem a bit odd, since,
// if only one update is stored, we don't know if it's the ready update or
// the downloading update. However, we can determine which it is by reading
@@ -4718,12 +4713,12 @@ UpdateManager.prototype = {
);
}
return Promise.all(promises);
- },
+ }
/**
* See nsIUpdateService.idl
*/
- refreshUpdateStatus: async function UM_refreshUpdateStatus() {
+ async refreshUpdateStatus() {
try {
LOG("UpdateManager:refreshUpdateStatus - Staging done.");
@@ -4855,12 +4850,12 @@ UpdateManager.prototype = {
transitionState(Ci.nsIApplicationUpdateService.STATE_IDLE);
}
}
- },
+ }
/**
* See nsIUpdateService.idl
*/
- elevationOptedIn: function UM_elevationOptedIn() {
+ elevationOptedIn() {
// The user has been been made aware that the update requires elevation.
let update = this._readyUpdate;
if (!update) {
@@ -4884,30 +4879,30 @@ UpdateManager.prototype = {
} else {
LOG("UpdateManager:elevationOptedIn - Not in pending-elevate state.");
}
- },
+ }
/**
* See nsIUpdateService.idl
*/
- cleanupDownloadingUpdate: function UM_cleanupDownloadingUpdate() {
+ cleanupDownloadingUpdate() {
LOG(
"UpdateManager:cleanupDownloadingUpdate - cleaning up downloading update."
);
cleanupDownloadingUpdate();
- },
+ }
/**
* See nsIUpdateService.idl
*/
- cleanupReadyUpdate: function UM_cleanupReadyUpdate() {
+ cleanupReadyUpdate() {
LOG("UpdateManager:cleanupReadyUpdate - cleaning up ready update.");
cleanupReadyUpdate();
- },
+ }
/**
* See nsIUpdateService.idl
*/
- doInstallCleanup: async function UM_doInstallCleanup() {
+ async doInstallCleanup() {
LOG("UpdateManager:doInstallCleanup - cleaning up");
let completionPromises = [];
@@ -4944,12 +4939,12 @@ UpdateManager.prototype = {
}
return Promise.allSettled(completionPromises);
- },
+ }
/**
* See nsIUpdateService.idl
*/
- doUninstallCleanup: async function UM_doUninstallCleanup() {
+ async doUninstallCleanup() {
LOG("UpdateManager:doUninstallCleanup - cleaning up.");
let completionPromises = [];
@@ -4965,11 +4960,14 @@ UpdateManager.prototype = {
);
return Promise.allSettled(completionPromises);
- },
+ }
- classID: Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}"),
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateManager", "nsIObserver"]),
-};
+ classID = Components.ID("{093C2356-4843-4C65-8709-D7DBCBBE7DFB}");
+ QueryInterface = ChromeUtils.generateQI([
+ Ci.nsIUpdateManager,
+ Ci.nsIObserver,
+ ]);
+}
/**
* CheckerService
@@ -5199,7 +5197,7 @@ export class CheckerService {
return {
id: checkId,
result: this.#updateCheckData[requestKey].promise,
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheck"]),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateCheck]),
};
}
@@ -5212,10 +5210,10 @@ export class CheckerService {
succeeded: false,
request: null,
updates: [],
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheckResult"]),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateCheckResult]),
})
),
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheck"]),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateCheck]),
};
}
@@ -5370,7 +5368,7 @@ export class CheckerService {
succeeded: true,
request,
updates,
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheckResult"]),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateCheckResult]),
});
}
@@ -5408,7 +5406,7 @@ export class CheckerService {
succeeded: false,
request,
updates: [update],
- QueryInterface: ChromeUtils.generateQI(["nsIUpdateCheckResult"]),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIUpdateCheckResult]),
});
}
@@ -5523,51 +5521,38 @@ export class CheckerService {
}
classID = Components.ID("{898CDC9B-E43F-422F-9CC4-2F6291B415A3}");
- QueryInterface = ChromeUtils.generateQI(["nsIUpdateChecker"]);
+ QueryInterface = ChromeUtils.generateQI([Ci.nsIUpdateChecker]);
}
-/**
- * Manages the download of updates
- * @param background
- * Whether or not this downloader is operating in background
- * update mode.
- * @param updateService
- * The update service that created this downloader.
- * @constructor
- */
-function Downloader(updateService) {
- LOG("Creating Downloader");
- this.updateService = updateService;
-}
-Downloader.prototype = {
+class Downloader {
/**
* The nsIUpdatePatch that we are downloading
*/
- _patch: null,
+ _patch = null;
/**
* The nsIUpdate that we are downloading
*/
- _update: null,
+ _update = null;
/**
* The nsIRequest object handling the download.
*/
- _request: null,
+ _request = null;
/**
* Whether or not the update being downloaded is a complete replacement of
* the user's existing installation or a patch representing the difference
* between the new version and the previous version.
*/
- isCompleteUpdate: null,
+ isCompleteUpdate = null;
/**
* We get the nsIRequest from nsIBITS asynchronously. When downloadUpdate has
* been called, but this._request is not yet valid, _pendingRequest will be
* a promise that will resolve when this._request has been set.
*/
- _pendingRequest: null,
+ _pendingRequest = null;
/**
* When using BITS, cancel actions happen asynchronously. This variable
@@ -5577,7 +5562,7 @@ Downloader.prototype = {
* resolved promise will remain stored in this variable to prevent cancel
* from being called twice (which, for BITS, is an error).
*/
- _cancelPromise: null,
+ _cancelPromise = null;
/**
* BITS receives progress notifications slowly, unless a user is watching.
@@ -5591,14 +5576,14 @@ Downloader.prototype = {
* we don't know if we need to start Active mode when _pendingRequest
* resolves.
*/
- _bitsActiveNotifications: false,
+ _bitsActiveNotifications = false;
/**
* This is a function that when called will stop the update process from
* waiting for language pack updates. This is for safety to ensure that a
* problem in the add-ons manager doesn't delay updates by much.
*/
- _langPackTimeout: null,
+ _langPackTimeout = null;
/**
* If gOnlyDownloadUpdatesThisSession is true, we prevent the update process
@@ -5606,7 +5591,21 @@ Downloader.prototype = {
* pretend that it hasn't in order to keep the current update in the
* "downloading" state.
*/
- _pretendingDownloadIsNotDone: false,
+ _pretendingDownloadIsNotDone = false;
+
+ /**
+ * Manages the download of updates
+ * @param background
+ * Whether or not this downloader is operating in background
+ * update mode.
+ * @param updateService
+ * The update service that created this downloader.
+ * @constructor
+ */
+ constructor(updateService) {
+ LOG("Creating Downloader");
+ this.updateService = updateService;
+ }
/**
* Cancels the active download.
@@ -5615,7 +5614,7 @@ Downloader.prototype = {
* an nsIIncrementalDownload, this will stop the download, but leaves the
* data around to allow the transfer to be resumed later.
*/
- cancel: async function Downloader_cancel(cancelError) {
+ async cancel(cancelError) {
LOG("Downloader: cancel");
if (cancelError === undefined) {
cancelError = Cr.NS_BINDING_ABORTED;
@@ -5664,13 +5663,13 @@ Downloader.prototype = {
this._request.cancel(cancelError);
}
}
- },
+ }
/**
* Verify the downloaded file. We assume that the download is complete at
* this point.
*/
- _verifyDownload: function Downloader__verifyDownload() {
+ _verifyDownload() {
LOG("Downloader:_verifyDownload called");
if (!this._request) {
AUSTLMY.pingDownloadCode(
@@ -5695,7 +5694,7 @@ Downloader.prototype = {
LOG("Downloader:_verifyDownload downloaded size == expected size.");
return true;
- },
+ }
/**
* Select the patch to use given the current state of updateDir and the given
@@ -5704,7 +5703,7 @@ Downloader.prototype = {
* A nsIUpdate object to select a patch from
* @return A nsIUpdatePatch object to download
*/
- _selectPatch: function Downloader__selectPatch(update) {
+ _selectPatch(update) {
// Given an update to download, we will always try to download the patch
// for a partial update over the patch for a full update.
@@ -5809,7 +5808,7 @@ Downloader.prototype = {
lazy.UM.downloadingUpdate = update;
return selectedPatch;
- },
+ }
/**
* Whether or not the user wants to be notified that an update is being
@@ -5820,35 +5819,30 @@ Downloader.prototype = {
PREF_APP_UPDATE_NOTIFYDURINGDOWNLOAD,
false
);
- },
-
- _notifyDownloadStatusObservers:
- function Downloader_notifyDownloadStatusObservers() {
- if (this._notifyDuringDownload) {
- let status = this.updateService.isDownloading ? "downloading" : "idle";
- Services.obs.notifyObservers(
- this._update,
- "update-downloading",
- status
- );
- }
- },
+ }
+
+ _notifyDownloadStatusObservers() {
+ if (this._notifyDuringDownload) {
+ let status = this.updateService.isDownloading ? "downloading" : "idle";
+ Services.obs.notifyObservers(this._update, "update-downloading", status);
+ }
+ }
/**
* Whether or not we are currently downloading something.
*/
get isBusy() {
return this._request != null || this._pendingRequest != null;
- },
+ }
get usingBits() {
return this._pendingRequest != null || this._request instanceof BitsRequest;
- },
+ }
/**
* Returns true if the specified patch can be downloaded with BITS.
*/
- _canUseBits: function Downloader__canUseBits(patch) {
+ _canUseBits(patch) {
if (getCanUseBits() != "CanUseBits") {
// This will have printed its own logging. No need to print more.
return false;
@@ -5863,13 +5857,13 @@ Downloader.prototype = {
}
LOG("Downloader:_canUseBits - Patch is able to use BITS download");
return true;
- },
+ }
/**
* Instruct the add-ons manager to start downloading language pack updates in
* preparation for the current update.
*/
- _startLangPackUpdates: function Downloader__startLangPackUpdates() {
+ _startLangPackUpdates() {
if (!Services.prefs.getBoolPref(PREF_APP_UPDATE_LANGPACK_ENABLED, false)) {
return;
}
@@ -5912,14 +5906,14 @@ Downloader.prototype = {
update,
Promise.race([langPackPromise, timeoutPromise])
);
- },
+ }
/**
* Download and stage the given update.
* @param update
* A nsIUpdate object to download a patch for. Cannot be null.
*/
- downloadUpdate: async function Downloader_downloadUpdate(update) {
+ async downloadUpdate(update) {
LOG("UpdateService:downloadUpdate");
if (!update) {
AUSTLMY.pingDownloadCode(undefined, AUSTLMY.DWNLD_ERR_NO_UPDATE);
@@ -6190,116 +6184,114 @@ Downloader.prototype = {
this._notifyDownloadStatusObservers();
return true;
- },
+ }
/**
* This is run when a download listener is added.
*/
- onDownloadListenerAdded: function Downloader_onDownloadListenerAdded() {
+ onDownloadListenerAdded() {
// Increase the status update frequency when someone starts listening
this._maybeStartActiveNotifications();
- },
+ }
/**
* This is run when a download listener is removed.
*/
- onDownloadListenerRemoved: function Downloader_onDownloadListenerRemoved() {
+ onDownloadListenerRemoved() {
// Decrease the status update frequency when no one is listening
if (!this.hasDownloadListeners) {
this._maybeStopActiveNotifications();
}
- },
+ }
get hasDownloadListeners() {
return this.updateService.hasDownloadListeners;
- },
+ }
/**
* This speeds up BITS progress notifications in response to a user watching
* the notifications.
*/
- _maybeStartActiveNotifications:
- async function Downloader__maybeStartActiveNotifications() {
- if (
- this.usingBits &&
- !this._bitsActiveNotifications &&
- this.hasDownloadListeners &&
+ async _maybeStartActiveNotifications() {
+ if (
+ this.usingBits &&
+ !this._bitsActiveNotifications &&
+ this.hasDownloadListeners &&
+ this._request
+ ) {
+ LOG(
+ "Downloader:_maybeStartActiveNotifications - Starting active " +
+ "notifications"
+ );
+ this._bitsActiveNotifications = true;
+ await Promise.all([
this._request
- ) {
- LOG(
- "Downloader:_maybeStartActiveNotifications - Starting active " +
- "notifications"
- );
- this._bitsActiveNotifications = true;
- await Promise.all([
- this._request
- .setNoProgressTimeout(BITS_ACTIVE_NO_PROGRESS_TIMEOUT_SECS)
- .catch(error => {
- LOG(
- "Downloader:_maybeStartActiveNotifications - Failed to set " +
- "no progress timeout. Error: " +
- error
- );
- }),
- this._request
- .changeMonitorInterval(BITS_ACTIVE_POLL_RATE_MS)
- .catch(error => {
- LOG(
- "Downloader:_maybeStartActiveNotifications - Failed to increase " +
- "status update frequency. Error: " +
- error
- );
- }),
- ]);
- }
- },
+ .setNoProgressTimeout(BITS_ACTIVE_NO_PROGRESS_TIMEOUT_SECS)
+ .catch(error => {
+ LOG(
+ "Downloader:_maybeStartActiveNotifications - Failed to set " +
+ "no progress timeout. Error: " +
+ error
+ );
+ }),
+ this._request
+ .changeMonitorInterval(BITS_ACTIVE_POLL_RATE_MS)
+ .catch(error => {
+ LOG(
+ "Downloader:_maybeStartActiveNotifications - Failed to increase " +
+ "status update frequency. Error: " +
+ error
+ );
+ }),
+ ]);
+ }
+ }
/**
* This slows down BITS progress notifications in response to a user no longer
* watching the notifications.
*/
- _maybeStopActiveNotifications:
- async function Downloader__maybeStopActiveNotifications() {
- if (
- this.usingBits &&
- this._bitsActiveNotifications &&
- !this.hasDownloadListeners &&
+ async _maybeStopActiveNotifications() {
+ if (
+ this.usingBits &&
+ this._bitsActiveNotifications &&
+ !this.hasDownloadListeners &&
+ this._request
+ ) {
+ LOG(
+ "Downloader:_maybeStopActiveNotifications - Stopping active " +
+ "notifications"
+ );
+ this._bitsActiveNotifications = false;
+ await Promise.all([
this._request
- ) {
- LOG(
- "Downloader:_maybeStopActiveNotifications - Stopping active " +
- "notifications"
- );
- this._bitsActiveNotifications = false;
- await Promise.all([
- this._request
- .setNoProgressTimeout(BITS_IDLE_NO_PROGRESS_TIMEOUT_SECS)
- .catch(error => {
- LOG(
- "Downloader:_maybeStopActiveNotifications - Failed to set " +
- "no progress timeout: " +
- error
- );
- }),
- this._request
- .changeMonitorInterval(BITS_IDLE_POLL_RATE_MS)
- .catch(error => {
- LOG(
- "Downloader:_maybeStopActiveNotifications - Failed to decrease " +
- "status update frequency: " +
- error
- );
- }),
- ]);
- }
- },
+ .setNoProgressTimeout(BITS_IDLE_NO_PROGRESS_TIMEOUT_SECS)
+ .catch(error => {
+ LOG(
+ "Downloader:_maybeStopActiveNotifications - Failed to set " +
+ "no progress timeout: " +
+ error
+ );
+ }),
+ this._request
+ .changeMonitorInterval(BITS_IDLE_POLL_RATE_MS)
+ .catch(error => {
+ LOG(
+ "Downloader:_maybeStopActiveNotifications - Failed to decrease " +
+ "status update frequency: " +
+ error
+ );
+ }),
+ ]);
+ }
+ }
/**
* When the async request begins
* @param request
* The nsIRequest object for the transfer
*/
- onStartRequest: function Downloader_onStartRequest(request) {
+ onStartRequest(request) {
if (this.usingBits) {
LOG("Downloader:onStartRequest");
} else {
@@ -6319,7 +6311,7 @@ Downloader.prototype = {
this.updateService.forEachDownloadListener(listener => {
listener.onStartRequest(request);
});
- },
+ }
/**
* When new data has been downloaded
@@ -6330,7 +6322,7 @@ Downloader.prototype = {
* @param maxProgress
* The total number of bytes that must be transferred
*/
- onProgress: function Downloader_onProgress(request, progress, maxProgress) {
+ onProgress(request, progress, maxProgress) {
LOG("Downloader:onProgress - progress: " + progress + "/" + maxProgress);
if (progress > this._patch.size) {
@@ -6372,7 +6364,7 @@ Downloader.prototype = {
}
});
this.updateService._consecutiveSocketErrors = 0;
- },
+ }
/**
* When we have new status text
@@ -6383,7 +6375,7 @@ Downloader.prototype = {
* @param statusText
* Human readable version of |status|
*/
- onStatus: function Downloader_onStatus(request, status, statusText) {
+ onStatus(request, status, statusText) {
LOG(
"Downloader:onStatus - status: " + status + ", statusText: " + statusText
);
@@ -6393,7 +6385,7 @@ Downloader.prototype = {
listener.onStatus(request, status, statusText);
}
});
- },
+ }
/**
* When data transfer ceases
@@ -6403,7 +6395,7 @@ Downloader.prototype = {
* Status code containing the reason for the cessation.
*/
/* eslint-disable-next-line complexity */
- onStopRequest: async function Downloader_onStopRequest(request, status) {
+ async onStopRequest(request, status) {
if (gOnlyDownloadUpdatesThisSession) {
LOG(
"Downloader:onStopRequest - End of update download detected and " +
@@ -6936,25 +6928,25 @@ Downloader.prototype = {
// Prevent leaking the update object (bug 454964)
this._update = null;
}
- },
+ }
/**
* This function should be called when shutting down so that resources get
* freed properly.
*/
- cleanup: async function Downloader_cleanup() {
+ async cleanup() {
if (this.usingBits) {
if (this._pendingRequest) {
await this._pendingRequest;
}
this._request.shutdown();
}
- },
+ }
/**
* See nsIInterfaceRequestor.idl
*/
- getInterface: function Downloader_getInterface(iid) {
+ getInterface(iid) {
// The network request may require proxy authentication, so provide the
// default nsIAuthPrompt if requested.
if (iid.equals(Ci.nsIAuthPrompt)) {
@@ -6963,14 +6955,14 @@ Downloader.prototype = {
return prompt.QueryInterface(iid);
}
throw Components.Exception("", Cr.NS_NOINTERFACE);
- },
+ }
- QueryInterface: ChromeUtils.generateQI([
- "nsIRequestObserver",
- "nsIProgressEventSink",
- "nsIInterfaceRequestor",
- ]),
-};
+ QueryInterface = ChromeUtils.generateQI([
+ Ci.nsIRequestObserver,
+ Ci.nsIProgressEventSink,
+ Ci.nsIInterfaceRequestor,
+ ]);
+}
// On macOS, all browser windows can be closed without Firefox exiting. If it
// is left in this state for a while and an update is pending, we should restart
diff --git a/toolkit/mozapps/update/common/readstrings.cpp b/toolkit/mozapps/update/common/readstrings.cpp
index 28dc8ea6ff..4d09c9d9a4 100644
--- a/toolkit/mozapps/update/common/readstrings.cpp
+++ b/toolkit/mozapps/update/common/readstrings.cpp
@@ -144,7 +144,7 @@ int ReadStrings(const NS_tchar* path, const char* keyList,
size_t flen = size_t(len);
- char* fileContents = new char[flen + 1];
+ mozilla::UniquePtr<char[]> fileContents(new char[flen + 1]);
if (!fileContents) {
return READ_STRINGS_MEM_ERROR;
}
@@ -154,16 +154,15 @@ int ReadStrings(const NS_tchar* path, const char* keyList,
return READ_ERROR;
}
- size_t rd = fread(fileContents, sizeof(char), flen, fp);
+ size_t rd = fread(fileContents.get(), sizeof(char), flen, fp);
if (rd != flen) {
return READ_ERROR;
}
fileContents[flen] = '\0';
- int result = ReadStringsFromBuffer(fileContents, keyList, numStrings, results,
- section);
- delete[] fileContents;
+ int result = ReadStringsFromBuffer(fileContents.get(), keyList, numStrings,
+ results, section);
return result;
}
diff --git a/toolkit/mozapps/update/docs/MaintenanceServiceTests.rst b/toolkit/mozapps/update/docs/MaintenanceServiceTests.rst
index b954b572f8..65259c94d9 100644
--- a/toolkit/mozapps/update/docs/MaintenanceServiceTests.rst
+++ b/toolkit/mozapps/update/docs/MaintenanceServiceTests.rst
@@ -47,11 +47,11 @@ into the registry.
[HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4]
[HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4\0]
- "issuer"="DigiCert SHA2 Assured ID Code Signing CA"
+ "issuer"="DigiCert Trusted G4 Code Signing RSA4096 SHA384 2021 CA1"
"name"="Mozilla Corporation"
[HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4\1]
- "issuer"="DigiCert Assured ID Code Signing CA-1"
+ "issuer"="DigiCert SHA2 Assured ID Code Signing CA"
"name"="Mozilla Corporation"
[HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\MaintenanceService\3932ecacee736d366d6436db0f55bce4\2]
diff --git a/toolkit/mozapps/update/nsUpdateService.manifest b/toolkit/mozapps/update/nsUpdateService.manifest
index a8d2534b1e..6db9b00eb3 100644
--- a/toolkit/mozapps/update/nsUpdateService.manifest
+++ b/toolkit/mozapps/update/nsUpdateService.manifest
@@ -1 +1 @@
-category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,43200,86400
+category update-timer nsUpdateService @mozilla.org/updates/update-service;1,getService,background-update-timer,app.update.interval,21600,86400
diff --git a/toolkit/mozapps/update/updater/Makefile.in b/toolkit/mozapps/update/updater/Makefile.in
index ec3ad9773a..f93a374428 100644
--- a/toolkit/mozapps/update/updater/Makefile.in
+++ b/toolkit/mozapps/update/updater/Makefile.in
@@ -5,6 +5,12 @@
# For changes here, also consider ./updater-xpcshell/Makefile.in
+# In a compile build, the moz.build stanzas produce the binary named
+# `UpdateSettings`; we just need it in the correct place.
+#
+# In an artifact build, we copy upstream artifacts from
+# `dist/update_framework_artifacts/UpdateSettings-localbuild.framework`
+
ifndef MOZ_WINCONSOLE
ifdef MOZ_DEBUG
MOZ_WINCONSOLE = 1
@@ -26,6 +32,11 @@ libs::
$(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/MacOS
$(NSINSTALL) $(DIST)/bin/org.mozilla.updater $(DIST)/bin/updater.app/Contents/MacOS
$(NSINSTALL) -D $(DIST)/bin/updater.app/Contents/Frameworks
+ifneq (,$(COMPILE_ENVIRONMENT))
$(NSINSTALL) $(DIST)/bin/UpdateSettings $(DIST)/bin/updater.app/Contents/Frameworks/UpdateSettings.framework
+endif # COMPILE_ENVIRONMENT
+ifneq (,$(MOZ_ARTIFACT_BUILDS))
+ $(NSINSTALL) $(DIST)/update_framework_artifacts/UpdateSettings-localbuild.framework/UpdateSettings $(DIST)/bin/updater.app/Contents/Frameworks/UpdateSettings.framework
+endif # MOZ_ARTIFACT_BUILDS
$(NSINSTALL) $(srcdir)/macos-frameworks/UpdateSettings/Info.plist $(DIST)/bin/updater.app/Contents/Frameworks/UpdateSettings.framework/Resources
endif
diff --git a/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/Makefile.in b/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/Makefile.in
new file mode 100644
index 0000000000..2798eecd64
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/Makefile.in
@@ -0,0 +1,25 @@
+# vim:set ts=8 sw=8 sts=8 noet:
+# 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 $(topsrcdir)/config/rules.mk
+
+# In a compile build, the moz.build stanzas produce a binary named
+# `UpdateSettings-localbuild`. We need to produce
+# `dist/update_framework_artifacts/UpdateSettings-localbuild.framework/UpdateSettings`
+# for consumption by artifact builds.
+#
+# In an artifact build, we already have upstream artifacts in
+# `dist/update_framework_artifacts/UpdateSettings-localbuild.framework`.
+
+ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
+libs::
+ifneq (,$(COMPILE_ENVIRONMENT))
+ rm -rf $(FINAL_TARGET)/UpdateSettings-localbuild.framework
+ $(NSINSTALL) -D $(FINAL_TARGET)/UpdateSettings-localbuild.framework
+ cp $(FINAL_TARGET)/UpdateSettings-localbuild UpdateSettings
+ $(NSINSTALL) UpdateSettings $(FINAL_TARGET)/UpdateSettings-localbuild.framework
+endif # COMPILE_ENVIRONMENT
+ $(NSINSTALL) $(srcdir)/../UpdateSettings/Info.plist $(FINAL_TARGET)/UpdateSettings-localbuild.framework/Resources
+endif # MOZ_WIDGET_TOOLKIT
diff --git a/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/moz.build b/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/moz.build
new file mode 100644
index 0000000000..ca3a0c9f1c
--- /dev/null
+++ b/toolkit/mozapps/update/updater/macos-frameworks/UpdateSettings-localbuild/moz.build
@@ -0,0 +1,18 @@
+# -*- 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/.
+
+Framework("UpdateSettings-localbuild")
+FINAL_TARGET = "dist/update_framework_artifacts"
+
+DEFINES["ACCEPTED_MAR_CHANNEL_IDS"] = '""'
+
+UNIFIED_SOURCES += [
+ "../UpdateSettings/UpdateSettings.mm",
+]
+
+OS_LIBS += [
+ "-framework Foundation",
+]
diff --git a/toolkit/mozapps/update/updater/macos-frameworks/moz.build b/toolkit/mozapps/update/updater/macos-frameworks/moz.build
index 19fa11942e..cb49b680c0 100644
--- a/toolkit/mozapps/update/updater/macos-frameworks/moz.build
+++ b/toolkit/mozapps/update/updater/macos-frameworks/moz.build
@@ -7,7 +7,12 @@
with Files("**"):
BUG_COMPONENT = ("Toolkit", "Application Update")
-DIRS += ["UpdateSettings", "UpdateSettings-xpcshell", "UpdateSettings-WrongChannel"]
+DIRS += [
+ "UpdateSettings",
+ "UpdateSettings-localbuild",
+ "UpdateSettings-WrongChannel",
+ "UpdateSettings-xpcshell",
+]
EXPORTS += [
"UpdateSettingsUtil.h",
diff --git a/toolkit/mozapps/update/updater/updater.cpp b/toolkit/mozapps/update/updater/updater.cpp
index 084945dc44..81702d74bc 100644
--- a/toolkit/mozapps/update/updater/updater.cpp
+++ b/toolkit/mozapps/update/updater/updater.cpp
@@ -2992,7 +2992,7 @@ int NS_main(int argc, NS_tchar** argv) {
#ifdef MOZ_VERIFY_MAR_SIGNATURE
int rv = PopulategMARStrings();
if (rv == OK) {
- printf("Channels Allowed: %s\n", gMARStrings.MARChannelID.get());
+ printf("Channels Allowed: '%s'\n", gMARStrings.MARChannelID.get());
return 0;
}
printf("Error: %d\n", rv);