summaryrefslogtreecommitdiffstats
path: root/browser/components/reportbrokensite/ReportBrokenSite.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/reportbrokensite/ReportBrokenSite.sys.mjs')
-rw-r--r--browser/components/reportbrokensite/ReportBrokenSite.sys.mjs770
1 files changed, 770 insertions, 0 deletions
diff --git a/browser/components/reportbrokensite/ReportBrokenSite.sys.mjs b/browser/components/reportbrokensite/ReportBrokenSite.sys.mjs
new file mode 100644
index 0000000000..706c013383
--- /dev/null
+++ b/browser/components/reportbrokensite/ReportBrokenSite.sys.mjs
@@ -0,0 +1,770 @@
+/* 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-env mozilla/browser-window */
+
+const DEFAULT_NEW_REPORT_ENDPOINT = "https://webcompat.com/issues/new";
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ ClientEnvironment: "resource://normandy/lib/ClientEnvironment.sys.mjs",
+});
+
+const gDescriptionCheckRE = /\S/;
+
+class ViewState {
+ #doc;
+ #mainView;
+ #reportSentView;
+ #formElement;
+ #reasonOptions;
+ #randomizeReasons = false;
+
+ currentTabURI;
+ currentTabWebcompatDetailsPromise;
+
+ constructor(doc) {
+ this.#doc = doc;
+ this.#mainView = doc.ownerGlobal.PanelMultiView.getViewNode(
+ this.#doc,
+ "report-broken-site-popup-mainView"
+ );
+ this.#reportSentView = doc.ownerGlobal.PanelMultiView.getViewNode(
+ this.#doc,
+ "report-broken-site-popup-reportSentView"
+ );
+ this.#formElement = doc.ownerGlobal.PanelMultiView.getViewNode(
+ this.#doc,
+ "report-broken-site-panel-form"
+ );
+ ViewState.#cache.set(doc, this);
+
+ this.#reasonOptions = Array.from(
+ // Skip the first option ("choose reason"), since it always stays at the top
+ this.reasonInput.querySelectorAll(`option:not(:first-of-type)`)
+ );
+ }
+
+ static #cache = new WeakMap();
+ static get(doc) {
+ return ViewState.#cache.get(doc) ?? new ViewState(doc);
+ }
+
+ get mainPanelview() {
+ return this.#mainView;
+ }
+
+ get reportSentPanelview() {
+ return this.#reportSentView;
+ }
+
+ get urlInput() {
+ return this.#mainView.querySelector("#report-broken-site-popup-url");
+ }
+
+ get url() {
+ return this.urlInput.value;
+ }
+
+ set url(spec) {
+ this.urlInput.value = spec;
+ }
+
+ resetURLToCurrentTab() {
+ const { currentURI } = this.#doc.ownerGlobal.gBrowser.selectedBrowser;
+ this.currentTabURI = currentURI;
+ this.urlInput.value = currentURI.spec;
+ }
+
+ get descriptionInput() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-description"
+ );
+ }
+
+ get description() {
+ return this.descriptionInput.value;
+ }
+
+ set description(value) {
+ this.descriptionInput.value = value;
+ }
+
+ static REASON_CHOICES_ID_PREFIX = "report-broken-site-popup-reason-";
+
+ get reasonInput() {
+ return this.#mainView.querySelector("#report-broken-site-popup-reason");
+ }
+
+ get reason() {
+ const reason = this.reasonInput.selectedOptions[0].id.replace(
+ ViewState.REASON_CHOICES_ID_PREFIX,
+ ""
+ );
+ return reason == "choose" ? undefined : reason;
+ }
+
+ set reason(value) {
+ this.reasonInput.selectedIndex = this.#mainView.querySelector(
+ `#${ViewState.REASON_CHOICES_ID_PREFIX}${value}`
+ ).index;
+ }
+
+ #randomizeReasonsOrdering() {
+ // As with QuickActionsLoaderDefault, we use the Normandy
+ // randomizationId as our PRNG seed to ensure that the same
+ // user should always get the same sequence.
+ const seed = [...lazy.ClientEnvironment.randomizationId]
+ .map(x => x.charCodeAt(0))
+ .reduce((sum, a) => sum + a, 0);
+
+ const items = [...this.#reasonOptions];
+ this.#shuffleArray(items, seed);
+ items[0].parentNode.append(...items);
+ }
+
+ #shuffleArray(array, seed) {
+ // We use SplitMix as it is reputed to have a strong distribution of values.
+ const prng = this.#getSplitMix32PRNG(seed);
+ for (let i = array.length - 1; i > 0; i--) {
+ const j = Math.floor(prng() * (i + 1));
+ [array[i], array[j]] = [array[j], array[i]];
+ }
+ }
+
+ // SplitMix32 is a splittable pseudorandom number generator (PRNG).
+ // License: MIT (https://github.com/attilabuti/SimplexNoise)
+ #getSplitMix32PRNG(a) {
+ return () => {
+ a |= 0;
+ a = (a + 0x9e3779b9) | 0;
+ var t = a ^ (a >>> 16);
+ t = Math.imul(t, 0x21f0aaad);
+ t = t ^ (t >>> 15);
+ t = Math.imul(t, 0x735a2d97);
+ return ((t = t ^ (t >>> 15)) >>> 0) / 4294967296;
+ };
+ }
+
+ #restoreReasonsOrdering() {
+ this.#reasonOptions[0].parentNode.append(...this.#reasonOptions);
+ }
+
+ get form() {
+ return this.#formElement;
+ }
+
+ reset() {
+ this.currentTabWebcompatDetailsPromise = undefined;
+ this.form.reset();
+
+ this.resetURLToCurrentTab();
+ }
+
+ ensureReasonOrderingMatchesPref() {
+ const randomizeReasons =
+ this.#doc.ownerGlobal.ReportBrokenSite.randomizeReasons;
+ if (randomizeReasons != this.#randomizeReasons) {
+ if (randomizeReasons) {
+ this.#randomizeReasonsOrdering();
+ } else {
+ this.#restoreReasonsOrdering();
+ }
+ this.#randomizeReasons = randomizeReasons;
+ }
+ }
+
+ get isURLValid() {
+ return this.urlInput.checkValidity();
+ }
+
+ get isReasonValid() {
+ const { reasonEnabled, reasonIsOptional } =
+ this.#doc.ownerGlobal.ReportBrokenSite;
+ return (
+ !reasonEnabled || reasonIsOptional || this.reasonInput.checkValidity()
+ );
+ }
+
+ get isDescriptionValid() {
+ const { descriptionIsOptional } = this.#doc.ownerGlobal.ReportBrokenSite;
+ return (
+ descriptionIsOptional ||
+ gDescriptionCheckRE.test(this.descriptionInput.value)
+ );
+ }
+
+ #focusMainViewElement(toFocus) {
+ const panelview = this.#doc.ownerGlobal.PanelView.forNode(this.#mainView);
+ panelview.selectedElement = toFocus;
+ panelview.focusSelectedElement();
+ }
+
+ focusFirstInvalidElement() {
+ if (!this.isURLValid) {
+ this.#focusMainViewElement(this.urlInput);
+ } else if (!this.isReasonValid) {
+ this.#focusMainViewElement(this.reasonInput);
+ this.reasonInput.showPicker();
+ } else if (!this.isDescriptionValid) {
+ this.#focusMainViewElement(this.descriptionInput);
+ }
+ }
+
+ get sendMoreInfoLink() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-send-more-info-link"
+ );
+ }
+
+ get reasonLabelRequired() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-reason-label"
+ );
+ }
+
+ get reasonLabelOptional() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-reason-optional-label"
+ );
+ }
+
+ get descriptionLabelRequired() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-description-label"
+ );
+ }
+
+ get descriptionLabelOptional() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-description-optional-label"
+ );
+ }
+
+ get sendButton() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-send-button"
+ );
+ }
+
+ get cancelButton() {
+ return this.#mainView.querySelector(
+ "#report-broken-site-popup-cancel-button"
+ );
+ }
+
+ get mainView() {
+ return this.#mainView;
+ }
+
+ get reportSentView() {
+ return this.#reportSentView;
+ }
+
+ get okayButton() {
+ return this.#reportSentView.querySelector(
+ "#report-broken-site-popup-okay-button"
+ );
+ }
+}
+
+export var ReportBrokenSite = new (class ReportBrokenSite {
+ #newReportEndpoint = undefined;
+
+ get sendMoreInfoEndpoint() {
+ return this.#newReportEndpoint || DEFAULT_NEW_REPORT_ENDPOINT;
+ }
+
+ static WEBCOMPAT_REPORTER_CONFIG = {
+ src: "desktop-reporter",
+ utm_campaign: "report-broken-site",
+ utm_source: "desktop-reporter",
+ };
+
+ static DATAREPORTING_PREF = "datareporting.healthreport.uploadEnabled";
+ static REPORTER_ENABLED_PREF = "ui.new-webcompat-reporter.enabled";
+
+ static REASON_PREF = "ui.new-webcompat-reporter.reason-dropdown";
+ static REASON_PREF_VALUES = {
+ 0: "disabled",
+ 1: "optional",
+ 2: "required",
+ };
+ static REASON_RANDOMIZED_PREF =
+ "ui.new-webcompat-reporter.reason-dropdown.randomized";
+ static SEND_MORE_INFO_PREF = "ui.new-webcompat-reporter.send-more-info-link";
+ static NEW_REPORT_ENDPOINT_PREF =
+ "ui.new-webcompat-reporter.new-report-endpoint";
+ static REPORT_SITE_ISSUE_PREF = "extensions.webcompat-reporter.enabled";
+
+ static MAIN_PANELVIEW_ID = "report-broken-site-popup-mainView";
+ static SENT_PANELVIEW_ID = "report-broken-site-popup-reportSentView";
+
+ #_enabled = false;
+ get enabled() {
+ return this.#_enabled;
+ }
+
+ #reasonEnabled = false;
+ #reasonIsOptional = true;
+ #randomizeReasons = false;
+ #descriptionIsOptional = true;
+ #sendMoreInfoEnabled = true;
+
+ get reasonEnabled() {
+ return this.#reasonEnabled;
+ }
+
+ get reasonIsOptional() {
+ return this.#reasonIsOptional;
+ }
+
+ get randomizeReasons() {
+ return this.#randomizeReasons;
+ }
+
+ get descriptionIsOptional() {
+ return this.#descriptionIsOptional;
+ }
+
+ constructor() {
+ for (const [name, [pref, dflt]] of Object.entries({
+ dataReportingPref: [ReportBrokenSite.DATAREPORTING_PREF, false],
+ reasonPref: [ReportBrokenSite.REASON_PREF, 0],
+ reasonRandomizedPref: [ReportBrokenSite.REASON_RANDOMIZED_PREF, false],
+ sendMoreInfoPref: [ReportBrokenSite.SEND_MORE_INFO_PREF, false],
+ newReportEndpointPref: [
+ ReportBrokenSite.NEW_REPORT_ENDPOINT_PREF,
+ DEFAULT_NEW_REPORT_ENDPOINT,
+ ],
+ enabledPref: [ReportBrokenSite.REPORTER_ENABLED_PREF, true],
+ reportSiteIssueEnabledPref: [
+ ReportBrokenSite.REPORT_SITE_ISSUE_PREF,
+ false,
+ ],
+ })) {
+ XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ name,
+ pref,
+ dflt,
+ this.#checkPrefs.bind(this)
+ );
+ }
+ this.#checkPrefs();
+ }
+
+ canReportURI(uri) {
+ return uri && (uri.schemeIs("http") || uri.schemeIs("https"));
+ }
+
+ #recordGleanEvent(name, extra) {
+ Glean.webcompatreporting[name].record(extra);
+ }
+
+ updateParentMenu(event) {
+ // We need to make sure that the Report Broken Site menu item
+ // is disabled and/or hidden depending on the prefs/active tab URL
+ // when our parent popups are shown, and if their tab's location
+ // changes while they are open.
+ const tabbrowser = event.target.ownerGlobal.gBrowser;
+ this.enableOrDisableMenuitems(tabbrowser.selectedBrowser);
+
+ tabbrowser.addTabsProgressListener(this);
+ event.target.addEventListener(
+ "popuphidden",
+ () => {
+ tabbrowser.removeTabsProgressListener(this);
+ },
+ { once: true }
+ );
+ }
+
+ init(tabbrowser) {
+ // Called in browser.js.
+ const { ownerGlobal } = tabbrowser.selectedBrowser;
+ const { document } = ownerGlobal;
+
+ const state = ViewState.get(document);
+
+ this.#initMainView(state);
+ this.#initReportSentView(state);
+
+ for (const id of ["menu_HelpPopup", "appMenu-popup"]) {
+ document
+ .getElementById(id)
+ .addEventListener("popupshown", this.updateParentMenu.bind(this));
+ }
+
+ state.mainPanelview.addEventListener("ViewShowing", ({ target }) => {
+ const { selectedBrowser } = target.ownerGlobal.gBrowser;
+ let source = "helpMenu";
+ switch (target.closest("panelmultiview")?.id) {
+ case "appMenu-multiView":
+ source = "hamburgerMenu";
+ break;
+ case "protections-popup-multiView":
+ source = "ETPShieldIconMenu";
+ break;
+ }
+ this.#onMainViewShown(source, selectedBrowser);
+ });
+
+ // Make sure the URL input is focused when the main view pops up.
+ state.mainPanelview.addEventListener("ViewShown", () => {
+ const panelview = ownerGlobal.PanelView.forNode(state.mainPanelview);
+ panelview.selectedElement = state.urlInput;
+ panelview.focusSelectedElement();
+ });
+
+ // Make sure the Okay button is focused when the report sent view pops up.
+ state.reportSentPanelview.addEventListener("ViewShown", () => {
+ const panelview = ownerGlobal.PanelView.forNode(
+ state.reportSentPanelview
+ );
+ panelview.selectedElement = state.okayButton;
+ panelview.focusSelectedElement();
+ });
+ }
+
+ enableOrDisableMenuitems(selectedbrowser) {
+ // Ensures that the various Report Broken Site menu items and
+ // toolbar buttons are enabled/hidden when appropriate (and
+ // also the Help menu's Report Site Issue item)/
+
+ const canReportUrl = this.canReportURI(selectedbrowser.currentURI);
+
+ const { document } = selectedbrowser.ownerGlobal;
+
+ const cmd = document.getElementById("cmd_reportBrokenSite");
+ if (this.enabled) {
+ cmd.setAttribute("hidden", "false"); // see bug 805653
+ } else {
+ cmd.setAttribute("hidden", "true");
+ }
+ if (canReportUrl) {
+ cmd.removeAttribute("disabled");
+ } else {
+ cmd.setAttribute("disabled", "true");
+ }
+
+ // Changes to the "hidden" and "disabled" state of the command aren't reliably
+ // reflected on the main menu unless we open it twice, or do it manually.
+ // (See bug 1864953).
+ const mainmenuItem = document.getElementById("help_reportBrokenSite");
+ if (mainmenuItem) {
+ mainmenuItem.hidden = !this.enabled;
+ mainmenuItem.disabled = !canReportUrl;
+ }
+
+ // Report Site Issue is our older issue reporter, shown in the Help
+ // menu on pre-release channels. We should hide it unless we're
+ // disabled, at which point we should show it when available.
+ const reportSiteIssue = document.getElementById("help_reportSiteIssue");
+ if (reportSiteIssue) {
+ reportSiteIssue.hidden = this.enabled || !this.reportSiteIssueEnabledPref;
+ reportSiteIssue.disabled = !canReportUrl;
+ }
+
+ // "Site not working?" on the protections panel should be hidden when
+ // Report Broken Site is visible (bug 1868527).
+ const siteNotWorking = document.getElementById(
+ "protections-popup-tp-switch-section-footer"
+ );
+ if (siteNotWorking) {
+ siteNotWorking.hidden = this.enabled;
+ }
+ }
+
+ #checkPrefs(whichChanged) {
+ // No breakage reports can be sent by Glean if it's disabled, so we also
+ // disable the broken site reporter. We also have our own pref.
+ this.#_enabled =
+ Services.policies.isAllowed("feedbackCommands") &&
+ this.dataReportingPref &&
+ this.enabledPref;
+
+ this.#reasonEnabled = this.reasonPref == 1 || this.reasonPref == 2;
+ this.#reasonIsOptional = this.reasonPref == 1;
+ if (!whichChanged || whichChanged == ReportBrokenSite.REASON_PREF) {
+ const setting = ReportBrokenSite.REASON_PREF_VALUES[this.reasonPref];
+ this.#recordGleanEvent("reasonDropdown", { setting });
+ }
+
+ this.#sendMoreInfoEnabled = this.sendMoreInfoPref;
+ this.#newReportEndpoint = this.newReportEndpointPref;
+
+ this.#randomizeReasons = this.reasonRandomizedPref;
+ }
+
+ #initMainView(state) {
+ state.sendButton.addEventListener("command", () => {
+ state.form.requestSubmit();
+ });
+
+ state.form.addEventListener("submit", async event => {
+ event.preventDefault();
+ if (!state.form.checkValidity()) {
+ state.focusFirstInvalidElement();
+ return;
+ }
+ const multiview = event.target.closest("panelmultiview");
+ this.#recordGleanEvent("send");
+ await this.#sendReportAsGleanPing(state);
+ multiview.showSubView("report-broken-site-popup-reportSentView");
+ state.reset();
+ });
+
+ state.cancelButton.addEventListener("command", ({ target }) => {
+ target.ownerGlobal.CustomizableUI.hidePanelForNode(target);
+ state.reset();
+ });
+
+ state.sendMoreInfoLink.addEventListener("click", async event => {
+ event.preventDefault();
+ const tabbrowser = event.target.ownerGlobal.gBrowser;
+ this.#recordGleanEvent("sendMoreInfo");
+ event.target.ownerGlobal.CustomizableUI.hidePanelForNode(event.target);
+ await this.#openWebCompatTab(tabbrowser);
+ state.reset();
+ });
+ }
+
+ #initReportSentView(state) {
+ state.okayButton.addEventListener("command", ({ target }) => {
+ target.ownerGlobal.CustomizableUI.hidePanelForNode(target);
+ });
+ }
+
+ async #onMainViewShown(source, selectedBrowser) {
+ const { document } = selectedBrowser.ownerGlobal;
+
+ let didReset = false;
+ const state = ViewState.get(document);
+ const uri = selectedBrowser.currentURI;
+ if (!state.isURLValid && !state.isDescriptionValid) {
+ state.reset();
+ didReset = true;
+ } else if (!state.currentTabURI || !uri.equals(state.currentTabURI)) {
+ state.reset();
+ didReset = true;
+ } else if (!state.url) {
+ state.resetURLToCurrentTab();
+ }
+
+ const { sendMoreInfoLink } = state;
+ const { sendMoreInfoEndpoint } = this;
+ if (sendMoreInfoLink.href !== sendMoreInfoEndpoint) {
+ sendMoreInfoLink.href = sendMoreInfoEndpoint;
+ }
+ sendMoreInfoLink.hidden = !this.#sendMoreInfoEnabled;
+
+ state.reasonInput.hidden = !this.#reasonEnabled;
+ state.reasonInput.required = this.#reasonEnabled && !this.#reasonIsOptional;
+
+ state.ensureReasonOrderingMatchesPref();
+
+ state.reasonLabelRequired.hidden =
+ !this.#reasonEnabled || this.#reasonIsOptional;
+ state.reasonLabelOptional.hidden =
+ !this.#reasonEnabled || !this.#reasonIsOptional;
+
+ state.descriptionLabelRequired.hidden = this.#descriptionIsOptional;
+ state.descriptionLabelOptional.hidden = !this.#descriptionIsOptional;
+
+ this.#recordGleanEvent("opened", { source });
+
+ if (didReset || !state.currentTabWebcompatDetailsPromise) {
+ state.currentTabWebcompatDetailsPromise = this.#queryActor(
+ "GetWebCompatInfo",
+ undefined,
+ selectedBrowser
+ ).catch(err => {
+ console.error("Report Broken Site: unexpected error", err);
+ });
+ }
+ }
+
+ async #queryActor(msg, params, browser) {
+ const actor =
+ browser.browsingContext.currentWindowGlobal.getActor("ReportBrokenSite");
+ return actor.sendQuery(msg, params);
+ }
+
+ async #loadTab(tabbrowser, url, triggeringPrincipal) {
+ const tab = tabbrowser.addTab(url, {
+ inBackground: false,
+ triggeringPrincipal,
+ });
+ const expectedBrowser = tabbrowser.getBrowserForTab(tab);
+ return new Promise(resolve => {
+ const listener = {
+ onLocationChange(browser, webProgress, request, uri, flags) {
+ if (
+ browser == expectedBrowser &&
+ uri.spec == url &&
+ webProgress.isTopLevel
+ ) {
+ resolve(tab);
+ tabbrowser.removeTabsProgressListener(listener);
+ }
+ },
+ };
+ tabbrowser.addTabsProgressListener(listener);
+ });
+ }
+
+ async #openWebCompatTab(tabbrowser) {
+ const endpointUrl = this.sendMoreInfoEndpoint;
+ const principal = Services.scriptSecurityManager.createNullPrincipal({});
+ const tab = await this.#loadTab(tabbrowser, endpointUrl, principal);
+ const { document } = tabbrowser.selectedBrowser.ownerGlobal;
+ const { description, reason, url, currentTabWebcompatDetailsPromise } =
+ ViewState.get(document);
+
+ return this.#queryActor(
+ "SendDataToWebcompatCom",
+ {
+ reason,
+ description,
+ endpointUrl,
+ reportUrl: url,
+ reporterConfig: ReportBrokenSite.WEBCOMPAT_REPORTER_CONFIG,
+ webcompatInfo: await currentTabWebcompatDetailsPromise,
+ },
+ tab.linkedBrowser
+ ).catch(err => {
+ console.error("Report Broken Site: unexpected error", err);
+ });
+ }
+
+ async #sendReportAsGleanPing({
+ currentTabWebcompatDetailsPromise,
+ description,
+ reason,
+ url,
+ }) {
+ const gBase = Glean.brokenSiteReport;
+ const gTabInfo = Glean.brokenSiteReportTabInfo;
+ const gAntitracking = Glean.brokenSiteReportTabInfoAntitracking;
+ const gFrameworks = Glean.brokenSiteReportTabInfoFrameworks;
+ const gApp = Glean.brokenSiteReportBrowserInfoApp;
+ const gGraphics = Glean.brokenSiteReportBrowserInfoGraphics;
+ const gPrefs = Glean.brokenSiteReportBrowserInfoPrefs;
+ const gSystem = Glean.brokenSiteReportBrowserInfoSystem;
+
+ if (reason) {
+ gBase.breakageCategory.set(reason);
+ }
+
+ gBase.description.set(description);
+ gBase.url.set(url);
+
+ const details = await currentTabWebcompatDetailsPromise;
+
+ if (!details) {
+ GleanPings.brokenSiteReport.submit();
+ return;
+ }
+
+ const {
+ antitracking,
+ browser,
+ devicePixelRatio,
+ frameworks,
+ languages,
+ userAgent,
+ } = details;
+
+ gTabInfo.languages.set(languages);
+ gTabInfo.useragentString.set(userAgent);
+ gGraphics.devicePixelRatio.set(devicePixelRatio);
+
+ for (const [name, value] of Object.entries(antitracking)) {
+ gAntitracking[name].set(value);
+ }
+
+ for (const [name, value] of Object.entries(frameworks)) {
+ gFrameworks[name].set(value);
+ }
+
+ const { app, graphics, locales, platform, prefs, security } = browser;
+
+ gApp.defaultLocales.set(locales);
+ gApp.defaultUseragentString.set(app.defaultUserAgent);
+
+ const { fissionEnabled, isTablet, memoryMB } = platform;
+ gApp.fissionEnabled.set(fissionEnabled);
+ gSystem.isTablet.set(isTablet ?? false);
+ gSystem.memory.set(memoryMB);
+
+ gPrefs.cookieBehavior.set(prefs["network.cookie.cookieBehavior"]);
+ gPrefs.forcedAcceleratedLayers.set(
+ prefs["layers.acceleration.force-enabled"]
+ );
+ gPrefs.globalPrivacyControlEnabled.set(
+ prefs["privacy.globalprivacycontrol.enabled"]
+ );
+ gPrefs.installtriggerEnabled.set(
+ prefs["extensions.InstallTrigger.enabled"]
+ );
+ gPrefs.opaqueResponseBlocking.set(prefs["browser.opaqueResponseBlocking"]);
+ gPrefs.resistFingerprintingEnabled.set(
+ prefs["privacy.resistFingerprinting"]
+ );
+ gPrefs.softwareWebrender.set(prefs["gfx.webrender.software"]);
+
+ if (security) {
+ for (const [name, value] of Object.entries(security)) {
+ if (value?.length) {
+ Glean.brokenSiteReportBrowserInfoSecurity[name].set(value);
+ }
+ }
+ }
+
+ const { devices, drivers, features, hasTouchScreen, monitors } = graphics;
+
+ gGraphics.devicesJson.set(JSON.stringify(devices));
+ gGraphics.driversJson.set(JSON.stringify(drivers));
+ gGraphics.featuresJson.set(JSON.stringify(features));
+ gGraphics.hasTouchScreen.set(hasTouchScreen);
+ gGraphics.monitorsJson.set(JSON.stringify(monitors));
+
+ GleanPings.brokenSiteReport.submit();
+ }
+
+ open(event) {
+ const { target } = event.sourceEvent;
+ const { selectedBrowser } = target.ownerGlobal.gBrowser;
+ const { ownerGlobal } = selectedBrowser;
+ const { document } = ownerGlobal;
+
+ switch (target.id) {
+ case "appMenu-report-broken-site-button":
+ ownerGlobal.PanelUI.showSubView(
+ ReportBrokenSite.MAIN_PANELVIEW_ID,
+ target
+ );
+ break;
+ case "protections-popup-report-broken-site-button":
+ document
+ .getElementById("protections-popup-multiView")
+ .showSubView(ReportBrokenSite.MAIN_PANELVIEW_ID);
+ break;
+ case "help_reportBrokenSite":
+ // hide the hamburger menu first, as we overlap with it.
+ const appMenuPopup = document.getElementById("appMenu-popup");
+ appMenuPopup?.hidePopup();
+
+ ownerGlobal.PanelUI.showSubView(
+ ReportBrokenSite.MAIN_PANELVIEW_ID,
+ ownerGlobal.PanelUI.menuButton
+ );
+ break;
+ }
+ }
+})();