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` - - - -
    -
    - ${autoOpenToggleMarkup} ${adsToggleMarkup} -
    - ${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` - - -
    - ${this.headerTemplate()} -
    - - ${this.userInteractionMessageTemplate()}${sidebarContent} - ${showSettings - ? this.settingsTemplate({ className: "first-footer-card" }) - : null} -
    -
    `; - } - - 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")