diff --git a/.eslintrc-rollouts.js b/.eslintrc-rollouts.js
index 98231a3dd0c2..6d4c05bac39e 100644
--- a/.eslintrc-rollouts.js
+++ b/.eslintrc-rollouts.js
@@ -442,7 +442,6 @@ const rollouts = [
"browser/components/sidebar/**",
"browser/components/shell/**",
"browser/components/sessionstore/**",
- "browser/components/shopping/**",
"browser/components/storybook/.storybook/**",
"browser/components/storybook/custom-elements-manifest.config.mjs",
"browser/components/syncedtabs/**",
diff --git a/browser/components/moz.build b/browser/components/moz.build
index 890a4b50e287..3dc676af0e58 100644
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -60,7 +60,6 @@ DIRS += [
"search",
"sessionstore",
"shell",
- "shopping",
"sidebar",
"syncedtabs",
"tabbrowser",
diff --git a/browser/components/shopping/ReviewCheckerParent.sys.mjs b/browser/components/shopping/ReviewCheckerParent.sys.mjs
deleted file mode 100644
index d0664f794465..000000000000
--- a/browser/components/shopping/ReviewCheckerParent.sys.mjs
+++ /dev/null
@@ -1,190 +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/. */
-
-const lazy = {};
-ChromeUtils.defineESModuleGetters(lazy, {
- PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
- ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
-});
-
-const ABOUT_SHOPPING_SIDEBAR = "about:shoppingsidebar";
-const ABOUT_BLANK = "about:blank";
-
-/**
- * When a Review Checker sidebar panel is open ReviewCheckerParent
- * handles listening for location changes and determining if the
- * location or the current URI is a product or not.
- *
- * The ReviewCheckerChild will use that info to update the sidebar UI.
- *
- * This is a simplified version of the `ShoppingSidebarParent` for
- * using the shopping components in the main sidebar instead of the
- * custom shopping sidebar.
- */
-export class ReviewCheckerParent extends JSWindowActorParent {
- static SHOPPING_OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
- static CLOSE_SIDEBAR = "CloseReviewCheckerSidebar";
- static REVERSE_SIDEBAR = "ReverseSidebarPositionFromReviewChecker";
- static SHOW_SIDEBAR_SETTINGS = "ShowSidebarSettingsFromReviewChecker";
- static DISPATCH_NEW_POSITION_CARD_IF_ELIGIBLE =
- "DispatchNewPositionCardIfEligible";
-
- actorCreated() {
- this.topBrowserWindow = this.browsingContext.topChromeWindow;
- this.topBrowserWindow.gBrowser.addProgressListener(this);
-
- /* The manager class that tracks auto-open behaviour is likely to be instantiated before ReviewCheckerParent.
- * Once the parent actor is created, dispatch an event to the manager to see if we
- * can render the notification for the RC's new sidebar position. Also be sure to set up an
- * eventListener so that we can hear back from the manager. */
- this.showNewPositionCard = this.showNewPositionCard.bind(this);
- this.topBrowserWindow.addEventListener(
- "ReviewCheckerManager:ShowNewPositionCard",
- this.showNewPositionCard
- );
- this.dispatchNewPositionCardIfEligible();
- }
-
- didDestroy() {
- if (!this.topBrowserWindow) {
- return;
- }
-
- this.topBrowserWindow.removeEventListener(
- "ReviewCheckerManager:ShowNewPositionCard",
- this.showNewPositionCard
- );
- this.topBrowserWindow.gBrowser.removeProgressListener(this);
- this.topBrowserWindow = undefined;
- }
-
- /**
- * Returns true if a URL is ignored by ReviewChecker.
- *
- * @param {string} url the url that we want to validate.
- */
- static isIgnoredURL(url) {
- // about:shoppingsidebar is only used for testing with fake data.
- return url == ABOUT_SHOPPING_SIDEBAR || url == ABOUT_BLANK;
- }
-
- updateCurrentURL(uri, flags, isSimulated) {
- if (ReviewCheckerParent.isIgnoredURL(uri.spec)) {
- uri = null;
- }
- this.sendAsyncMessage("ReviewChecker:UpdateCurrentURL", {
- url: uri?.spec,
- isReload: !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD),
- isSimulated,
- });
- }
-
- getCurrentURL() {
- let { selectedBrowser } = this.topBrowserWindow.gBrowser;
- let uri = selectedBrowser.currentURI;
-
- // Remove the unhandled navigation flag if present, as the
- // product will be handled now.
- lazy.ShoppingUtils.clearIsDistinctProductPageVisitFlag(selectedBrowser);
-
- if (ReviewCheckerParent.isIgnoredURL(uri.spec)) {
- return null;
- }
- return uri?.spec;
- }
-
- showNewPositionCard() {
- this.sendAsyncMessage("ReviewChecker:ShowNewPositionCard");
- }
-
- async receiveMessage(message) {
- if (this.browsingContext.usePrivateBrowsing) {
- throw new Error("We should never be invoked in PBM.");
- }
- switch (message.name) {
- case "GetCurrentURL":
- return this.getCurrentURL();
- case "DisableShopping":
- Services.prefs.setIntPref(
- ReviewCheckerParent.SHOPPING_OPTED_IN_PREF,
- 2
- );
- this.closeSidebarPanel();
- break;
- case "CloseShoppingSidebar":
- this.closeSidebarPanel();
- break;
- case "ReverseSidebarPosition":
- this.reverseSidebarPosition();
- break;
- case "ShowSidebarSettings":
- this.showSidebarSettings();
- break;
- }
- return null;
- }
-
- /**
- * Called by TabsProgressListener whenever any browser navigates from one
- * URL to another.
- * Note that this includes hash changes / pushState navigations, because
- * those can be significant for us.
- */
- onLocationChange(
- aWebProgress,
- _aRequest,
- aLocationURI,
- aFlags,
- aIsSimulated
- ) {
- if (aWebProgress && !aWebProgress.isTopLevel) {
- return;
- }
-
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(
- this.topBrowserWindow
- );
- if (isPBM) {
- return;
- }
-
- // If this tab changed to a new location in the background
- // it will have an `isDistinctProductPageVisit` flag.
- // If that is present we need to handle the navigation as
- // an un-simulated location change.
- let { selectedBrowser } = this.topBrowserWindow.gBrowser;
- if (selectedBrowser.isDistinctProductPageVisit) {
- aIsSimulated = false;
- lazy.ShoppingUtils.clearIsDistinctProductPageVisitFlag(selectedBrowser);
- }
-
- this.updateCurrentURL(aLocationURI, aFlags, aIsSimulated);
- }
-
- dispatchEvent(eventName) {
- let event = new CustomEvent(eventName, {
- bubbles: true,
- composed: true,
- });
- this.topBrowserWindow.dispatchEvent(event);
- }
-
- closeSidebarPanel() {
- this.dispatchEvent(ReviewCheckerParent.CLOSE_SIDEBAR);
- }
-
- dispatchNewPositionCardIfEligible() {
- this.dispatchEvent(
- ReviewCheckerParent.DISPATCH_NEW_POSITION_CARD_IF_ELIGIBLE
- );
- }
-
- reverseSidebarPosition() {
- this.dispatchEvent(ReviewCheckerParent.REVERSE_SIDEBAR);
- }
-
- showSidebarSettings() {
- this.dispatchEvent(ReviewCheckerParent.SHOW_SIDEBAR_SETTINGS);
- }
-}
diff --git a/browser/components/shopping/ShoppingSidebarChild.sys.mjs b/browser/components/shopping/ShoppingSidebarChild.sys.mjs
deleted file mode 100644
index 06a8f7046572..000000000000
--- a/browser/components/shopping/ShoppingSidebarChild.sys.mjs
+++ /dev/null
@@ -1,521 +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/. */
-
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs";
-
-import { ShoppingProduct } from "chrome://global/content/shopping/ShoppingProduct.mjs";
-
-let lazy = {};
-
-let gAllActors = new Set();
-
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "optedIn",
- "browser.shopping.experience2023.optedIn",
- null,
- function optedInStateChanged() {
- for (let actor of gAllActors) {
- actor.optedInStateChanged();
- }
- }
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "adsEnabled",
- "browser.shopping.experience2023.ads.enabled",
- true
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "adsEnabledByUser",
- "browser.shopping.experience2023.ads.userEnabled",
- true,
- function adsEnabledByUserChanged() {
- for (let actor of gAllActors) {
- actor.adsEnabledByUserChanged();
- }
- }
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "autoOpenEnabled",
- "browser.shopping.experience2023.autoOpen.enabled",
- true
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "autoOpenEnabledByUser",
- "browser.shopping.experience2023.autoOpen.userEnabled",
- true,
- function autoOpenEnabledByUserChanged() {
- for (let actor of gAllActors) {
- actor.autoOpenEnabledByUserChanged();
- }
- }
-);
-
-export class ShoppingSidebarChild extends RemotePageChild {
- constructor() {
- super();
- }
-
- actorCreated() {
- super.actorCreated();
- gAllActors.add(this);
- }
-
- didDestroy() {
- this._destroyed = true;
- super.didDestroy?.();
- gAllActors.delete(this);
- this.#product?.off("analysis-progress", this.#onAnalysisProgress);
- this.#product?.uninit();
- }
-
- #productURI = null;
- #product = null;
-
- receiveMessage(message) {
- if (this.browsingContext.usePrivateBrowsing) {
- throw new Error("We should never be invoked in PBM.");
- }
- switch (message.name) {
- case "ShoppingSidebar:UpdateProductURL":
- this.handleURLUpdate(message.data);
- break;
- case "ShoppingSidebar:ShowKeepClosedMessage":
- this.sendToContent("ShowKeepClosedMessage");
- break;
- case "ShoppingSidebar:HideKeepClosedMessage":
- this.sendToContent("HideKeepClosedMessage");
- break;
- case "ShoppingSidebar:IsKeepClosedMessageShowing":
- return !!this.document.querySelector("shopping-container")
- ?.wrappedJSObject.showingKeepClosedMessage;
- }
- return null;
- }
-
- handleURLUpdate(data) {
- let { url, isReload } = data;
- let uri = url ? Services.io.newURI(url) : null;
-
- // If we're going from null to null, bail out:
- if (!this.#productURI && !uri) {
- return;
- }
-
- // If we haven't reloaded, check if the URIs represent the same product
- // as sites might change the URI after they have loaded (Bug 1852099).
- if (!isReload && this.isSameProduct(uri, this.#productURI)) {
- return;
- }
-
- this.#productURI = uri;
- this.updateContent({ haveUpdatedURI: true });
- }
-
- isSameProduct(newURI, currentURI) {
- if (!newURI || !currentURI) {
- return false;
- }
-
- // Check if the URIs are equal:
- if (currentURI.equalsExceptRef(newURI)) {
- return true;
- }
-
- if (!this.#product) {
- return false;
- }
-
- // If the current ShoppingProduct has product info set,
- // check if the product ids are the same:
- let currentProduct = this.#product.product;
- if (currentProduct) {
- let newProduct = ShoppingProduct.fromURL(URL.fromURI(newURI));
- if (newProduct.id === currentProduct.id) {
- return true;
- }
- }
-
- return false;
- }
-
- handleEvent(event) {
- let aid, sponsored;
- switch (event.type) {
- case "ContentReady":
- this.updateContent();
- break;
- case "PolledRequestMade":
- this.updateContent({ isPolledRequest: true });
- break;
- case "ReportProductAvailable":
- this.reportProductAvailable();
- break;
- case "AdClicked":
- aid = event.detail.aid;
- sponsored = event.detail.sponsored;
- ShoppingProduct.sendAttributionEvent("click", aid);
- Glean.shopping.surfaceAdsClicked.record({ sponsored });
- break;
- case "AdImpression":
- aid = event.detail.aid;
- sponsored = event.detail.sponsored;
- ShoppingProduct.sendAttributionEvent("impression", aid);
- Glean.shopping.surfaceAdsImpression.record({ sponsored });
- break;
- case "DisableShopping":
- this.sendAsyncMessage("DisableShopping");
- break;
- case "CloseShoppingSidebar":
- this.sendAsyncMessage("CloseShoppingSidebar");
- break;
- }
- }
-
- // Exposed for testing. Assumes uri is a nsURI.
- set productURI(uri) {
- if (!(uri instanceof Ci.nsIURI)) {
- throw new Error("productURI setter expects an nsIURI");
- }
- this.#productURI = uri;
- }
-
- // Exposed for testing. Assumes product is a ShoppingProduct.
- set product(product) {
- if (!(product instanceof ShoppingProduct)) {
- throw new Error("product setter expects an instance of ShoppingProduct");
- }
- this.#product = product;
- }
-
- get canFetchAndShowData() {
- return lazy.optedIn === 1;
- }
-
- get adsEnabled() {
- return lazy.adsEnabled;
- }
-
- get adsEnabledByUser() {
- return lazy.adsEnabledByUser;
- }
-
- get canFetchAndShowAd() {
- return this.adsEnabled && this.adsEnabledByUser;
- }
-
- get autoOpenEnabled() {
- return lazy.autoOpenEnabled;
- }
-
- get autoOpenEnabledByUser() {
- return lazy.autoOpenEnabledByUser;
- }
-
- optedInStateChanged() {
- // Force re-fetching things if needed by clearing the last product URI:
- this.#productURI = null;
- // Then let content know.
- this.updateContent({ focusCloseButton: true });
- }
-
- adsEnabledByUserChanged() {
- this.sendToContent("adsEnabledByUserChanged", {
- adsEnabledByUser: this.adsEnabledByUser,
- });
-
- this.requestRecommendations(this.#productURI);
- }
-
- autoOpenEnabledByUserChanged() {
- this.sendToContent("autoOpenEnabledByUserChanged", {
- autoOpenEnabledByUser: this.autoOpenEnabledByUser,
- });
- }
-
- getProductURI() {
- return this.#productURI;
- }
-
- /**
- * This callback is invoked whenever something changes that requires
- * re-rendering content. The expected cases for this are:
- * - page navigations (both to new products and away from a product once
- * the sidebar has been created)
- * - opt in state changes.
- *
- * @param {object?} options
- * Optional parameter object.
- * @param {bool} options.haveUpdatedURI = false
- * Whether we've got an up-to-date URI already. If true, we avoid
- * fetching the URI from the parent, and assume `this.#productURI`
- * is current. Defaults to false.
- * @param {bool} options.isPolledRequest = false
- * @param {bool} options.focusCloseButton = false
- */
- async updateContent({
- haveUpdatedURI = false,
- isPolledRequest = false,
- focusCloseButton = false,
- } = {}) {
- // updateContent is an async function, and when we're off making requests or doing
- // other things asynchronously, the actor can be destroyed, the user
- // might navigate to a new page, the user might disable the feature ... -
- // all kinds of things can change. So we need to repeatedly check
- // whether we can keep going with our async processes. This helper takes
- // care of these checks.
- let canContinue = (currentURI, checkURI = true) => {
- if (this._destroyed || !this.canFetchAndShowData) {
- return false;
- }
- if (!checkURI) {
- return true;
- }
- return currentURI && currentURI == this.#productURI;
- };
- this.#product?.off("analysis-progress", this.#onAnalysisProgress);
- this.#product?.uninit();
- // We are called either because the URL has changed or because the opt-in
- // state has changed. In both cases, we want to clear out content
- // immediately, without waiting for potentially async operations like
- // obtaining product information.
- // Do not clear data however if an analysis was requested via a call-to-action.
- if (!isPolledRequest) {
- this.sendToContent("Update", {
- adsEnabled: this.adsEnabled,
- adsEnabledByUser: this.adsEnabledByUser,
- autoOpenEnabled: this.autoOpenEnabled,
- autoOpenEnabledByUser: this.autoOpenEnabledByUser,
- showOnboarding: !this.canFetchAndShowData,
- data: null,
- recommendationData: null,
- focusCloseButton,
- });
- }
-
- if (this.canFetchAndShowData) {
- if (!this.#productURI) {
- // If we already have a URI and it's just null, bail immediately.
- if (haveUpdatedURI) {
- return;
- }
- let url = await this.sendQuery("GetProductURL");
-
- // Bail out if we opted out in the meantime, or don't have a URI.
- if (!canContinue(null, false)) {
- return;
- }
-
- this.#productURI = url ? Services.io.newURI(url) : null;
- }
-
- let uri = this.#productURI;
- this.#product = new ShoppingProduct(uri);
- this.#product.on(
- "analysis-progress",
- this.#onAnalysisProgress.bind(this)
- );
-
- let data;
- let isAnalysisInProgress;
-
- try {
- let analysisStatusResponse;
- if (isPolledRequest) {
- // Request a new analysis.
- analysisStatusResponse = await this.#product.requestCreateAnalysis();
- } else {
- // Check if there is an analysis in progress.
- analysisStatusResponse =
- await this.#product.requestAnalysisCreationStatus();
- }
- let analysisStatus = analysisStatusResponse?.status;
-
- isAnalysisInProgress =
- analysisStatus &&
- (analysisStatus == "pending" || analysisStatus == "in_progress");
- if (isAnalysisInProgress) {
- // Only clear the existing data if the update wasn't
- // triggered by a Polled Request event as re-analysis should
- // keep any stale data visible while processing.
- if (!isPolledRequest) {
- this.sendToContent("Update", {
- isAnalysisInProgress,
- });
- }
- analysisStatusResponse = await this.#product.pollForAnalysisCompleted(
- {
- pollInitialWait: analysisStatus == "in_progress" ? 0 : undefined,
- }
- );
- analysisStatus = analysisStatusResponse?.status;
- isAnalysisInProgress = false;
- }
-
- // Use the analysis status instead of re-requesting unnecessarily,
- // or throw if the status from the last analysis was an error.
- switch (analysisStatus) {
- case "not_analyzable":
- case "page_not_supported":
- data = { page_not_supported: true };
- break;
- case "not_enough_reviews":
- data = { not_enough_reviews: true };
- break;
- case "unprocessable":
- case "stale":
- throw new Error(analysisStatus, { cause: analysisStatus });
- default:
- // Status is "completed" or "not_found" (no analysis status),
- // so we should request the analysis data.
- }
-
- if (!data) {
- data = await this.#product.requestAnalysis();
- if (!data) {
- throw new Error("request failed");
- }
- }
- } catch (err) {
- console.error("Failed to fetch product analysis data", err);
- data = { error: err };
- }
- // Check if we got nuked from orbit, or the product URI or opt in changed while we waited.
- if (!canContinue(uri)) {
- return;
- }
-
- this.sendToContent("Update", {
- showOnboarding: false,
- data,
- productUrl: this.#productURI.spec,
- isAnalysisInProgress,
- });
-
- if (!data || data.error) {
- return;
- }
-
- if (!isPolledRequest && !data.grade) {
- Glean.shopping.surfaceNoReviewReliabilityAvailable.record();
- }
-
- this.requestRecommendations(uri);
- } else {
- // Don't bother continuing if the user has opted out.
- if (lazy.optedIn == 2) {
- return;
- }
- let url = await this.sendQuery("GetProductURL");
- if (!url) {
- return;
- }
-
- // Similar to canContinue() above, check to see if things
- // have changed while we were waiting. Bail out if the user
- // opted in, or if the actor doesn't exist.
- if (this._destroyed || this.canFetchAndShowData) {
- return;
- }
-
- this.#productURI = url ? Services.io.newURI(url) : null;
-
- // Send the productURI to content for Onboarding's dynamic text
- this.sendToContent("Update", {
- showOnboarding: true,
- data: null,
- productUrl: this.#productURI?.spec,
- });
- }
- }
-
- /**
- * Utility function to determine if we should request ads.
- */
- canFetchAds(uri) {
- return (
- uri.equalsExceptRef(this.#productURI) &&
- this.canFetchAndShowData &&
- this.canFetchAndShowAd
- );
- }
-
- /**
- * Utility function to determine if we should display ads. This is different
- * from fetching ads, because of ads exposure telemetry (bug 1858470).
- */
- canShowAds(uri) {
- return (
- uri.equalsExceptRef(this.#productURI) &&
- this.canFetchAndShowData &&
- this.canFetchAndShowAd
- );
- }
-
- /**
- * Request recommended products for a given uri and send the recommendations
- * to the content if recommendations are enabled.
- *
- * @param {nsIURI} uri The uri of the current product page
- */
- async requestRecommendations(uri) {
- if (!this.canFetchAds(uri)) {
- return;
- }
-
- let recommendationData = await this.#product.requestRecommendations();
-
- // Check if the product URI or opt in changed while we waited.
- if (!this.canShowAds(uri)) {
- return;
- }
-
- if (!recommendationData.length) {
- // We tried to fetch an ad, but didn't get one.
- Glean.shopping.surfaceNoAdsAvailable.record();
- } else {
- let sponsored = recommendationData[0].sponsored;
-
- ShoppingProduct.sendAttributionEvent(
- "placement",
- recommendationData[0].aid
- );
-
- Glean.shopping.surfaceAdsPlacement.record({
- sponsored,
- });
- }
-
- this.sendToContent("UpdateRecommendations", {
- recommendationData,
- });
- }
-
- sendToContent(eventName, detail) {
- if (this._destroyed) {
- return;
- }
- let win = this.contentWindow;
- let evt = new win.CustomEvent(eventName, {
- bubbles: true,
- detail: Cu.cloneInto(detail, win),
- });
- win.document.dispatchEvent(evt);
- }
-
- async reportProductAvailable() {
- await this.#product.sendReport();
- }
-
- #onAnalysisProgress(eventName, progress) {
- this.sendToContent("UpdateAnalysisProgress", {
- progress,
- });
- }
-}
diff --git a/browser/components/shopping/ShoppingSidebarParent.sys.mjs b/browser/components/shopping/ShoppingSidebarParent.sys.mjs
deleted file mode 100644
index d8c0cc318be0..000000000000
--- a/browser/components/shopping/ShoppingSidebarParent.sys.mjs
+++ /dev/null
@@ -1,481 +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/. */
-
-const lazy = {};
-ChromeUtils.defineESModuleGetters(lazy, {
- BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
- EveryWindow: "resource:///modules/EveryWindow.sys.mjs",
- isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
- PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
- ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
-});
-
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "AUTO_OPEN_SIDEBAR_ENABLED",
- "browser.shopping.experience2023.autoOpen.enabled",
- true
-);
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "AUTO_OPEN_SIDEBAR_USER_ENABLED",
- "browser.shopping.experience2023.autoOpen.userEnabled",
- true
-);
-
-export class ShoppingSidebarParent extends JSWindowActorParent {
- static SHOPPING_ENABLED_PREF = "browser.shopping.experience2023.enabled";
- static SHOPPING_ACTIVE_PREF = "browser.shopping.experience2023.active";
- static SHOPPING_OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
- static SIDEBAR_CLOSED_COUNT_PREF =
- "browser.shopping.experience2023.sidebarClosedCount";
- static SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
- "browser.shopping.experience2023.showKeepSidebarClosedMessage";
-
- updateCurrentURL(uri, flags) {
- this.sendAsyncMessage("ShoppingSidebar:UpdateProductURL", {
- url: uri?.spec ?? null,
- isReload: !!(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD),
- });
- }
-
- async receiveMessage(message) {
- if (this.browsingContext.usePrivateBrowsing) {
- throw new Error("We should never be invoked in PBM.");
- }
- switch (message.name) {
- case "GetProductURL":
- return this.getCurrentURL();
- case "DisableShopping":
- Services.prefs.setBoolPref(
- ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
- false
- );
- Services.prefs.setIntPref(
- ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF,
- 2
- );
- break;
- }
- return null;
- }
-
- /**
- * Gets the URL of the current tab.
- */
- getCurrentURL() {
- let sidebarBrowser = this.browsingContext.top.embedderElement;
- let panel = sidebarBrowser.closest(".browserSidebarContainer");
- let associatedTabbedBrowser = panel.querySelector(
- "browser[messagemanagergroup=browsers]"
- );
- return associatedTabbedBrowser.currentURI?.spec ?? null;
- }
-
- /**
- * Called when the user clicks the URL bar button.
- */
- static async urlbarButtonClick(event) {
- if (
- lazy.AUTO_OPEN_SIDEBAR_ENABLED &&
- lazy.AUTO_OPEN_SIDEBAR_USER_ENABLED &&
- event.target.getAttribute("shoppingsidebaropen") === "true"
- ) {
- let gBrowser = event.target.ownerGlobal.gBrowser;
- let shoppingBrowser = gBrowser
- .getPanel(gBrowser.selectedBrowser)
- .querySelector(".shopping-sidebar");
- let actor =
- shoppingBrowser.browsingContext.currentWindowGlobal.getActor(
- "ShoppingSidebar"
- );
-
- let isKeepClosedMessageShowing = await actor.sendQuery(
- "ShoppingSidebar:IsKeepClosedMessageShowing"
- );
-
- let sidebarClosedCount = Services.prefs.getIntPref(
- ShoppingSidebarParent.SIDEBAR_CLOSED_COUNT_PREF,
- 0
- );
- if (
- !isKeepClosedMessageShowing &&
- sidebarClosedCount >= 4 &&
- Services.prefs.getBoolPref(
- ShoppingSidebarParent.SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF,
- true
- )
- ) {
- actor.sendAsyncMessage("ShoppingSidebar:ShowKeepClosedMessage");
- return;
- }
-
- actor.sendAsyncMessage("ShoppingSidebar:HideKeepClosedMessage");
-
- if (sidebarClosedCount >= 6) {
- Services.prefs.setBoolPref(
- ShoppingSidebarParent.SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF,
- false
- );
- } else {
- Services.prefs.setIntPref(
- ShoppingSidebarParent.SIDEBAR_CLOSED_COUNT_PREF,
- sidebarClosedCount + 1
- );
- }
- }
-
- this.toggleAllSidebars("urlBar");
- }
-
- /**
- * Toggles opening or closing all Shopping sidebars.
- * Sets the active pref value for all windows to respond to.
- * params:
- *
- * @param {string?} source
- * Optional value, describes where the call came from.
- */
- static toggleAllSidebars(source) {
- let activeState = Services.prefs.getBoolPref(
- ShoppingSidebarParent.SHOPPING_ACTIVE_PREF
- );
- Services.prefs.setBoolPref(
- ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
- !activeState
- );
-
- let optedIn = Services.prefs.getIntPref(
- ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF
- );
- // If the user was opted out, then clicked the button, reset the optedIn
- // pref so they see onboarding.
- if (optedIn == 2) {
- Services.prefs.setIntPref(
- ShoppingSidebarParent.SHOPPING_OPTED_IN_PREF,
- 0
- );
- }
- if (source == "urlBar") {
- if (activeState) {
- Glean.shopping.surfaceClosed.record({ source: "addressBarIcon" });
- Glean.shopping.addressBarIconClicked.record({ action: "closed" });
- } else {
- Glean.shopping.addressBarIconClicked.record({ action: "opened" });
- }
- }
- }
-}
-
-class ShoppingSidebarManagerClass {
- #initialized = false;
- #everyWindowCallbackId = `shopping-${Services.uuid.generateUUID()}`;
-
- // Public API methods - these check that we are not in private browsing
- // mode. (It might be nice to eventually shift pref checks to the public
- // API, too.)
- //
- // Note that any refactoring should preserve the PBM checks in public APIs.
-
- ensureInitialized() {
- if (this.#initialized) {
- return;
- }
-
- this.updateSidebarVisibility = this.updateSidebarVisibility.bind(this);
- XPCOMUtils.defineLazyPreferenceGetter(
- this,
- "isEnabled",
- ShoppingSidebarParent.SHOPPING_ENABLED_PREF,
- false,
- this.updateSidebarVisibility
- );
- XPCOMUtils.defineLazyPreferenceGetter(
- this,
- "optedInPref",
- "browser.shopping.experience2023.optedIn",
- null,
- this.updateSidebarVisibility
- );
- XPCOMUtils.defineLazyPreferenceGetter(
- this,
- "isActive",
- ShoppingSidebarParent.SHOPPING_ACTIVE_PREF,
- true,
- this.updateSidebarVisibility
- );
- this.updateSidebarVisibility();
-
- lazy.EveryWindow.registerCallback(
- this.#everyWindowCallbackId,
- window => {
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
- if (isPBM) {
- return;
- }
-
- window.gBrowser.tabContainer.addEventListener("TabSelect", this);
- window.addEventListener("visibilitychange", this);
- },
- window => {
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
- if (isPBM) {
- return;
- }
-
- window.gBrowser.tabContainer.removeEventListener("TabSelect", this);
- window.removeEventListener("visibilitychange", this);
- }
- );
-
- this.#initialized = true;
- }
-
- updateSidebarVisibility() {
- this.enabled = this.isEnabled;
- for (let window of lazy.BrowserWindowTracker.orderedWindows) {
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
- if (isPBM) {
- continue;
- }
- this.updateSidebarVisibilityForWindow(window);
- }
- }
-
- updateSidebarVisibilityForWindow(window) {
- if (window.closed) {
- return;
- }
-
- if (!window.gBrowser) {
- return;
- }
-
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(window);
- if (isPBM) {
- return;
- }
-
- let document = window.document;
-
- if (!this.isActive) {
- document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
- sidebar.hidden = true;
- });
- document
- .querySelectorAll(".shopping-sidebar-splitter")
- .forEach(splitter => {
- splitter.hidden = true;
- });
- }
-
- this._maybeToggleButton(window.gBrowser);
-
- if (!this.enabled) {
- document.querySelectorAll("shopping-sidebar").forEach(sidebar => {
- sidebar.remove();
- });
- document
- .querySelectorAll(".shopping-sidebar-splitter")
- .forEach(splitter => {
- splitter.remove();
- });
- let button = document.getElementById("shopping-sidebar-button");
- if (button) {
- button.hidden = true;
- // Reset attributes to defaults.
- button.setAttribute("shoppingsidebaropen", false);
- document.l10n.setAttributes(button, "shopping-sidebar-open-button2");
- }
- return;
- }
-
- let { selectedBrowser, currentURI } = window.gBrowser;
- this._maybeToggleSidebar(selectedBrowser, currentURI, 0, false);
- }
-
- /**
- * Called by TabsProgressListener whenever any browser navigates from one
- * URL to another.
- * Note that this includes hash changes / pushState navigations, because
- * those can be significant for us.
- */
- onLocationChange(aBrowser, aLocationURI, aFlags) {
- let isPBM = lazy.PrivateBrowsingUtils.isWindowPrivate(aBrowser.ownerGlobal);
- if (isPBM || !this.enabled) {
- return;
- }
-
- lazy.ShoppingUtils.onLocationChange(aLocationURI, aFlags);
-
- this._maybeToggleButton(aBrowser.getTabBrowser());
- this._maybeToggleSidebar(aBrowser, aLocationURI, aFlags, true);
- }
-
- handleEvent(event) {
- switch (event.type) {
- case "TabSelect": {
- if (!this.enabled) {
- return;
- }
- this.updateSidebarVisibility();
- if (event.detail?.previousTab.linkedBrowser) {
- this._updateBCActiveness(event.detail.previousTab.linkedBrowser);
- }
- break;
- }
- case "visibilitychange": {
- if (!this.enabled) {
- return;
- }
- let { gBrowser } = event.target.ownerGlobal.top;
- if (!gBrowser) {
- return;
- }
- this.updateSidebarVisibilityForWindow(event.target.ownerGlobal.top);
- this._updateBCActiveness(gBrowser.selectedBrowser);
- }
- }
- }
-
- // Private API methods - these assume we are not in private browsing
- // mode. (It might be nice to eventually shift pref checks to the public
- // API, too.)
-
- _maybeToggleSidebar(aBrowser, aLocationURI, aFlags, aIsNavigation) {
- let gBrowser = aBrowser.getTabBrowser();
- let document = aBrowser.ownerDocument;
- if (!this.enabled) {
- return;
- }
-
- let browserPanel = gBrowser.getPanel(aBrowser);
- let sidebar = browserPanel.querySelector("shopping-sidebar");
- let actor;
- if (sidebar) {
- let { browsingContext } = sidebar.querySelector("browser");
- let global = browsingContext.currentWindowGlobal;
- actor = global.getExistingActor("ShoppingSidebar");
- }
- let isProduct = lazy.isProductURL(aLocationURI);
- if (isProduct && this.isActive) {
- if (!sidebar) {
- sidebar = document.createXULElement("shopping-sidebar");
- sidebar.hidden = false;
- let splitter = document.createXULElement("splitter");
- splitter.classList.add("sidebar-splitter", "shopping-sidebar-splitter");
- browserPanel.appendChild(splitter);
- browserPanel.appendChild(sidebar);
- } else {
- actor?.updateCurrentURL(aLocationURI, aFlags);
- sidebar.hidden = false;
- let splitter = browserPanel.querySelector(".shopping-sidebar-splitter");
- splitter.hidden = false;
- }
- } else if (sidebar && !sidebar.hidden) {
- actor?.updateCurrentURL(null);
- sidebar.hidden = true;
- let splitter = browserPanel.querySelector(".shopping-sidebar-splitter");
- splitter.hidden = true;
- }
-
- this._updateBCActiveness(aBrowser);
- this._setShoppingButtonState(aBrowser);
-
- // Note: (bug 1868602) only record surface displayed telemetry if:
- // - the foregrounded tab navigates to a product page with sidebar visible,
- // - a product page tab loaded in the background is foregrounded, or
- // - a foregrounded product page tab was loaded with the sidebar hidden and
- // now the sidebar has been shown.
- if (
- this.enabled &&
- lazy.ShoppingUtils.isProductPageNavigation(aLocationURI, aFlags)
- ) {
- if (
- this.isActive &&
- aBrowser === gBrowser.selectedBrowser &&
- (aIsNavigation || aBrowser.isDistinctProductPageVisit)
- ) {
- Glean.shopping.surfaceDisplayed.record();
- delete aBrowser.isDistinctProductPageVisit;
- } else if (aIsNavigation) {
- aBrowser.isDistinctProductPageVisit = true;
- }
- }
-
- if (isProduct) {
- // This is the auto-enable behavior that toggles the `active` pref. It
- // must be at the end of this function, or 2 sidebars could be created.
- lazy.ShoppingUtils.handleAutoActivateOnProduct();
-
- if (!this.isActive) {
- lazy.ShoppingUtils.sendTrigger({
- browser: aBrowser,
- id: "shoppingProductPageWithSidebarClosed",
- context: { isSidebarClosing: !aIsNavigation && !!sidebar },
- });
- }
- }
- }
-
- _maybeToggleButton(gBrowser) {
- let optedOut = this.optedInPref === 2;
- if (this.enabled && optedOut) {
- this._setShoppingButtonState(gBrowser.selectedBrowser);
- }
- }
-
- _updateBCActiveness(aBrowser) {
- let gBrowser = aBrowser.getTabBrowser();
- let document = aBrowser.ownerDocument;
- let browserPanel = gBrowser.getPanel(aBrowser);
- let sidebar = browserPanel.querySelector("shopping-sidebar");
- if (!sidebar) {
- return;
- }
- try {
- // Tell Gecko when the sidebar visibility changes to avoid background
- // sidebars taking more CPU / energy than needed.
- sidebar.querySelector("browser").docShellIsActive =
- !document.hidden &&
- aBrowser == gBrowser.selectedBrowser &&
- !sidebar.hidden;
- } catch (ex) {
- // The setter can throw and we do need to run the rest of this
- // code in that case.
- console.error(ex);
- }
- }
-
- _setShoppingButtonState(aBrowser) {
- let gBrowser = aBrowser.getTabBrowser();
- let document = aBrowser.ownerDocument;
- if (aBrowser !== gBrowser.selectedBrowser) {
- return;
- }
-
- let button = document.getElementById("shopping-sidebar-button");
-
- let isCurrentBrowserProduct = lazy.isProductURL(
- gBrowser.selectedBrowser.currentURI
- );
-
- // Only record if the state of the icon will change from hidden to visible.
- if (button.hidden && isCurrentBrowserProduct) {
- Glean.shopping.addressBarIconDisplayed.record();
- }
-
- button.hidden = !isCurrentBrowserProduct;
- button.setAttribute("shoppingsidebaropen", !!this.isActive);
- let l10nId = this.isActive
- ? "shopping-sidebar-close-button2"
- : "shopping-sidebar-open-button2";
- document.l10n.setAttributes(button, l10nId);
- }
-}
-
-const ShoppingSidebarManager = new ShoppingSidebarManagerClass();
-export { ShoppingSidebarManager };
diff --git a/browser/components/shopping/ShoppingUtils.sys.mjs b/browser/components/shopping/ShoppingUtils.sys.mjs
deleted file mode 100644
index 62b698c51b19..000000000000
--- a/browser/components/shopping/ShoppingUtils.sys.mjs
+++ /dev/null
@@ -1,422 +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/. */
-
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-
-const lazy = {};
-
-ChromeUtils.defineESModuleGetters(lazy, {
- ASRouter: "resource:///modules/asrouter/ASRouter.sys.mjs",
- isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
- getProductIdFromURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
- setTimeout: "resource://gre/modules/Timer.sys.mjs",
-});
-
-const OPTED_IN_PREF = "browser.shopping.experience2023.optedIn";
-const ACTIVE_PREF = "browser.shopping.experience2023.active";
-const LAST_AUTO_ACTIVATE_PREF =
- "browser.shopping.experience2023.lastAutoActivate";
-const AUTO_ACTIVATE_COUNT_PREF =
- "browser.shopping.experience2023.autoActivateCount";
-const ADS_USER_ENABLED_PREF = "browser.shopping.experience2023.ads.userEnabled";
-const AUTO_OPEN_ENABLED_PREF =
- "browser.shopping.experience2023.autoOpen.enabled";
-const AUTO_OPEN_USER_ENABLED_PREF =
- "browser.shopping.experience2023.autoOpen.userEnabled";
-const SIDEBAR_CLOSED_COUNT_PREF =
- "browser.shopping.experience2023.sidebarClosedCount";
-
-const CFR_FEATURES_PREF =
- "browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features";
-
-const ENABLED_PREF = "browser.shopping.experience2023.enabled";
-
-export const ShoppingUtils = {
- initialized: false,
- registered: false,
- handledAutoActivate: false,
- enabled: false,
- everyWindowCallbackId: `shoppingutils-${Services.uuid.generateUUID()}`,
-
- _updatePrefVariables() {
- this.enabled = Services.prefs.getBoolPref(ENABLED_PREF, false);
- },
-
- onPrefUpdate(_subject, topic) {
- if (topic !== "nsPref:changed") {
- return;
- }
- if (this.initialized) {
- ShoppingUtils.uninit(true);
- Glean.shoppingSettings.nimbusDisabledShopping.set(true);
- }
- this._updatePrefVariables();
-
- if (this.enabled) {
- ShoppingUtils.init();
- Glean.shoppingSettings.nimbusDisabledShopping.set(false);
- }
- },
-
- // Runs once per session:
- // * at application startup, with startup idle tasks,
- // * or after the user is enrolled in the Nimbus experiment.
- init() {
- if (this.initialized) {
- return;
- }
- this.onPrefUpdate = this.onPrefUpdate.bind(this);
- this.onActiveUpdate = this.onActiveUpdate.bind(this);
-
- if (!this.registered) {
- // Note (bug 1855545): we must set `this.registered` before calling
- // `onUpdate`, as it will immediately invoke `this.onPrefUpdate`,
- // which in turn calls `ShoppingUtils.init`, creating an infinite loop.
- this.registered = true;
- Services.prefs.addObserver(ENABLED_PREF, this.onPrefUpdate);
- this._updatePrefVariables();
- }
-
- if (!this.enabled) {
- return;
- }
-
- // Do startup-time stuff here, like recording startup-time glean events
- // or adjusting onboarding-related prefs once per session.
-
- this.setOnUpdate(undefined, undefined, this.optedIn);
- this.recordUserAdsPreference();
- this.recordUserAutoOpenPreference();
-
- if (this.isAutoOpenEligible()) {
- Services.prefs.setBoolPref(ACTIVE_PREF, true);
- }
- Services.prefs.addObserver(ACTIVE_PREF, this.onActiveUpdate);
-
- Services.prefs.setIntPref(SIDEBAR_CLOSED_COUNT_PREF, 0);
-
- this.initialized = true;
- },
-
- /**
- * Runs when:
- * - the shopping2023 enabled pref is changed,
- * - the user is unenrolled from the Nimbus experiment,
- * - or at shutdown, after quit-application-granted.
- *
- * @param {boolean} soft
- * If this is a soft uninit, for a pref change, we want to keep the
- * pref listeners around incase they are changed again.
- */
- uninit(soft) {
- if (!this.initialized) {
- return;
- }
-
- // Do shutdown-time stuff here, like firing glean pings or modifying any
- // prefs for onboarding.
-
- Services.prefs.removeObserver(ACTIVE_PREF, this.onActiveUpdate);
-
- if (!soft) {
- this.registered = false;
- Services.prefs.removeObserver(ENABLED_PREF, this.onPrefUpdate);
- }
-
- this.initialized = false;
- },
-
- isProductPageNavigation(aLocationURI, aFlags) {
- if (!lazy.isProductURL(aLocationURI)) {
- return false;
- }
-
- // Ignore same-document navigation, except in the case of Walmart
- // as they use pushState to navigate between pages.
- let isWalmart = aLocationURI.host.includes("walmart");
- let isNewDocument = !aFlags;
-
- let isSameDocument =
- aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
- let isReload = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD;
- let isSessionRestore =
- aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE;
-
- // Unfortunately, Walmart sometimes double-fires history manipulation
- // events when navigating between product pages. To dedupe, cache the
- // last visited Walmart URL just for a few milliseconds, so we can avoid
- // double-counting such navigations.
- if (isWalmart) {
- if (
- this.lastWalmartURI &&
- aLocationURI.equalsExceptRef(this.lastWalmartURI)
- ) {
- return false;
- }
- this.lastWalmartURI = aLocationURI;
- lazy.setTimeout(() => {
- this.lastWalmartURI = null;
- }, 100);
- }
-
- return (
- // On initial visit to a product page, even from another domain, both a page
- // load and a pushState will be triggered by Walmart, so this will
- // capture only a single displayed event.
- (!isWalmart && !!(isNewDocument || isReload || isSessionRestore)) ||
- (isWalmart && !!isSameDocument)
- );
- },
-
- /**
- * Similar to isProductPageNavigation but compares the
- * current location URI to a previous location URI and
- * checks if the URI and product has changed.
- *
- * This lets us avoid issues with over-counting products
- * that have multiple loads or history changes.
- *
- * @param {nsIURI} aLocationURI
- * The current location.
- * @param {integer} aFlags
- * The load flags or null.
- * @param {nsIURI} aPreviousURI
- * A previous product URI or null.
- * @returns {boolean} isNewProduct
- */
- hasLocationChanged(aLocationURI, aFlags, aPreviousURI) {
- let isReload = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD;
- let isSessionRestore =
- aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE;
-
- // If we have reloaded, restored or there isn't a previous URI
- // this is a location change.
- if (isReload || isSessionRestore || !aPreviousURI) {
- return true;
- }
-
- let isCurrentLocationProduct = lazy.isProductURL(aLocationURI);
- let isPrevLocationProduct = lazy.isProductURL(aPreviousURI);
-
- // If the locations are not products, we can just compare URIs.
- if (!isCurrentLocationProduct && !isPrevLocationProduct) {
- return aLocationURI.equalsExceptRef(aPreviousURI);
- }
-
- // If one of the URIs is not a product url, but the other is
- // this is a location change.
- if (!isCurrentLocationProduct || !isPrevLocationProduct) {
- return true;
- }
-
- // If URIs are both products we will need to check,
- // if the product have changed by comparing them.
- let isSameProduct = this.isSameProduct(aLocationURI, aPreviousURI);
- return !isSameProduct;
- },
-
- // For enabled users, increment a
- // counter when they visit supported product pages.
- recordExposure() {
- if (this.enabled) {
- Glean.shopping.productPageVisits.add(1);
- }
- },
-
- setOnUpdate(_pref, _prev, current) {
- Glean.shoppingSettings.componentOptedOut.set(current === 2);
- Glean.shoppingSettings.hasOnboarded.set(current > 0);
- },
-
- recordUserAdsPreference() {
- Glean.shoppingSettings.disabledAds.set(!ShoppingUtils.adsUserEnabled);
- },
-
- recordUserAutoOpenPreference() {
- Glean.shoppingSettings.autoOpenUserDisabled.set(
- !ShoppingUtils.autoOpenUserEnabled
- );
- },
-
- /**
- * If the user has not opted in, automatically set the sidebar to `active` if:
- * 1. The sidebar has not already been automatically set to `active` twice.
- * 2. It's been at least 24 hours since the user last saw the sidebar because
- * of this auto-activation behavior.
- * 3. This method has not already been called (handledAutoActivate is false)
- */
- handleAutoActivateOnProduct() {
- let shouldAutoActivate = false;
-
- if (
- !this.handledAutoActivate &&
- !this.optedIn &&
- this.cfrFeatures &&
- this.autoOpenEnabled
- ) {
- let autoActivateCount = Services.prefs.getIntPref(
- AUTO_ACTIVATE_COUNT_PREF,
- 0
- );
- let lastAutoActivate = Services.prefs.getIntPref(
- LAST_AUTO_ACTIVATE_PREF,
- 0
- );
- let now = Date.now() / 1000;
- // If we automatically set `active` to true in a previous session less
- // than 24 hours ago, set it to false now. This is done to prevent the
- // auto-activation state from persisting between sessions. Effectively,
- // the auto-activation will persist until either 1) the sidebar is closed,
- // or 2) Firefox restarts.
- if (now - lastAutoActivate < 24 * 60 * 60) {
- Services.prefs.setBoolPref(ACTIVE_PREF, false);
- }
- // Set active to true if we haven't done so recently nor more than twice.
- else if (autoActivateCount < 2) {
- Services.prefs.setBoolPref(ACTIVE_PREF, true);
- shouldAutoActivate = true;
- Services.prefs.setIntPref(
- AUTO_ACTIVATE_COUNT_PREF,
- autoActivateCount + 1
- );
- Services.prefs.setIntPref(LAST_AUTO_ACTIVATE_PREF, now);
- }
- }
- this.handledAutoActivate = true;
- return shouldAutoActivate;
- },
-
- /**
- * Send a Shopping-related trigger message to ASRouter.
- *
- * @param {object} trigger The trigger object to send to ASRouter.
- * @param {object} trigger.context Additional trigger properties to pass to
- * the targeting context.
- * @param {string} trigger.id The id of the trigger.
- * @param {MozBrowser} trigger.browser The browser to associate with the
- * trigger. (This can determine the tab/window the message is shown in,
- * depending on the message surface)
- */
- async sendTrigger(trigger) {
- await lazy.ASRouter.waitForInitialized;
- await lazy.ASRouter.sendTriggerMessage(trigger);
- },
-
- onActiveUpdate(subject, topic, data) {
- if (data !== ACTIVE_PREF || topic !== "nsPref:changed") {
- return;
- }
-
- let newValue = Services.prefs.getBoolPref(ACTIVE_PREF);
- if (newValue === false) {
- ShoppingUtils.resetActiveOnNextProductPage = true;
- }
- },
-
- isAutoOpenEligible() {
- return (
- this.optedIn === 1 && this.autoOpenEnabled && this.autoOpenUserEnabled
- );
- },
-
- onLocationChange(aLocationURI, aFlags) {
- let isProductPageNavigation = this.isProductPageNavigation(
- aLocationURI,
- aFlags
- );
-
- if (isProductPageNavigation) {
- this.recordExposure();
- }
-
- if (
- this.isAutoOpenEligible() &&
- this.resetActiveOnNextProductPage &&
- isProductPageNavigation
- ) {
- this.resetActiveOnNextProductPage = false;
- Services.prefs.setBoolPref(ACTIVE_PREF, true);
- }
- },
-
- /**
- * Check if two URIs represent the same product by
- * comparing URLs and then parsed product ID.
- *
- * @param {nsIURI} aURI
- * @param {nsIURI} bURI
- *
- * @returns {boolean}
- */
- isSameProduct(aURI, bURI) {
- if (!aURI || !bURI) {
- return false;
- }
-
- // Check if the URIs are equal and are products.
- if (aURI.equalsExceptRef(bURI)) {
- return lazy.isProductURL(aURI);
- }
-
- // Check if the product ids are the same:
- let aProductID = lazy.getProductIdFromURL(aURI);
- let bProductID = lazy.getProductIdFromURL(bURI);
-
- if (!aProductID || !bProductID) {
- return false;
- }
-
- return aProductID === bProductID;
- },
-
- /**
- * Removes browser `isDistinctProductPageVisit` flag that indicates
- * a tab has an unhandled product navigation.
- *
- * @param {browser} browser
- */
- clearIsDistinctProductPageVisitFlag(browser) {
- if (browser.isDistinctProductPageVisit) {
- delete browser.isDistinctProductPageVisit;
- }
- },
-};
-
-XPCOMUtils.defineLazyPreferenceGetter(
- ShoppingUtils,
- "optedIn",
- OPTED_IN_PREF,
- 0,
- ShoppingUtils.setOnUpdate
-);
-
-XPCOMUtils.defineLazyPreferenceGetter(
- ShoppingUtils,
- "cfrFeatures",
- CFR_FEATURES_PREF,
- true
-);
-
-XPCOMUtils.defineLazyPreferenceGetter(
- ShoppingUtils,
- "adsUserEnabled",
- ADS_USER_ENABLED_PREF,
- false,
- ShoppingUtils.recordUserAdsPreference
-);
-
-XPCOMUtils.defineLazyPreferenceGetter(
- ShoppingUtils,
- "autoOpenEnabled",
- AUTO_OPEN_ENABLED_PREF,
- false
-);
-
-XPCOMUtils.defineLazyPreferenceGetter(
- ShoppingUtils,
- "autoOpenUserEnabled",
- AUTO_OPEN_USER_ENABLED_PREF,
- false,
- ShoppingUtils.recordUserAutoOpenPreference
-);
diff --git a/browser/components/shopping/content/adjusted-rating.mjs b/browser/components/shopping/content/adjusted-rating.mjs
deleted file mode 100644
index fb5d956530f5..000000000000
--- a/browser/components/shopping/content/adjusted-rating.mjs
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-/**
- * Class for displaying the adjusted ratings for a given product.
- */
-class AdjustedRating extends MozLitElement {
- static properties = {
- rating: { type: Number, reflect: true },
- };
-
- render() {
- if (!this.rating && this.rating !== 0) {
- this.hidden = true;
- return null;
- }
-
- this.hidden = false;
-
- return html`
-
-
-
-
-
- `;
- }
-}
-
-customElements.define("adjusted-rating", AdjustedRating);
diff --git a/browser/components/shopping/content/analysis-explainer.css b/browser/components/shopping/content/analysis-explainer.css
deleted file mode 100644
index ce7ebf55cc38..000000000000
--- a/browser/components/shopping/content/analysis-explainer.css
+++ /dev/null
@@ -1,39 +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/. */
-
-@import url("chrome://global/skin/in-content/common.css");
-
-#analysis-explainer-wrapper p,
-.analysis-explainer-grading-scale-description {
- line-height: 150%;
-}
-
-#analysis-explainer-grading-scale-wrapper {
- margin-inline-start: 0.54em;
-}
-
-#analysis-explainer-grading-scale-list {
- list-style: none;
- padding: 0;
-}
-
-.analysis-explainer-grading-scale-entry {
- display: flex;
- align-items: flex-start;
- align-self: stretch;
- gap: 0.54rem;
- padding: 0.54em;
- padding-inline-start: 0;
-}
-
-.analysis-explainer-grading-scale-letters {
- display: flex;
- align-items: flex-start;
- gap: 0.2rem;
- width: 3.47em;
-}
-
-.analysis-explainer-grading-scale-description {
- margin: 0;
-}
diff --git a/browser/components/shopping/content/analysis-explainer.mjs b/browser/components/shopping/content/analysis-explainer.mjs
deleted file mode 100644
index c545f9327af5..000000000000
--- a/browser/components/shopping/content/analysis-explainer.mjs
+++ /dev/null
@@ -1,173 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-card.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/letter-grade.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://global/content/elements/moz-support-link.mjs";
-
-const VALID_EXPLAINER_L10N_IDS = new Map([
- ["reliable", "shopping-analysis-explainer-review-grading-scale-reliable"],
- ["mixed", "shopping-analysis-explainer-review-grading-scale-mixed"],
- ["unreliable", "shopping-analysis-explainer-review-grading-scale-unreliable"],
-]);
-
-/**
- * Class for displaying details about letter grades, adjusted rating, and highlights.
- */
-class AnalysisExplainer extends MozLitElement {
- static properties = {
- productUrl: { type: String, reflect: true },
- };
-
- static get queries() {
- return {
- reviewQualityExplainerLink: "#review-quality-url",
- };
- }
-
- getGradesDescriptionTemplate() {
- return html`
-
- `;
- }
-
- createGradingScaleEntry(letters, descriptionL10nId) {
- let letterGradesTemplate = [];
- for (let letter of letters) {
- letterGradesTemplate.push(
- html` `
- );
- }
- return html`
-
-
-
- ${letterGradesTemplate}
-
-
-
-
- `;
- }
-
- getGradingScaleListTemplate() {
- return html`
-
-
- ${this.createGradingScaleEntry(
- ["A", "B"],
- VALID_EXPLAINER_L10N_IDS.get("reliable")
- )}
- ${this.createGradingScaleEntry(
- ["C"],
- VALID_EXPLAINER_L10N_IDS.get("mixed")
- )}
- ${this.createGradingScaleEntry(
- ["D", "F"],
- VALID_EXPLAINER_L10N_IDS.get("unreliable")
- )}
-
-
- `;
- }
-
- getRetailerDisplayName() {
- if (!this.productUrl) {
- return null;
- }
- let url = new URL(this.productUrl);
- let hostname = url.hostname;
- let displayNames = {
- "www.amazon.com": "Amazon",
- "www.amazon.de": "Amazon",
- "www.amazon.fr": "Amazon",
- // only other regional domain is bestbuy.ca
- "www.bestbuy.com": "Best Buy",
- // regional urls redirect to walmart.com
- "www.walmart.com": "Walmart",
- };
- return displayNames[hostname];
- }
-
- handleReviewQualityUrlClicked(e) {
- if (e.target.localName == "a" && e.button == 0) {
- Glean.shopping.surfaceShowQualityExplainerUrlClicked.record();
- }
- }
-
- createReviewsExplainer() {
- const retailer = this.getRetailerDisplayName();
-
- if (retailer) {
- return html`
-
- `;
- }
-
- return html`
-
- `;
- }
-
- // Bug 1857620: rather than manually set the utm parameters on the SUMO link,
- // we should instead update moz-support-link to allow arbitrary utm parameters.
- render() {
- return html`
-
-
-
-
-
- ${this.getGradesDescriptionTemplate()}
- ${this.getGradingScaleListTemplate()}
-
- ${this.createReviewsExplainer()}
-
-
-
-
-
-
- `;
- }
-}
-
-customElements.define("analysis-explainer", AnalysisExplainer);
diff --git a/browser/components/shopping/content/assets/optInDark.avif b/browser/components/shopping/content/assets/optInDark.avif
deleted file mode 100644
index fb662e63dd20..000000000000
Binary files a/browser/components/shopping/content/assets/optInDark.avif and /dev/null differ
diff --git a/browser/components/shopping/content/assets/optInLight.avif b/browser/components/shopping/content/assets/optInLight.avif
deleted file mode 100644
index e148580976a8..000000000000
Binary files a/browser/components/shopping/content/assets/optInLight.avif and /dev/null differ
diff --git a/browser/components/shopping/content/assets/packaging.svg b/browser/components/shopping/content/assets/packaging.svg
deleted file mode 100644
index fddf88c87847..000000000000
--- a/browser/components/shopping/content/assets/packaging.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/browser/components/shopping/content/assets/priceTagButtonCallout.svg b/browser/components/shopping/content/assets/priceTagButtonCallout.svg
deleted file mode 100644
index 26431804cba6..000000000000
--- a/browser/components/shopping/content/assets/priceTagButtonCallout.svg
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/shopping/content/assets/quality.svg b/browser/components/shopping/content/assets/quality.svg
deleted file mode 100644
index cdc092d1eac9..000000000000
--- a/browser/components/shopping/content/assets/quality.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
diff --git a/browser/components/shopping/content/assets/ratingDark.avif b/browser/components/shopping/content/assets/ratingDark.avif
deleted file mode 100644
index 240c2d623842..000000000000
Binary files a/browser/components/shopping/content/assets/ratingDark.avif and /dev/null differ
diff --git a/browser/components/shopping/content/assets/ratingLight.avif b/browser/components/shopping/content/assets/ratingLight.avif
deleted file mode 100644
index a6eb3a0316ed..000000000000
Binary files a/browser/components/shopping/content/assets/ratingLight.avif and /dev/null differ
diff --git a/browser/components/shopping/content/assets/reviewCheckerCalloutPriceTag.svg b/browser/components/shopping/content/assets/reviewCheckerCalloutPriceTag.svg
deleted file mode 100644
index 06b3a4f48d32..000000000000
--- a/browser/components/shopping/content/assets/reviewCheckerCalloutPriceTag.svg
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/shopping/content/assets/reviewsVisualCallout.svg b/browser/components/shopping/content/assets/reviewsVisualCallout.svg
deleted file mode 100644
index 9a9b93ba063f..000000000000
--- a/browser/components/shopping/content/assets/reviewsVisualCallout.svg
+++ /dev/null
@@ -1,77 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/shopping/content/assets/shipping.svg b/browser/components/shopping/content/assets/shipping.svg
deleted file mode 100644
index d9b01a0a96a5..000000000000
--- a/browser/components/shopping/content/assets/shipping.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/browser/components/shopping/content/assets/shopping.svg b/browser/components/shopping/content/assets/shopping.svg
deleted file mode 100644
index 5b81219d4886..000000000000
--- a/browser/components/shopping/content/assets/shopping.svg
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
diff --git a/browser/components/shopping/content/assets/unanalyzedDark.avif b/browser/components/shopping/content/assets/unanalyzedDark.avif
deleted file mode 100644
index 66efc3bc6e01..000000000000
Binary files a/browser/components/shopping/content/assets/unanalyzedDark.avif and /dev/null differ
diff --git a/browser/components/shopping/content/assets/unanalyzedLight.avif b/browser/components/shopping/content/assets/unanalyzedLight.avif
deleted file mode 100644
index 28cd1c26f121..000000000000
Binary files a/browser/components/shopping/content/assets/unanalyzedLight.avif and /dev/null differ
diff --git a/browser/components/shopping/content/highlight-item.css b/browser/components/shopping/content/highlight-item.css
deleted file mode 100644
index e30a5b64ea6f..000000000000
--- a/browser/components/shopping/content/highlight-item.css
+++ /dev/null
@@ -1,65 +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/. */
-
-/* Separate logo, label and details into rows and columns */
-.highlight-item-wrapper {
- display: grid;
- column-gap: 1em;
- grid-template-columns: 1rem auto;
- grid-template-rows: 1rem auto;
-}
-
-.highlight-icon {
- content: url("chrome://global/skin/icons/defaultFavicon.svg");
- color: var(--icon-color);
- fill: currentColor;
- -moz-context-properties: fill;
- grid-row-start: 1;
- grid-column-start: 1;
-
- &.price {
- content: url("chrome://browser/skin/price.svg");
- }
-
- &.quality {
- content: url("chrome://browser/content/shopping/assets/quality.svg");
- }
-
- &.shipping {
- content: url("chrome://browser/content/shopping/assets/shipping.svg");
- }
-
- &.competitiveness {
- content: url("chrome://global/skin/icons/trophy.svg");
- }
-
- &.packaging\/appearance {
- content: url("chrome://browser/content/shopping/assets/packaging.svg");
- }
-}
-
-.highlight-label {
- font-weight: var(--font-weight-bold);
- grid-column-start: 2;
- grid-row-start: 1;
-}
-
-.highlight-details {
- grid-column-start: 2;
- grid-row-start: 2;
- margin: 0;
- padding: 0;
-}
-
-.highlight-details-list {
- list-style-type: none;
- padding: 0;
- margin: 0;
-}
-
-.highlight-details-list > li {
- /* Render LTR since English review snippets are only supported at this time. */
- direction: ltr;
- margin: 1em 0;
-}
diff --git a/browser/components/shopping/content/highlight-item.mjs b/browser/components/shopping/content/highlight-item.mjs
deleted file mode 100644
index a61764dc86d2..000000000000
--- a/browser/components/shopping/content/highlight-item.mjs
+++ /dev/null
@@ -1,57 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-/**
- * Class for displaying a list of highlighted product reviews, according to highlight category.
- */
-class Highlight extends MozLitElement {
- l10nId;
- highlightType;
- /**
- * reviews is a list of Strings, representing all the reviews to display
- * under a highlight category.
- */
- reviews;
-
- /**
- * lang defines the language in which the reviews are written. We should specify
- * language so that screen readers can read text with the appropriate language packs.
- */
- lang;
-
- render() {
- let ulTemplate = [];
-
- for (let review of this.reviews) {
- ulTemplate.push(
- html`
- ${review}
- `
- );
- }
-
- return html`
-
-
- `;
- }
-}
-
-customElements.define("highlight-item", Highlight);
diff --git a/browser/components/shopping/content/highlights.mjs b/browser/components/shopping/content/highlights.mjs
deleted file mode 100644
index 09ed055b28c0..000000000000
--- a/browser/components/shopping/content/highlights.mjs
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/highlight-item.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-card.mjs";
-
-const VALID_HIGHLIGHT_L10N_IDs = new Map([
- ["price", "shopping-highlight-price"],
- ["quality", "shopping-highlight-quality"],
- ["shipping", "shopping-highlight-shipping"],
- ["competitiveness", "shopping-highlight-competitiveness"],
- ["packaging/appearance", "shopping-highlight-packaging"],
-]);
-
-/**
- * Class for displaying all available highlight categories for a product and any
- * highlight reviews per category.
- */
-class ReviewHighlights extends MozLitElement {
- /**
- * highlightsMap is a map of highlight categories to an array of reviews per category.
- * It is possible for a category to have no reviews.
- */
- #highlightsMap;
-
- static properties = {
- highlights: { type: Object },
- lang: { type: String, reflect: true },
- };
-
- static get queries() {
- return {
- reviewHighlightsListEl: "#review-highlights-list",
- };
- }
-
- updateHighlightsMap() {
- let availableKeys;
- this.#highlightsMap = null;
-
- try {
- if (!this.highlights) {
- return;
- }
-
- // Filter highlights that have data.
- let keys = Object.keys(this.highlights);
- availableKeys = keys.filter(
- key => Object.values(this.highlights[key]).flat().length !== 0
- );
-
- // Filter valid highlight category types. Valid types are guaranteed to have data-l10n-ids.
- availableKeys = availableKeys.filter(key =>
- VALID_HIGHLIGHT_L10N_IDs.has(key)
- );
-
- // If there are no highlights to show in the end, stop here and don't render content.
- if (!availableKeys.length) {
- return;
- }
- } catch (e) {
- return;
- }
-
- this.#highlightsMap = new Map();
-
- for (let key of availableKeys) {
- // Ignore negative,neutral,positive sentiments and simply append review strings into one array.
- let reviews = Object.values(this.highlights[key]).flat();
- this.#highlightsMap.set(key, reviews);
- }
- }
-
- createHighlightElement(type, reviews) {
- let highlightEl = document.createElement("highlight-item");
- // We already verify highlight type and its l10n id in updateHighlightsMap.
- let l10nId = VALID_HIGHLIGHT_L10N_IDs.get(type);
- highlightEl.id = type;
- highlightEl.l10nId = l10nId;
- highlightEl.highlightType = type;
- highlightEl.reviews = reviews;
- highlightEl.lang = this.lang;
- return highlightEl;
- }
-
- render() {
- this.updateHighlightsMap();
-
- if (!this.#highlightsMap) {
- this.hidden = true;
- return null;
- }
-
- this.hidden = false;
-
- let highlightsTemplate = [];
- for (let [key, value] of this.#highlightsMap) {
- let highlightEl = this.createHighlightElement(key, value);
- highlightsTemplate.push(highlightEl);
- }
-
- return html`
-
-
-
${highlightsTemplate}
-
-
- `;
- }
-}
-
-customElements.define("review-highlights", ReviewHighlights);
diff --git a/browser/components/shopping/content/letter-grade.css b/browser/components/shopping/content/letter-grade.css
deleted file mode 100644
index bf37e62a6af4..000000000000
--- a/browser/components/shopping/content/letter-grade.css
+++ /dev/null
@@ -1,132 +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/. */
-
-@import url("chrome://global/skin/in-content/common.css");
-
-:host {
- --background-term-a: #B3FFE3;
- --background-term-b: #80EBFF;
- --background-term-c: #FFEA80;
- --background-term-d: #FFB587;
- --background-term-f: #FF848B;
- --in-content-box-border-color: rgba(0, 0, 0, 0.15);
- --inner-border: 1px solid var(--in-content-box-border-color);
- --letter-grade-width: 1.5rem;
- --letter-grade-term-inline-padding: 0.25rem;
-}
-
-#letter-grade-wrapper {
- border-radius: 4px;
- color: #000;
- display: flex;
- font-weight: 600;
- line-height: 150%;
- margin: 0;
- overflow-wrap: anywhere;
-}
-
-#letter-grade-term {
- align-items: center;
- align-self: stretch;
- box-sizing: border-box;
- display: flex;
- flex-shrink: 0;
- font-size: 1em;
- justify-content: center;
- margin: 0;
- padding: 0.0625rem var(--letter-grade-term-inline-padding);
- text-align: center;
- width: var(--letter-grade-width);
-}
-
-:host([showdescription]) #letter-grade-term {
- /* For border "shadow" inside the container */
- border-block: var(--inner-border);
- border-inline-start: var(--inner-border);
- border-start-start-radius: 4px;
- border-end-start-radius: 4px;
- /* Add 1px padding so that the letter does not shift when changing
- * between the show description and no description variants. */
- padding-inline-end: calc(var(--letter-grade-term-inline-padding) + 1px);
-}
-
-:host(:not([showdescription])) #letter-grade-term {
- border: var(--inner-border);
- border-radius: 4px;
-}
-
-#letter-grade-description {
- /* For border "shadow" inside the container */
- border-block: var(--inner-border);
- border-inline-end: var(--inner-border);
- border-start-end-radius: 4px;
- border-end-end-radius: 4px;
-
- align-items: center;
- align-self: stretch;
- box-sizing: border-box;
- display: flex;
- font-size: 0.87rem;
- font-weight: var(--font-weight);
- margin: 0;
- padding: 0.125rem 0.5rem;
-}
-
-/* Letter grade colors */
-
-:host([letter="A"]) #letter-grade-term {
- background-color: var(--background-term-a);
-}
-
-:host([letter="A"]) #letter-grade-description {
- background-color: rgba(231, 255, 246, 1);
-}
-
-:host([letter="B"]) #letter-grade-term {
- background-color: var(--background-term-b);
-}
-
-:host([letter="B"]) #letter-grade-description {
- background-color: rgba(222, 250, 255, 1);
-}
-
-:host([letter="C"]) #letter-grade-term {
- background-color: var(--background-term-c);
-}
-
-:host([letter="C"]) #letter-grade-description {
- background-color: rgba(255, 249, 218, 1);
-}
-
-:host([letter="D"]) #letter-grade-term {
- background-color: var(--background-term-d);
-}
-
-:host([letter="D"]) #letter-grade-description {
- background-color: rgba(252, 230, 213, 1);
-}
-
-:host([letter="F"]) #letter-grade-term {
- background-color: var(--background-term-f);
-}
-
-:host([letter="F"]) #letter-grade-description {
- background-color: rgba(255, 228, 230, 1);
-}
-
-@media (prefers-contrast) {
- :host {
- --in-content-box-border-color: unset;
- }
-
- #letter-grade-term {
- background-color: var(--in-content-page-color) !important;
- color: var(--in-content-page-background) !important;
- }
-
- #letter-grade-description {
- background-color: var(--in-content-page-background) !important;
- color: var(--in-content-page-color) !important;
- }
-}
diff --git a/browser/components/shopping/content/letter-grade.mjs b/browser/components/shopping/content/letter-grade.mjs
deleted file mode 100644
index 71badb1c4578..000000000000
--- a/browser/components/shopping/content/letter-grade.mjs
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-const VALID_LETTER_GRADE_L10N_IDS = new Map([
- ["A", "shopping-letter-grade-description-ab"],
- ["B", "shopping-letter-grade-description-ab"],
- ["C", "shopping-letter-grade-description-c"],
- ["D", "shopping-letter-grade-description-df"],
- ["F", "shopping-letter-grade-description-df"],
-]);
-
-class LetterGrade extends MozLitElement {
- #descriptionL10N;
-
- static properties = {
- letter: { type: String, reflect: true },
- showdescription: { type: Boolean, reflect: true },
- };
-
- get fluentStrings() {
- if (!this._fluentStrings) {
- this._fluentStrings = new Localization(["browser/shopping.ftl"], true);
- }
- return this._fluentStrings;
- }
-
- descriptionTemplate() {
- if (this.showdescription) {
- return html`
`;
- }
-
- return null;
- }
-
- render() {
- // Do not render if letter is invalid
- if (!VALID_LETTER_GRADE_L10N_IDS.has(this.letter)) {
- return null;
- }
-
- this.#descriptionL10N = VALID_LETTER_GRADE_L10N_IDS.get(this.letter);
- let tooltipL10NArgs;
- // Do not localize tooltip if using Storybook.
- if (!window.IS_STORYBOOK) {
- const localizedDescription = this.fluentStrings.formatValueSync(
- this.#descriptionL10N
- );
- tooltipL10NArgs = `{"letter": "${this.letter}", "description": "${localizedDescription}"}`;
- }
-
- return html`
-
-
- ${this.letter}
- ${this.descriptionTemplate()}
-
- `;
- }
-}
-
-customElements.define("letter-grade", LetterGrade);
diff --git a/browser/components/shopping/content/onboarding.mjs b/browser/components/shopping/content/onboarding.mjs
deleted file mode 100644
index fd0db23fe494..000000000000
--- a/browser/components/shopping/content/onboarding.mjs
+++ /dev/null
@@ -1,31 +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/. */
-
-const BUNDLE_SRC =
- "chrome://browser/content/aboutwelcome/aboutwelcome.bundle.js";
-
-class Onboarding {
- constructor({ win } = {}) {
- this.doc = win.document;
- win.addEventListener("RenderWelcome", () => this._addScriptsAndRender());
- }
-
- async _addScriptsAndRender() {
- // Load the bundle to render the content as configured.
- this.doc.querySelector(`[src="${BUNDLE_SRC}"]`)?.remove();
- let bundleScript = this.doc.createElement("script");
- bundleScript.src = BUNDLE_SRC;
- this.doc.head.appendChild(bundleScript);
- }
-
- static getOnboarding() {
- if (!this.onboarding) {
- this.onboarding = new Onboarding({ win: window });
- }
- return this.onboarding;
- }
-}
-
-const OnboardingContainer = Onboarding.getOnboarding();
-export default OnboardingContainer;
diff --git a/browser/components/shopping/content/recommended-ad.css b/browser/components/shopping/content/recommended-ad.css
deleted file mode 100644
index 3e2551b84f25..000000000000
--- a/browser/components/shopping/content/recommended-ad.css
+++ /dev/null
@@ -1,105 +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/. */
-
-:host {
- --rec-content-gap: var(--space-medium);
-}
-
-#recommended-ad-wrapper {
- display: flex;
- flex-direction: column;
- gap: var(--space-small);
- text-decoration: none;
- color: var(--in-content-text-color);
-}
-
-#recommended-ad-wrapper:hover {
- cursor: pointer;
-}
-
-#recommended-ad-wrapper:hover #ad-title {
- text-decoration: underline;
- color: var(--link-color-hover);
-}
-
-#recommended-ad-wrapper:focus-visible {
- outline-offset: 4px;
- border-radius: 1px;
-}
-
-#ad-content {
- display: flex;
- justify-content: space-between;
- gap: var(--rec-content-gap);
- height: 80px;
-}
-
-#ad-letter-wrapper {
- display: flex;
- justify-content: space-between;
- flex: 1;
- gap: var(--rec-content-gap);
-}
-
-#ad-preview-image {
- max-width: 80px;
- max-height: 80px;
-}
-
-#ad-title {
- overflow: hidden;
- text-overflow: ellipsis;
- height: fit-content;
- display: -webkit-box;
- -webkit-line-clamp: 4;
- -webkit-box-orient: vertical;
- /* This text won't be localized and when in RTL, the ellipsis is positioned
- awkwardly so we are forcing LTR */
- direction: ltr;
-}
-
-#price {
- font-size: 1em;
- font-weight: 600;
-}
-
-#footer {
- display: flex;
- align-self: end;
-
- &.has-price {
- justify-content: space-between;
- align-self: unset;
- }
-}
-
-#sponsored-label {
- font-size: var(--font-size-small);
- margin-block-start: 8px;
- color: var(--text-color-deemphasized);
-}
-
-/**
- * Render responsive UI for smaller sidebar widths. CSS variables are not supported
- * in media queries, so we'll hardcode the units in the calculation.
- *
- * 270px: min width for the card component needed before switching to another layout.
- * 12px: gap between product image, product name, letter grade (see --rec-content-gap). Since we can't use vars, approximate to 12px instead.
- */
-@media (width < calc(270px + 12px)) {
- #ad-content {
- flex-direction: column;
- height: revert;
- }
-
- #ad-letter-wrapper {
- flex-direction: column-reverse;
- }
-
- #footer {
- align-self: start;
- gap: var(--space-small);
- flex-direction: column;
- }
-}
diff --git a/browser/components/shopping/content/recommended-ad.mjs b/browser/components/shopping/content/recommended-ad.mjs
deleted file mode 100644
index 02f6f2b4d24b..000000000000
--- a/browser/components/shopping/content/recommended-ad.mjs
+++ /dev/null
@@ -1,178 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-card.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://global/content/elements/moz-five-star.mjs";
-
-const AD_IMPRESSION_TIMEOUT = 1500;
-// We are only showing prices in USD for now. In the future we will need
-// to update this to include other currencies.
-const SUPPORTED_CURRENCY_SYMBOLS = new Map([["USD", "$"]]);
-
-class RecommendedAd extends MozLitElement {
- static properties = {
- product: { type: Object, reflect: true },
- };
-
- static get queries() {
- return {
- letterGradeEl: "letter-grade",
- linkEl: "#recommended-ad-wrapper",
- priceEl: "#price",
- ratingEl: "moz-five-star",
- sponsoredLabelEl: "#sponsored-label",
- };
- }
-
- connectedCallback() {
- super.connectedCallback();
- if (this.initialized) {
- return;
- }
- this.initialized = true;
-
- document.addEventListener("visibilitychange", this);
- }
-
- disconnectedCallback() {
- super.disconnectedCallback();
- document.removeEventListener("visibilitychange", this);
- this.resetImpressionTimer();
- this.revokeImageUrl();
- }
-
- startImpressionTimer() {
- if (!this.timeout && document.visibilityState === "visible") {
- this.timeout = setTimeout(
- () => this.recordImpression(),
- AD_IMPRESSION_TIMEOUT
- );
- }
- }
-
- resetImpressionTimer() {
- this.timeout = clearTimeout(this.timeout);
- }
-
- revokeImageUrl() {
- if (this.imageUrl) {
- URL.revokeObjectURL(this.imageUrl);
- }
- }
-
- recordImpression() {
- if (this.hasImpressed) {
- return;
- }
-
- this.dispatchEvent(
- new CustomEvent("AdImpression", {
- bubbles: true,
- detail: { aid: this.product.aid, sponsored: this.product.sponsored },
- })
- );
-
- document.removeEventListener("visibilitychange", this);
- this.resetImpressionTimer();
-
- this.hasImpressed = true;
- }
-
- handleClick(event) {
- if (event.button === 0 || event.button === 1) {
- this.dispatchEvent(
- new CustomEvent("AdClicked", {
- bubbles: true,
- detail: { aid: this.product.aid, sponsored: this.product.sponsored },
- })
- );
- }
- }
-
- handleEvent(event) {
- if (event.type !== "visibilitychange") {
- return;
- }
- if (document.visibilityState === "visible") {
- this.startImpressionTimer();
- } else if (!this.hasImpressed) {
- this.resetImpressionTimer();
- }
- }
-
- priceTemplate() {
- const currencySymbol = SUPPORTED_CURRENCY_SYMBOLS.get(
- this.product.currency
- );
-
- // TODO: not all non-USD currencies have the symbol displayed on the right.
- // Adjust symbol position as we add more supported currencies.
- let priceLabelText;
- if (this.product.currency === "USD") {
- priceLabelText = `${currencySymbol}${this.product.price}`;
- } else {
- priceLabelText = `${this.product.price}${currencySymbol}`;
- }
-
- return html`${priceLabelText} `;
- }
-
- render() {
- this.startImpressionTimer();
-
- this.revokeImageUrl();
- this.imageUrl = URL.createObjectURL(this.product.image_blob);
-
- const hasPrice =
- this.product.price &&
- SUPPORTED_CURRENCY_SYMBOLS.has(this.product.currency);
-
- return html`
-
-
-
-
-
-
- ${this.product.name}
-
-
-
-
-
-
- ${
- this.product.sponsored
- ? html``
- : null
- }
- `;
- }
-}
-
-customElements.define("recommended-ad", RecommendedAd);
diff --git a/browser/components/shopping/content/reliability.mjs b/browser/components/shopping/content/reliability.mjs
deleted file mode 100644
index 1e46b30c4bce..000000000000
--- a/browser/components/shopping/content/reliability.mjs
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/letter-grade.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-card.mjs";
-
-class ReviewReliability extends MozLitElement {
- static properties = {
- letter: { type: String },
- };
-
- static get queries() {
- return {
- letterGradeEl: "letter-grade",
- };
- }
-
- render() {
- if (!this.letter) {
- this.hidden = true;
- return null;
- }
-
- return html`
-
-
-
-
-
- `;
- }
-}
-
-customElements.define("review-reliability", ReviewReliability);
diff --git a/browser/components/shopping/content/settings.css b/browser/components/shopping/content/settings.css
deleted file mode 100644
index 2424eb2994eb..000000000000
--- a/browser/components/shopping/content/settings.css
+++ /dev/null
@@ -1,75 +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/. */
-
-@import url("chrome://global/skin/in-content/common.css");
-
-#shopping-settings-wrapper {
- --shopping-settings-between-label-and-control-option-gap: 4px;
- display: grid;
- grid-template-rows: auto;
- row-gap: 8px;
- margin-top: 12px;
-
- .shopping-settings-toggle-option-wrapper {
- display: flex;
- flex-direction: column;
- row-gap: var(--shopping-settings-between-label-and-control-option-gap);
- }
-
- #shopping-settings-opt-out-section {
- display: flex;
- justify-content: center;
-
- #shopping-settings-opt-out-button {
- display: flex;
- align-items: center;
- }
- }
-
- #shopping-settings-toggles-section {
- row-gap: var(--space-small);
- overflow: auto;
- overflow-wrap: break-word;
- hyphens: auto;
- }
-
- #shopping-ads-learn-more-link {
- font-size: var(--font-size-small);
- }
-
- /* When `browser.shopping.experience2023.autoOpen` is true. */
- &.shopping-settings-auto-open-ui-enabled {
- --shopping-settings-between-options-gap: 12px;
- row-gap: var(--shopping-settings-between-options-gap);
-
- #shopping-settings-toggles-section {
- display: flex;
- flex-direction: column;
- row-gap: var(--shopping-settings-between-options-gap);
- }
-
- .divider {
- border: var(--shopping-card-border-width) solid var(--shopping-card-border-color);
-
- @media (prefers-contrast) {
- border-color: ButtonText;
- border-width: 1px 0 0 0;
- }
- }
-
- #shopping-settings-opt-out-section {
- row-gap: var(--shopping-settings-between-label-and-control-option-gap);
-
- #shopping-settings-opt-out-button {
- width: fit-content;
- margin-inline-start: 0;
- }
- }
- }
-}
-
-#powered-by-fakespot {
- font-size: var(--font-size-small);
- color: var(--text-color-deemphasized);
-}
diff --git a/browser/components/shopping/content/settings.mjs b/browser/components/shopping/content/settings.mjs
deleted file mode 100644
index 5d2fec4c5279..000000000000
--- a/browser/components/shopping/content/settings.mjs
+++ /dev/null
@@ -1,205 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* eslint-env mozilla/remote-page */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://global/content/elements/moz-toggle.mjs";
-
-class ShoppingSettings extends MozLitElement {
- static properties = {
- adsEnabled: { type: Boolean },
- adsEnabledByUser: { type: Boolean },
- autoOpenEnabled: { type: Boolean },
- autoOpenEnabledByUser: { type: Boolean },
- hostname: { type: String },
- };
-
- static get queries() {
- return {
- wrapperEl: "#shopping-settings-wrapper",
- recommendationsToggleEl: "#shopping-settings-recommendations-toggle",
- autoOpenToggleEl: "#shopping-settings-auto-open-toggle",
- autoOpenToggleDescriptionEl: "#shopping-auto-open-description",
- dividerEl: ".divider",
- optOutButtonEl: "#shopping-settings-opt-out-button",
- shoppingCardEl: "shopping-card",
- adsLearnMoreLinkEl: "#shopping-ads-learn-more-link",
- fakespotLearnMoreLinkEl: "#powered-by-fakespot-link",
- };
- }
-
- onToggleRecommendations() {
- this.adsEnabledByUser = this.recommendationsToggleEl.pressed;
- let action = this.adsEnabledByUser ? "enabled" : "disabled";
- Glean.shopping.surfaceAdsSettingToggled.record({ action });
- RPMSetPref(
- "browser.shopping.experience2023.ads.userEnabled",
- this.adsEnabledByUser
- );
- }
-
- onToggleAutoOpen() {
- this.autoOpenEnabledByUser = this.autoOpenToggleEl.pressed;
- let action = this.autoOpenEnabledByUser ? "enabled" : "disabled";
- Glean.shopping.surfaceAutoOpenSettingToggled.record({ action });
- RPMSetPref(
- "browser.shopping.experience2023.autoOpen.userEnabled",
- this.autoOpenEnabledByUser
- );
- if (!this.autoOpenEnabledByUser) {
- RPMSetPref("browser.shopping.experience2023.active", false);
- }
- }
-
- onDisableShopping() {
- window.dispatchEvent(
- new CustomEvent("DisableShopping", { bubbles: true, composed: true })
- );
- Glean.shopping.surfaceOptOutButtonClicked.record();
- }
-
- fakespotLinkClicked(e) {
- if (e.target.localName == "a" && e.button == 0) {
- Glean.shopping.surfacePoweredByFakespotLinkClicked.record();
- }
- }
-
- render() {
- // Whether we show recommendations at all (including offering a user
- // control for them) is controlled via a nimbus-enabled pref.
- let canShowRecommendationToggle = this.adsEnabled;
-
- let adsToggleMarkup = canShowRecommendationToggle
- ? html` `
- : null;
-
- /* Auto-open experiment changes how the settings card appears by:
- * 1. Showing a new toggle for enabling/disabling auto-open behaviour
- * 2. Adding a divider between the toggles and opt-out button
- * 3. Showing text indicating that Review Checker is enabled (not opted-out) above the opt-out button
- *
- * Only show if `browser.shopping.experience2023.autoOpen.enabled` is true.
- */
- let autoOpenDescriptionL10nId =
- "shopping-settings-auto-open-and-close-description-three-sites";
- let autoOpenDescriptionL10nArgs = {
- firstSite: "Amazon",
- secondSite: "Best Buy",
- thirdSite: "Walmart",
- };
-
- if (
- RPMGetBoolPref("toolkit.shopping.experience2023.defr", false) &&
- (this.hostname === "www.amazon.fr" || this.hostname === "www.amazon.de")
- ) {
- autoOpenDescriptionL10nId =
- "shopping-settings-auto-open-and-close-description-single-site";
- autoOpenDescriptionL10nArgs = {
- currentSite: "Amazon",
- };
- }
-
- let autoOpenToggleMarkup = this.autoOpenEnabled
- ? html`
-
-
-
-
`
- : null;
-
- return html`
-
-
-
-
-
- ${this.autoOpenEnabled
- ? html` `
- : null}
-
-
-
-
-
-
- `;
- }
-}
-
-customElements.define("shopping-settings", ShoppingSettings);
diff --git a/browser/components/shopping/content/shopping-card.css b/browser/components/shopping/content/shopping-card.css
deleted file mode 100644
index 0f5c03aab655..000000000000
--- a/browser/components/shopping/content/shopping-card.css
+++ /dev/null
@@ -1,60 +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/. */
-
-.shopping-card {
- position: relative;
-}
-
-moz-card {
- --card-padding: var(--space-medium);
-
- &::part(heading) {
- font-size: unset;
- }
-}
-
-#content {
- align-self: stretch;
-}
-
-#label-wrapper {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between;
- align-items: center;
- gap: 0.5rem;
- width: 100%;
- margin-bottom: var(--space-small);
-}
-
-#heading {
- font-weight: var(--font-weight-bold);
- margin: 0;
-}
-
-.show-more footer {
- width: 100%;
- background-color: var(--background-color-box);
- box-shadow: 2px -10px 11px var(--background-color-box);
- border-top: var(--border-width) solid var(--card-border-color);
- border-radius: 0 0 var(--border-radius-medium) var(--border-radius-medium);
- position: absolute;
- bottom: 0;
- text-align: center;
- padding-block: var(--space-small);
- inset-inline: 0;
-}
-
-.show-more[expanded="false"] {
- overflow: clip;
- height: 200px;
-}
-
-:host(:not([showMoreButtonDisabled])) .show-more ::slotted(div) {
- margin-block-end: 4rem;
-}
-
-:host([showMoreButtonDisabled]) footer {
- display: none;
-}
diff --git a/browser/components/shopping/content/shopping-card.mjs b/browser/components/shopping/content/shopping-card.mjs
deleted file mode 100644
index d828ea99d72d..000000000000
--- a/browser/components/shopping/content/shopping-card.mjs
+++ /dev/null
@@ -1,180 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://global/content/elements/moz-card.mjs";
-
-const MIN_SHOW_MORE_HEIGHT = 200;
-/**
- * A card container to be used in the shopping sidebar. There are three card types.
- * The default type where no type attribute is required and the card will have no extra functionality.
- * The "accordion" type will initially not show any content. The card will contain a arrow to expand the
- * card so all of the content is visible.
- *
- * @property {string} label - The label text that will be used for the card header
- * @property {string} type - (optional) The type of card. No type specified
- * will be the default card. The other available types are "accordion" and "show-more".
- */
-class ShoppingCard extends MozLitElement {
- static properties = {
- label: { type: String },
- type: { type: String },
- rating: { type: String },
- _isExpanded: { type: Boolean },
- };
-
- static get queries() {
- return {
- contentEl: "#content",
- };
- }
-
- cardTemplate() {
- if (this.type === "show-more") {
- return html`
-
-
-
-
-
- `;
- }
- return html`
-
- ${this.headingTemplate()}
-
-
- `;
- }
-
- headingTemplate() {
- if (this.rating) {
- return html`
- ${this.label}
-
-
`;
- }
- return "";
- }
-
- onCardToggle(e) {
- const action = e.newState == "open" ? "expanded" : "collapsed";
- let cardId = this.getAttribute("id");
- switch (cardId) {
- case "shopping-settings-label":
- Glean.shopping.surfaceSettingsExpandClicked.record({ action });
- break;
- case "shopping-analysis-explainer-label":
- Glean.shopping.surfaceShowQualityExplainerClicked.record({
- action,
- });
- break;
- }
- }
-
- handleShowMoreButtonClick(e) {
- this._isExpanded = !this._isExpanded;
- // toggle show more/show less text
- e.target.setAttribute(
- "data-l10n-id",
- this._isExpanded
- ? "shopping-show-less-button"
- : "shopping-show-more-button"
- );
- // toggle content expanded attribute
- this.contentEl.attributes.expanded.value = this._isExpanded;
-
- let action = this._isExpanded ? "expanded" : "collapsed";
- Glean.shopping.surfaceShowMoreReviewsButtonClicked.record({
- action,
- });
- }
-
- enableShowMoreButton() {
- this._isExpanded = false;
- this.toggleAttribute("showMoreButtonDisabled", false);
- this.contentEl.attributes.expanded.value = false;
- }
-
- disableShowMoreButton() {
- this._isExpanded = true;
- this.toggleAttribute("showMoreButtonDisabled", true);
- this.contentEl.attributes.expanded.value = true;
- }
-
- firstUpdated() {
- if (this.type !== "show-more") {
- return;
- }
-
- let contentSlot = this.shadowRoot.querySelector("slot[name='content']");
- let contentSlotEls = contentSlot.assignedElements();
- if (!contentSlotEls.length) {
- return;
- }
-
- let slottedDiv = contentSlotEls[0];
-
- this.handleContentSlotResize = this.handleContentSlotResize.bind(this);
- this.contentResizeObserver = new ResizeObserver(
- this.handleContentSlotResize
- );
- this.contentResizeObserver.observe(slottedDiv);
- }
-
- disconnectedCallback() {
- this.contentResizeObserver?.disconnect();
- }
-
- handleContentSlotResize(entries) {
- for (let entry of entries) {
- if (entry.contentRect.height === 0) {
- return;
- }
-
- if (entry.contentRect.height < MIN_SHOW_MORE_HEIGHT) {
- this.disableShowMoreButton();
- } else if (this.hasAttribute("showMoreButtonDisabled")) {
- this.enableShowMoreButton();
- }
- }
- }
-
- render() {
- return html`
-
-
- ${this.cardTemplate()}
-
- `;
- }
-}
-customElements.define("shopping-card", ShoppingCard);
diff --git a/browser/components/shopping/content/shopping-container.css b/browser/components/shopping/content/shopping-container.css
deleted file mode 100644
index 904bc1caa067..000000000000
--- a/browser/components/shopping/content/shopping-container.css
+++ /dev/null
@@ -1,157 +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/. */
-
-:host {
- --shopping-close-button-size: var(--button-min-height);
- --shopping-header-font-size: 1.3rem;
-}
-
-#shopping-container {
- display: flex;
- flex-direction: column;
- width: 100%;
-}
-
-#header-wrapper {
- display: flex;
- justify-content: space-between;
- align-items: center;
- gap: var(--space-small);
- background-color: var(--shopping-header-background);
- box-sizing: border-box;
- padding-block: 16px 8px;
- padding-inline: 16px 8px;
- position: sticky;
- top: 0;
- width: 100%;
- z-index: 3;
-
- &.header-wrapper-overflow {
- align-items: baseline;
- }
-}
-
-#shopping-header {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: var(--space-small);
-}
-
-#shopping-header-title {
- font-size: var(--shopping-header-font-size);
- font-weight: var(--font-weight-bold);
- margin: 0;
-}
-
-.header-wrapper-shadow {
- box-shadow: 0 1px 2px 0 rgba(58, 57, 68, 0.20);
-}
-
-#beta-marker {
- font-size: var(--font-size-small);
- font-weight: var(--font-weight);
- padding: 2px 4px;
- margin: 0;
- line-height: 150%;
- text-transform: uppercase;
- color: var(--icon-color);
- border: 1px solid var(--icon-color);
- border-radius: var(--border-radius-small);
-}
-
-#close-button {
- min-width: var(--shopping-close-button-size);
- min-height: var(--shopping-close-button-size);
- -moz-context-properties: fill;
- fill: currentColor;
- background-image: url("chrome://global/skin/icons/close.svg");
- background-repeat: no-repeat;
- background-position: center;
- margin: 0;
-
- @media not (prefers-contrast) {
- color: var(--icon-color);
- }
-}
-
-#content {
- overflow: auto;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- gap: 16px;
- padding: 0 16px 16px;
-
- &:focus-visible {
- outline-offset: -2px;
- }
-}
-
-.loading-box {
- box-shadow: none;
- border: none;
- border-radius: var(--border-radius-medium);
- background: var(--in-content-button-background);
- margin-block: 1rem;
-}
-
-.loading-box.small {
- height: 2.67rem;
-}
-
-.loading-box.medium {
- height: 5.34rem;
-}
-
-.loading-box.large {
- height: 12.8rem;
-}
-
-.loading-box:nth-child(odd) {
- background-color: var(--in-content-button-background);
-}
-
-.loading-box:nth-child(even) {
- background-color: var(--in-content-button-background-hover);
-}
-
-@media not (prefers-reduced-motion) {
- .animate > .loading-box {
- animation-name: fade-in;
- animation-direction: alternate;
- animation-duration: 1s;
- animation-iteration-count: infinite;
- animation-timing-function: ease-in-out;
- }
-
- /* First box + every 4th box, fifth box + every 4th box */
- .loading-box:nth-child(4n-3) {
- animation-delay: -1s;
- }
-
- /* Second box + every 4th box, sixth box + every 4th box */
- .loading-box:nth-child(4n-2) {
- animation-delay: 0s;
- }
-
- /* Third box + every 4th box */
- .loading-box:nth-child(4n-1) {
- animation-delay: -1.5s;
- }
-
- /* Fourth box + every 4th box */
- .loading-box:nth-child(4n) {
- animation-delay: -0.5s;
- }
-
- @keyframes fade-in {
- from {
- opacity: .25;
- }
- to {
- opacity: 1;
- }
- }
-}
diff --git a/browser/components/shopping/content/shopping-container.mjs b/browser/components/shopping/content/shopping-container.mjs
deleted file mode 100644
index 5fb0c9ba84db..000000000000
--- a/browser/components/shopping/content/shopping-container.mjs
+++ /dev/null
@@ -1,543 +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-env mozilla/remote-page */
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/highlights.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/settings.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/adjusted-rating.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/reliability.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/analysis-explainer.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-message-bar.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/unanalyzed.mjs";
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/recommended-ad.mjs";
-
-// The number of pixels that must be scrolled from the
-// top of the sidebar to show the header box shadow.
-const HEADER_SCROLL_PIXEL_OFFSET = 8;
-
-const HEADER_NOT_TEXT_WRAPPED_HEIGHT = 32;
-const SIDEBAR_CLOSED_COUNT_PREF =
- "browser.shopping.experience2023.sidebarClosedCount";
-const SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
- "browser.shopping.experience2023.showKeepSidebarClosedMessage";
-const SHOPPING_SIDEBAR_ACTIVE_PREF = "browser.shopping.experience2023.active";
-
-const CLOSED_COUNT_PREVIOUS_MIN = 4;
-const CLOSED_COUNT_PREVIOUS_MAX = 6;
-
-export class ShoppingContainer extends MozLitElement {
- static properties = {
- data: { type: Object },
- showOnboarding: { type: Boolean },
- productUrl: { type: String },
- recommendationData: { type: Array },
- isOffline: { type: Boolean },
- analysisEvent: { type: Object },
- userReportedAvailable: { type: Boolean },
- adsEnabled: { type: Boolean },
- adsEnabledByUser: { type: Boolean },
- isAnalysisInProgress: { type: Boolean },
- analysisProgress: { type: Number },
- showHeaderShadow: { type: Boolean, state: true },
- isOverflow: { type: Boolean },
- autoOpenEnabled: { type: Boolean },
- autoOpenEnabledByUser: { type: Boolean },
- showingKeepClosedMessage: { type: Boolean },
- isHeaderOverflow: { type: Boolean, state: true },
- };
-
- static get queries() {
- return {
- reviewReliabilityEl: "review-reliability",
- adjustedRatingEl: "adjusted-rating",
- highlightsEl: "review-highlights",
- settingsEl: "shopping-settings",
- analysisExplainerEl: "analysis-explainer",
- unanalyzedProductEl: "unanalyzed-product-card",
- shoppingMessageBarEl: "shopping-message-bar",
- recommendedAdEl: "recommended-ad",
- loadingEl: "#loading-wrapper",
- closeButtonEl: "#close-button",
- keepClosedMessageBarEl: "#keep-closed-message-bar",
- containerContentEl: "#content",
- header: "#shopping-header",
- };
- }
-
- connectedCallback() {
- super.connectedCallback();
- if (this.initialized) {
- return;
- }
- this.initialized = true;
-
- window.document.addEventListener("Update", this);
- window.document.addEventListener("NewAnalysisRequested", this);
- window.document.addEventListener("ReanalysisRequested", this);
- window.document.addEventListener("ReportedProductAvailable", this);
- window.document.addEventListener("adsEnabledByUserChanged", this);
- window.document.addEventListener("scroll", this);
- window.document.addEventListener("UpdateRecommendations", this);
- window.document.addEventListener("UpdateAnalysisProgress", this);
- window.document.addEventListener("autoOpenEnabledByUserChanged", this);
- window.document.addEventListener("ShowKeepClosedMessage", this);
- window.document.addEventListener("HideKeepClosedMessage", this);
-
- window.dispatchEvent(
- new CustomEvent("ContentReady", {
- bubbles: true,
- composed: true,
- })
- );
- }
-
- disconnectedCallback() {
- this.headerResizeObserver?.disconnect();
- }
-
- firstUpdated() {
- this.headerResizeObserver = new ResizeObserver(([entry]) =>
- this.maybeSetIsHeaderOverflow(entry)
- );
- this.headerResizeObserver.observe(this.header);
- }
-
- updated() {
- if (this.focusCloseButton) {
- this.closeButtonEl.focus();
- }
- }
-
- async _update({
- data,
- showOnboarding,
- productUrl,
- recommendationData,
- adsEnabled,
- adsEnabledByUser,
- isAnalysisInProgress,
- analysisProgress,
- focusCloseButton,
- autoOpenEnabled,
- autoOpenEnabledByUser,
- }) {
- // If we're not opted in or there's no shopping URL in the main browser,
- // the actor will pass `null`, which means this will clear out any existing
- // content in the sidebar.
- this.data = data;
- this.showOnboarding = showOnboarding ?? this.showOnboarding;
- this.productUrl = productUrl;
- this.recommendationData = recommendationData;
- this.isOffline = !navigator.onLine;
- this.isAnalysisInProgress = isAnalysisInProgress;
- this.adsEnabled = adsEnabled ?? this.adsEnabled;
- this.adsEnabledByUser = adsEnabledByUser ?? this.adsEnabledByUser;
- this.analysisProgress = analysisProgress;
- this.focusCloseButton = focusCloseButton;
- this.autoOpenEnabled = autoOpenEnabled ?? this.autoOpenEnabled;
- this.autoOpenEnabledByUser =
- autoOpenEnabledByUser ?? this.autoOpenEnabledByUser;
- }
-
- _updateRecommendations({ recommendationData }) {
- this.recommendationData = recommendationData;
- }
-
- _updateAnalysisProgress({ progress }) {
- this.analysisProgress = progress;
- }
-
- handleEvent(event) {
- switch (event.type) {
- case "Update":
- this._update(event.detail);
- break;
- case "NewAnalysisRequested":
- case "ReanalysisRequested":
- this.isAnalysisInProgress = true;
- this.analysisEvent = {
- type: event.type,
- productUrl: this.productUrl,
- };
- window.dispatchEvent(
- new CustomEvent("PolledRequestMade", {
- bubbles: true,
- composed: true,
- })
- );
- break;
- case "ReportedProductAvailable":
- this.userReportedAvailable = true;
- window.dispatchEvent(
- new CustomEvent("ReportProductAvailable", {
- bubbles: true,
- composed: true,
- })
- );
- Glean.shopping.surfaceReactivatedButtonClicked.record();
- break;
- case "adsEnabledByUserChanged":
- this.adsEnabledByUser = event.detail?.adsEnabledByUser;
- break;
- case "scroll":
- this.showHeaderShadow = window.scrollY > HEADER_SCROLL_PIXEL_OFFSET;
- break;
- case "UpdateRecommendations":
- this._updateRecommendations(event.detail);
- break;
- case "UpdateAnalysisProgress":
- this._updateAnalysisProgress(event.detail);
- break;
- case "autoOpenEnabledByUserChanged":
- this.autoOpenEnabledByUser = event.detail?.autoOpenEnabledByUser;
- break;
- case "ShowKeepClosedMessage":
- this.showingKeepClosedMessage = true;
- break;
- case "HideKeepClosedMessage":
- this.showingKeepClosedMessage = false;
- break;
- }
- }
-
- maybeSetIsHeaderOverflow(entry) {
- let isOverflow = entry.contentRect.height > HEADER_NOT_TEXT_WRAPPED_HEIGHT;
- if (this.isHeaderOverflow != isOverflow) {
- this.isHeaderOverflow = isOverflow;
- }
- }
-
- getHostnameFromProductUrl() {
- let hostname = URL.parse(this.productUrl)?.hostname;
- if (hostname) {
- return hostname;
- }
- console.warn(`Unknown product url ${this.productUrl}.`);
- return null;
- }
-
- analysisDetailsTemplate() {
- /* At present, en is supported as the default language for reviews. As we support more sites,
- * update `lang` accordingly if highlights need to be displayed in other languages. */
- let hostname = this.getHostnameFromProductUrl();
-
- let lang = "en";
- let isDEFRSupported = RPMGetBoolPref(
- "toolkit.shopping.experience2023.defr",
- false
- );
-
- if (isDEFRSupported && hostname === "www.amazon.fr") {
- lang = "fr";
- } else if (isDEFRSupported && hostname === "www.amazon.de") {
- lang = "de";
- }
-
- return html`
-
-
-
- `;
- }
-
- hasDataTemplate() {
- let dataBodyTemplate = null;
-
- // The user requested an analysis which is not done yet.
- if (
- this.analysisEvent?.productUrl == this.productUrl &&
- this.isAnalysisInProgress
- ) {
- const isReanalysis = this.analysisEvent.type === "ReanalysisRequested";
- dataBodyTemplate = html`
- ${isReanalysis ? this.analysisDetailsTemplate() : null}`;
- } else if (this.data?.error) {
- dataBodyTemplate = html` `;
- } else if (this.data.page_not_supported) {
- dataBodyTemplate = html` `;
- } else if (this.data.deleted_product_reported) {
- dataBodyTemplate = html` `;
- } else if (this.data.deleted_product) {
- dataBodyTemplate = this.userReportedAvailable
- ? html` `
- : html` `;
- } else if (this.data.needs_analysis) {
- if (!this.data.product_id || typeof this.data.grade != "string") {
- // Product is new to us.
- dataBodyTemplate = html` `;
- } else {
- // We successfully analyzed the product before, but the current analysis is outdated and can be updated
- // via a re-analysis.
- dataBodyTemplate = html`
-
- ${this.analysisDetailsTemplate()}
- `;
- }
- } else if (this.data.not_enough_reviews) {
- // We already saw and tried to analyze this product before, but there are not enough reviews
- // to make a detailed analysis.
- dataBodyTemplate = html` `;
- } else {
- dataBodyTemplate = this.analysisDetailsTemplate();
- }
-
- return html`
- ${dataBodyTemplate}${this.explainerTemplate()}${this.recommendationTemplate()}
- `;
- }
-
- recommendationTemplate() {
- const canShowAds = this.adsEnabled && this.adsEnabledByUser;
- if (this.recommendationData?.length && canShowAds) {
- return html` `;
- }
- return null;
- }
-
- /**
- * @param {object?} options
- * @param {boolean?} options.animate = true
- * Whether to animate the loading state. Defaults to true.
- * There will be no animation for users who prefer reduced motion,
- * irrespective of the value of this option.
- */
- loadingTemplate({ animate = true } = {}) {
- /* Due to limitations with aria-busy for certain screen readers
- * (see Bug 1682063), mark loading container as a pseudo image and
- * use aria-label as a workaround. */
- return html`
-
- `;
- }
-
- noDataTemplate({ animate = true } = {}) {
- if (this.isAnalysisInProgress) {
- return html` ${this.explainerTemplate()}${this.recommendationTemplate()}`;
- }
- return this.loadingTemplate({ animate });
- }
-
- headerTemplate() {
- const headerWrapperClasses = `${this.showHeaderShadow ? "header-wrapper-shadow" : ""} ${this.isHeaderOverflow ? "header-wrapper-overflow" : ""}`;
- return html``;
- }
-
- // TODO: (Bug 1949647) do not render "Keep closed" message and notification card simultaneously.
- renderContainer(sidebarContent, { showSettings = false } = {}) {
- return html`
-
-
- `;
- }
-
- explainerTemplate({ className = "" } = {}) {
- return html` `;
- }
-
- settingsTemplate({ className = "" } = {}) {
- let hostname = this.getHostnameFromProductUrl();
- return html` `;
- }
-
- userInteractionMessageTemplate() {
- if (!this.autoOpenEnabled || !this.autoOpenEnabledByUser) {
- return null;
- }
-
- if (this.showingKeepClosedMessage) {
- return this.keepClosedMessageTemplate();
- }
-
- return null;
- }
-
- keepClosedMessageTemplate() {
- return html` `;
- }
-
- render() {
- let content;
- // this.data may be null because we're viewing a non PDP, or a PDP that does not have data yet.
- const isLoadingData = !this.data;
-
- if (this.showOnboarding) {
- content = html``;
- } else if (isLoadingData || this.isOffline) {
- content = this.noDataTemplate({ animate: !this.isOffline });
- } else {
- content = this.hasDataTemplate();
- }
-
- const showSettings =
- !this.showOnboarding &&
- !this.isOffline &&
- (!isLoadingData || (isLoadingData && this.isAnalysisInProgress));
- return this.renderContainer(content, { showSettings });
- }
-
- handleCloseButtonClick() {
- let canShowKeepClosedMessage;
-
- if (this.autoOpenEnabled && this.autoOpenEnabledByUser) {
- canShowKeepClosedMessage =
- this._canShowKeepClosedMessageOnCloseButtonClick();
- }
-
- if (!canShowKeepClosedMessage) {
- RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
- window.dispatchEvent(
- new CustomEvent("CloseShoppingSidebar", {
- bubbles: true,
- composed: true,
- })
- );
- }
-
- Glean.shopping.surfaceClosed.record({ source: "closeButton" });
- }
-
- /**
- * Delaying close is only applicable to the "Keep closed" message.
- *
- * We can show the message under these conditions:
- * - User has already seen the new location notification card
- * - The message was never seen before
- * - User clicked the close button at least 5 times in a session (i.e. met the minimum of 4 counts before this point)
- * - The number of close attempts in a session is less than 7 (i.e. met the maximum of 6 counts before this point)
- *
- * Do not show the message again after the 7th close.
- */
- _canShowKeepClosedMessageOnCloseButtonClick() {
- if (
- !RPMGetBoolPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false) ||
- this.showOnboarding
- ) {
- return false;
- }
-
- let sidebarClosedCount = RPMGetIntPref(SIDEBAR_CLOSED_COUNT_PREF, 0);
- let canShowKeepClosedMessage =
- !this.showingKeepClosedMessage &&
- sidebarClosedCount >= CLOSED_COUNT_PREVIOUS_MIN;
-
- if (canShowKeepClosedMessage) {
- this.showingKeepClosedMessage = true;
- return true;
- }
-
- this.showingKeepClosedMessage = false;
-
- if (sidebarClosedCount >= CLOSED_COUNT_PREVIOUS_MAX) {
- RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
- } else {
- RPMSetPref(SIDEBAR_CLOSED_COUNT_PREF, sidebarClosedCount + 1);
- }
-
- return false;
- }
-}
-
-customElements.define("shopping-container", ShoppingContainer);
diff --git a/browser/components/shopping/content/shopping-message-bar.css b/browser/components/shopping/content/shopping-message-bar.css
deleted file mode 100644
index fe3320310f43..000000000000
--- a/browser/components/shopping/content/shopping-message-bar.css
+++ /dev/null
@@ -1,70 +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/. */
-
-@import url("chrome://global/skin/in-content/common.css");
-
-:host {
- display: block;
- border-radius: 4px;
-}
-
-#message-bar-container {
- display: flex;
- flex-direction: column;
- gap: 4px;
- padding: 0;
- margin: 0;
-}
-
-:host([type="stale"]) #message-bar-container {
- display: flex;
- flex-direction: row;
- flex-wrap: wrap;
- column-gap: 8px;
-
- > span {
- display: flex;
- flex-direction: column;
- align-self: center;
- }
-}
-
-button {
- margin-inline-start: 0;
-}
-
-.shopping-message-bar {
- display: flex;
- align-items: center;
- padding-block: 0.5rem;
- gap: 0.75rem;
-
- &.analysis-in-progress {
- align-items: start;
- }
-
- .icon {
- --message-bar-icon-url: url("chrome://global/skin/icons/info-filled.svg");
- width: var(--icon-size-default);
- height: var(--icon-size-default);
- flex-shrink: 0;
- appearance: none;
- -moz-context-properties: fill, stroke;
- fill: currentColor;
- stroke: currentColor;
- color: var(--icon-color);
- background-image: var(--message-bar-icon-url);
- }
-}
-
-:host([type=analysis-in-progress]) .icon,
-:host([type=reanalysis-in-progress]) .icon {
- --message-bar-icon-url: conic-gradient(var(--icon-color-information) var(--analysis-progress-pcent, 0%), transparent var(--analysis-progress-pcent, 0%));
- border: 1px solid var(--icon-color);
- border-radius: 50%;
- margin-block: 1px 0;
- margin-inline: 1px 0;
- width: calc(var(--icon-size-default) - 2px);
- height: calc(var(--icon-size-default) - 2px);
-}
diff --git a/browser/components/shopping/content/shopping-message-bar.mjs b/browser/components/shopping/content/shopping-message-bar.mjs
deleted file mode 100644
index 50c74b6fb871..000000000000
--- a/browser/components/shopping/content/shopping-message-bar.mjs
+++ /dev/null
@@ -1,276 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* eslint-env mozilla/remote-page */
-
-import { html, styleMap } from "chrome://global/content/vendor/lit.all.mjs";
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://global/content/elements/moz-message-bar.mjs";
-
-const SHOPPING_SIDEBAR_ACTIVE_PREF = "browser.shopping.experience2023.active";
-const SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF =
- "browser.shopping.experience2023.showKeepSidebarClosedMessage";
-const SHOPPING_AUTO_OPEN_SIDEBAR_PREF =
- "browser.shopping.experience2023.autoOpen.userEnabled";
-
-class ShoppingMessageBar extends MozLitElement {
- #MESSAGE_TYPES_RENDER_TEMPLATE_MAPPING = new Map([
- ["stale", () => this.staleWarningTemplate()],
- ["generic-error", () => this.genericErrorTemplate()],
- ["not-enough-reviews", () => this.notEnoughReviewsTemplate()],
- ["product-not-available", () => this.productNotAvailableTemplate()],
- ["thanks-for-reporting", () => this.thanksForReportingTemplate()],
- [
- "product-not-available-reported",
- () => this.productNotAvailableReportedTemplate(),
- ],
- ["analysis-in-progress", () => this.analysisInProgressTemplate()],
- ["reanalysis-in-progress", () => this.reanalysisInProgressTemplate()],
- ["page-not-supported", () => this.pageNotSupportedTemplate()],
- ["thank-you-for-feedback", () => this.thankYouForFeedbackTemplate()],
- ["keep-closed", () => this.keepClosedTemplate()],
- ]);
-
- static properties = {
- type: { type: String },
- productUrl: { type: String, reflect: true },
- progress: { type: Number, reflect: true },
- };
-
- static get queries() {
- return {
- reAnalysisButtonEl: "#message-bar-reanalysis-button",
- productAvailableBtnEl: "#message-bar-report-product-available-btn",
- yesKeepClosedButtonEl: "#yes-keep-closed-button",
- noThanksButtonEl: "#no-thanks-button",
- };
- }
-
- onClickAnalysisButton() {
- this.dispatchEvent(
- new CustomEvent("ReanalysisRequested", {
- bubbles: true,
- composed: true,
- })
- );
- Glean.shopping.surfaceReanalyzeClicked.record();
- }
-
- onClickProductAvailable() {
- this.dispatchEvent(
- new CustomEvent("ReportedProductAvailable", {
- bubbles: true,
- composed: true,
- })
- );
- }
-
- handleNoThanksClick() {
- RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
- RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
- this.dispatchEvent(
- new CustomEvent("HideKeepClosedMessage", {
- bubbles: true,
- composed: true,
- })
- );
- Glean.shopping.surfaceNoThanksButtonClicked.record();
- }
-
- handleKeepClosedClick() {
- RPMSetPref(SHOPPING_SIDEBAR_ACTIVE_PREF, false);
- RPMSetPref(SHOW_KEEP_SIDEBAR_CLOSED_MESSAGE_PREF, false);
- RPMSetPref(SHOPPING_AUTO_OPEN_SIDEBAR_PREF, false);
- this.dispatchEvent(
- new CustomEvent("HideKeepClosedMessage", {
- bubbles: true,
- composed: true,
- })
- );
- Glean.shopping.surfaceYesKeepClosedButtonClicked.record();
- }
-
- staleWarningTemplate() {
- return html``;
- }
-
- genericErrorTemplate() {
- return html`
- `;
- }
-
- notEnoughReviewsTemplate() {
- return html`
- `;
- }
-
- productNotAvailableTemplate() {
- return html`
-
- `;
- }
-
- thanksForReportingTemplate() {
- return html`
- `;
- }
-
- productNotAvailableReportedTemplate() {
- return html`
- `;
- }
-
- analysisInProgressTemplate() {
- return html``;
- }
-
- reanalysisInProgressTemplate() {
- return html``;
- }
-
- pageNotSupportedTemplate() {
- return html`
- `;
- }
-
- thankYouForFeedbackTemplate() {
- return html`
- `;
- }
-
- keepClosedTemplate() {
- return html`
-
-
-
-
- `;
- }
-
- render() {
- let messageBarTemplate = this.#MESSAGE_TYPES_RENDER_TEMPLATE_MAPPING.get(
- this.type
- )();
- if (messageBarTemplate) {
- if (this.type == "stale") {
- Glean.shopping.surfaceStaleAnalysisShown.record();
- }
- return html`
-
-
- ${messageBarTemplate}
- `;
- }
- return null;
- }
-}
-
-customElements.define("shopping-message-bar", ShoppingMessageBar);
diff --git a/browser/components/shopping/content/shopping-page.css b/browser/components/shopping/content/shopping-page.css
deleted file mode 100644
index b6fd43704f25..000000000000
--- a/browser/components/shopping/content/shopping-page.css
+++ /dev/null
@@ -1,28 +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/. */
-
-:root {
- --shopping-header-background: light-dark(#f9f9fb, #2b2a33);
- background-color: var(--shopping-header-background);
- font: menu;
-}
-
-@media (prefers-contrast) {
- button.shopping-button:enabled,
- button.ghost-button:not(.semi-transparent):enabled {
- background-color: ButtonFace;
- border-color: ButtonText;
- color: ButtonText;
-
- &:hover {
- background-color: SelectedItemText;
- border-color: SelectedItem;
- color: SelectedItem;
- }
-
- &:hover:active {
- border-color: ButtonText;
- }
- }
-}
diff --git a/browser/components/shopping/content/shopping-sidebar.js b/browser/components/shopping/content/shopping-sidebar.js
deleted file mode 100644
index 820a327c32f3..000000000000
--- a/browser/components/shopping/content/shopping-sidebar.js
+++ /dev/null
@@ -1,79 +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/. */
-
-"use strict";
-
-// This is loaded into chrome windows with the subscript loader. Wrap in
-// a block to prevent accidentally leaking globals onto `window`.
-{
- const SHOPPING_SIDEBAR_WIDTH_PREF =
- "browser.shopping.experience2023.sidebarWidth";
- class ShoppingSidebar extends MozXULElement {
- #initialized;
-
- static get markup() {
- return `
-
- `;
- }
-
- constructor() {
- super();
- }
-
- connectedCallback() {
- this.initialize();
- }
-
- initialize() {
- if (this.#initialized) {
- return;
- }
- this.resizeObserverFn = this.resizeObserverFn.bind(this);
- this.appendChild(this.constructor.fragment);
-
- let previousWidth = Services.prefs.getIntPref(
- SHOPPING_SIDEBAR_WIDTH_PREF,
- 0
- );
- if (previousWidth > 0) {
- this.style.width = `${previousWidth}px`;
- }
-
- this.resizeObserver = new ResizeObserver(this.resizeObserverFn);
- this.resizeObserver.observe(this);
-
- this.#initialized = true;
- }
-
- resizeObserverFn(entries) {
- for (let entry of entries) {
- if (entry.contentBoxSize[0].inlineSize < 1) {
- return;
- }
-
- Services.prefs.setIntPref(
- SHOPPING_SIDEBAR_WIDTH_PREF,
- entry.contentBoxSize[0].inlineSize
- );
- }
- }
- }
-
- customElements.define("shopping-sidebar", ShoppingSidebar);
-}
diff --git a/browser/components/shopping/content/shopping.html b/browser/components/shopping/content/shopping.html
deleted file mode 100644
index 15219202b36e..000000000000
--- a/browser/components/shopping/content/shopping.html
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/browser/components/shopping/content/unanalyzed.css b/browser/components/shopping/content/unanalyzed.css
deleted file mode 100644
index d2e2b0b4b001..000000000000
--- a/browser/components/shopping/content/unanalyzed.css
+++ /dev/null
@@ -1,41 +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/. */
-
-@import url("chrome://global/skin/in-content/common.css");
-
-#unanalyzed-product-wrapper {
- display: flex;
- flex-direction: column;
- align-items: center;
- gap: 0.5rem;
-}
-
-#unanalyzed-product-icon {
- max-width: 264px;
- max-height: 290px;
- width: 100%;
- content: url("chrome://browser/content/shopping/assets/unanalyzedLight.avif");
-
- @media (prefers-color-scheme: dark) {
- content: url("chrome://browser/content/shopping/assets/unanalyzedDark.avif");
- }
-}
-
-#unanalyzed-product-message-content {
- display: flex;
- flex-direction: column;
- line-height: 1.5;
-
- > h2 {
- font-size: inherit;
- }
-
- > p {
- margin-block: 0.25rem;
- }
-}
-
-#unanalyzed-product-analysis-button {
- width: 100%;
-}
diff --git a/browser/components/shopping/content/unanalyzed.mjs b/browser/components/shopping/content/unanalyzed.mjs
deleted file mode 100644
index 0be85b65e4a4..000000000000
--- a/browser/components/shopping/content/unanalyzed.mjs
+++ /dev/null
@@ -1,61 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-import { html } from "chrome://global/content/vendor/lit.all.mjs";
-
-import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
-
-// eslint-disable-next-line import/no-unassigned-import
-import "chrome://browser/content/shopping/shopping-card.mjs";
-
-class UnanalyzedProductCard extends MozLitElement {
- static properties = {
- productURL: { type: String, reflect: true },
- };
-
- static get queries() {
- return {
- analysisButtonEl: "#unanalyzed-product-analysis-button",
- };
- }
-
- onClickAnalysisButton() {
- this.dispatchEvent(
- new CustomEvent("NewAnalysisRequested", {
- bubbles: true,
- composed: true,
- })
- );
- Glean.shopping.surfaceAnalyzeReviewsNoneAvailableClicked.record();
- }
-
- render() {
- return html`
-
-
-
-
-
-
-
-
- `;
- }
-}
-
-customElements.define("unanalyzed-product-card", UnanalyzedProductCard);
diff --git a/browser/components/shopping/jar.mn b/browser/components/shopping/jar.mn
deleted file mode 100644
index 25fe1b1c0e94..000000000000
--- a/browser/components/shopping/jar.mn
+++ /dev/null
@@ -1,31 +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/.
-
-browser.jar:
- content/browser/shopping/onboarding.mjs (content/onboarding.mjs)
- content/browser/shopping/shopping.html (content/shopping.html)
- content/browser/shopping/shopping-container.css (content/shopping-container.css)
- content/browser/shopping/shopping-page.css (content/shopping-page.css)
- content/browser/shopping/shopping-sidebar.js (content/shopping-sidebar.js)
- content/browser/shopping/shopping-message-bar.css (content/shopping-message-bar.css)
- content/browser/shopping/shopping-message-bar.mjs (content/shopping-message-bar.mjs)
- content/browser/shopping/highlights.mjs (content/highlights.mjs)
- content/browser/shopping/highlight-item.css (content/highlight-item.css)
- content/browser/shopping/highlight-item.mjs (content/highlight-item.mjs)
- content/browser/shopping/shopping-card.css (content/shopping-card.css)
- content/browser/shopping/shopping-card.mjs (content/shopping-card.mjs)
- content/browser/shopping/letter-grade.css (content/letter-grade.css)
- content/browser/shopping/letter-grade.mjs (content/letter-grade.mjs)
- content/browser/shopping/settings.mjs (content/settings.mjs)
- content/browser/shopping/settings.css (content/settings.css)
- content/browser/shopping/shopping-container.mjs (content/shopping-container.mjs)
- content/browser/shopping/adjusted-rating.mjs (content/adjusted-rating.mjs)
- content/browser/shopping/reliability.mjs (content/reliability.mjs)
- content/browser/shopping/analysis-explainer.css (content/analysis-explainer.css)
- content/browser/shopping/analysis-explainer.mjs (content/analysis-explainer.mjs)
- content/browser/shopping/unanalyzed.css (content/unanalyzed.css)
- content/browser/shopping/unanalyzed.mjs (content/unanalyzed.mjs)
- content/browser/shopping/recommended-ad.css (content/recommended-ad.css)
- content/browser/shopping/recommended-ad.mjs (content/recommended-ad.mjs)
- content/browser/shopping/assets/ (content/assets/*)
diff --git a/browser/components/shopping/moz.build b/browser/components/shopping/moz.build
deleted file mode 100644
index 1c830e672b36..000000000000
--- a/browser/components/shopping/moz.build
+++ /dev/null
@@ -1,19 +0,0 @@
-# -*- 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/.
-
-JAR_MANIFESTS += ["jar.mn"]
-
-FINAL_TARGET_FILES.actors += [
- "ShoppingSidebarChild.sys.mjs",
- "ShoppingSidebarParent.sys.mjs",
-]
-
-EXTRA_JS_MODULES += [
- "ShoppingUtils.sys.mjs",
-]
-
-with Files("**"):
- BUG_COMPONENT = ("Firefox", "Shopping")