зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1848160 - Record product page exposure for users in shopping experiment. r=perry.mcmanis,shopping-reviewers,kpatenio
Differential Revision: https://phabricator.services.mozilla.com/D188261
This commit is contained in:
Родитель
d15bfcf389
Коммит
47f610d245
|
@ -10069,7 +10069,7 @@ var ShoppingSidebarManager = {
|
|||
}
|
||||
|
||||
let { selectedBrowser, currentURI } = gBrowser;
|
||||
this.onLocationChange(selectedBrowser, currentURI, 0);
|
||||
this._maybeToggleSidebar(selectedBrowser, currentURI, 0);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -10079,6 +10079,15 @@ var ShoppingSidebarManager = {
|
|||
* those can be significant for us.
|
||||
*/
|
||||
onLocationChange(aBrowser, aLocationURI, aFlags) {
|
||||
ShoppingUtils.maybeRecordExposure(aLocationURI, aFlags);
|
||||
this._maybeToggleSidebar(aBrowser, aLocationURI, aFlags);
|
||||
},
|
||||
|
||||
// The strange signature is because this function was formerly the
|
||||
// onLocationChange function, but we needed to differentiate between
|
||||
// calls triggered by actual location changes and calls triggered by
|
||||
// TabSelect. We will refactor this code in bug 1845842.
|
||||
_maybeToggleSidebar(aBrowser, aLocationURI, aFlags) {
|
||||
if (!this._enabled) {
|
||||
return;
|
||||
}
|
||||
|
@ -10110,30 +10119,12 @@ var ShoppingSidebarManager = {
|
|||
this._updateBCActiveness(aBrowser);
|
||||
this._setShoppingButtonState(aBrowser);
|
||||
|
||||
if (isProduct) {
|
||||
let isVisible = sidebar && !sidebar.hidden;
|
||||
|
||||
// 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;
|
||||
|
||||
if (
|
||||
isVisible &&
|
||||
// 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))
|
||||
) {
|
||||
Glean.shopping.surfaceDisplayed.record();
|
||||
}
|
||||
if (
|
||||
sidebar &&
|
||||
!sidebar.hidden &&
|
||||
ShoppingUtils.isProductPageNavigation(aLocationURI, aFlags)
|
||||
) {
|
||||
Glean.shopping.surfaceDisplayed.record();
|
||||
}
|
||||
|
||||
if (isProduct) {
|
||||
|
|
|
@ -7,7 +7,9 @@ import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|||
const lazy = {};
|
||||
|
||||
ChromeUtils.defineESModuleGetters(lazy, {
|
||||
isProductURL: "chrome://global/content/shopping/ShoppingProduct.mjs",
|
||||
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
|
||||
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(lazy, {
|
||||
|
@ -25,9 +27,19 @@ export const ShoppingUtils = {
|
|||
initialized: false,
|
||||
registered: false,
|
||||
handledAutoActivate: false,
|
||||
nimbusEnabled: false,
|
||||
nimbusControl: false,
|
||||
|
||||
_updateNimbusVariables() {
|
||||
this.nimbusEnabled =
|
||||
lazy.NimbusFeatures.shopping2023.getVariable("enabled");
|
||||
this.nimbusControl =
|
||||
lazy.NimbusFeatures.shopping2023.getVariable("control");
|
||||
},
|
||||
|
||||
onNimbusUpdate() {
|
||||
if (lazy.NimbusFeatures.shopping2023.getVariable("enabled")) {
|
||||
this._updateNimbusVariables();
|
||||
if (this.nimbusEnabled) {
|
||||
ShoppingUtils.init();
|
||||
Glean.shoppingSettings.nimbusDisabledShopping.set(false);
|
||||
} else {
|
||||
|
@ -46,10 +58,11 @@ export const ShoppingUtils = {
|
|||
|
||||
if (!this.registered) {
|
||||
lazy.NimbusFeatures.shopping2023.onUpdate(ShoppingUtils.onNimbusUpdate);
|
||||
this._updateNimbusVariables();
|
||||
this.registered = true;
|
||||
}
|
||||
|
||||
if (!lazy.NimbusFeatures.shopping2023.getVariable("enabled")) {
|
||||
if (!this.nimbusEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -75,6 +88,59 @@ export const ShoppingUtils = {
|
|||
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)
|
||||
);
|
||||
},
|
||||
|
||||
// For users in either the nimbus control or treatment groups, increment a
|
||||
// counter when they visit supported product pages.
|
||||
maybeRecordExposure(aLocationURI, aFlags) {
|
||||
if (
|
||||
(this.nimbusEnabled || this.nimbusControl) &&
|
||||
ShoppingUtils.isProductPageNavigation(aLocationURI, aFlags)
|
||||
) {
|
||||
Glean.shopping.productPageVisits.add(1);
|
||||
}
|
||||
},
|
||||
|
||||
setOnUpdate(_pref, _prev, current) {
|
||||
Glean.shoppingSettings.componentOptedOut.set(current === 2);
|
||||
Glean.shoppingSettings.hasOnboarded.set(current > 0);
|
||||
|
|
|
@ -428,6 +428,26 @@ shopping:
|
|||
send_in_pings:
|
||||
- events
|
||||
|
||||
product_page_visits:
|
||||
type: counter
|
||||
description: |
|
||||
Counts number of visits to a supported retailer product page
|
||||
while enrolled in either the control or treatment branches
|
||||
of the shopping experiment.
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1848160
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1848160
|
||||
data_sensitivity:
|
||||
- interaction
|
||||
expires: 122
|
||||
notification_emails:
|
||||
- betling@mozilla.com
|
||||
- fx-desktop-shopping-eng@mozilla.com
|
||||
send_in_pings:
|
||||
- metrics
|
||||
telemetry_mirror: SHOPPING_PRODUCT_PAGE_VISITS
|
||||
|
||||
surface_powered_by_fakespot_link_clicked:
|
||||
type: event
|
||||
description: |
|
||||
|
|
|
@ -15,6 +15,7 @@ prefs =
|
|||
browser.newtabpage.activity-stream.asrouter.userprefs.cfr.features=false
|
||||
|
||||
[browser_adjusted_rating.js]
|
||||
[browser_exposure_telemetry.js]
|
||||
[browser_inprogress_analysis.js]
|
||||
[browser_network_offline.js]
|
||||
[browser_not_enough_reviews.js]
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
ShoppingUtils: "resource:///modules/ShoppingUtils.sys.mjs",
|
||||
});
|
||||
|
||||
// Tests in this file simulate exposure detection without actually loading the
|
||||
// product pages. Instead, we call the `ShoppingUtils.maybeRecordExposure`
|
||||
// method, passing in flags and URLs to simulate onLocationChange events.
|
||||
// Bug 1853401 captures followup work to add integration tests.
|
||||
|
||||
const PRODUCT_PAGE = Services.io.newURI(
|
||||
"https://example.com/product/B09TJGHL5F"
|
||||
);
|
||||
const WALMART_PAGE = Services.io.newURI(
|
||||
"https://www.walmart.com/ip/Utz-Cheese-Balls-23-Oz/15543964"
|
||||
);
|
||||
const WALMART_OTHER_PAGE = Services.io.newURI(
|
||||
"https://www.walmart.com/ip/Utz-Gluten-Free-Cheese-Balls-23-0-OZ/10898644"
|
||||
);
|
||||
|
||||
async function setup(pref) {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [[`browser.shopping.experience2023.${pref}`, true]],
|
||||
});
|
||||
await Services.fog.testFlushAllChildren();
|
||||
Services.fog.testResetFOG();
|
||||
}
|
||||
|
||||
async function teardown(pref) {
|
||||
await SpecialPowers.popPrefEnv();
|
||||
await Services.fog.testFlushAllChildren();
|
||||
Services.fog.testResetFOG();
|
||||
|
||||
// Clear out the normally short-lived pushState navigation cache in between
|
||||
// runs, to avoid accidentally deduping when we shouldn't.
|
||||
ShoppingUtils.lastWalmartURI = null;
|
||||
}
|
||||
|
||||
async function runTest({ aLocationURI, aFlags, expected }) {
|
||||
async function _run() {
|
||||
Assert.equal(undefined, Glean.shopping.productPageVisits.testGetValue());
|
||||
ShoppingUtils.maybeRecordExposure(aLocationURI, aFlags);
|
||||
await Services.fog.testFlushAllChildren();
|
||||
Assert.equal(expected, Glean.shopping.productPageVisits.testGetValue());
|
||||
}
|
||||
|
||||
await setup("enabled");
|
||||
await _run();
|
||||
await teardown("enabled");
|
||||
|
||||
await setup("control");
|
||||
await _run();
|
||||
await teardown("control");
|
||||
}
|
||||
|
||||
add_task(async function test_shopping_exposure_new_page() {
|
||||
await runTest({
|
||||
aLocationURI: PRODUCT_PAGE,
|
||||
aFlags: 0,
|
||||
expected: 1,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_shopping_exposure_reload_page() {
|
||||
await runTest({
|
||||
aLocationURI: PRODUCT_PAGE,
|
||||
aFlags: Ci.nsIWebProgressListener.LOCATION_CHANGE_RELOAD,
|
||||
expected: 1,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_shopping_exposure_session_restore_page() {
|
||||
await runTest({
|
||||
aLocationURI: PRODUCT_PAGE,
|
||||
aFlags: Ci.nsIWebProgressListener.LOCATION_CHANGE_SESSION_STORE,
|
||||
expected: 1,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_shopping_exposure_ignore_same_page() {
|
||||
await runTest({
|
||||
aLocationURI: PRODUCT_PAGE,
|
||||
aFlags: Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
|
||||
expected: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_shopping_exposure_count_same_page_pushstate() {
|
||||
await runTest({
|
||||
aLocationURI: WALMART_PAGE,
|
||||
aFlags: Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT,
|
||||
expected: 1,
|
||||
});
|
||||
});
|
||||
|
||||
add_task(async function test_shopping_exposure_ignore_pushstate_repeats() {
|
||||
async function _run() {
|
||||
let aFlags = Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT;
|
||||
Assert.equal(undefined, Glean.shopping.productPageVisits.testGetValue());
|
||||
|
||||
// Slightly different setup here: simulate deduping by setting the first
|
||||
// walmart page's URL as the `ShoppingUtils.lastWalmartURI`, then fire the
|
||||
// pushState for the first page, then twice for a second page. This seems
|
||||
// to be roughly the observed behavior when navigating between walmart
|
||||
// product pages.
|
||||
ShoppingUtils.lastWalmartURI = WALMART_PAGE;
|
||||
ShoppingUtils.maybeRecordExposure(WALMART_PAGE, aFlags);
|
||||
ShoppingUtils.maybeRecordExposure(WALMART_OTHER_PAGE, aFlags);
|
||||
ShoppingUtils.maybeRecordExposure(WALMART_OTHER_PAGE, aFlags);
|
||||
await Services.fog.testFlushAllChildren();
|
||||
Assert.equal(1, Glean.shopping.productPageVisits.testGetValue());
|
||||
}
|
||||
await setup("enabled");
|
||||
await _run();
|
||||
await teardown("enabled");
|
||||
await setup("control");
|
||||
await _run();
|
||||
await teardown("control");
|
||||
});
|
|
@ -8,7 +8,10 @@
|
|||
*/
|
||||
add_task(async function test_shopping_settings() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["toolkit.telemetry.testing.overridePreRelease", true]],
|
||||
set: [
|
||||
["toolkit.telemetry.testing.overridePreRelease", true],
|
||||
["browser.shopping.experience2023.optedIn", 0],
|
||||
],
|
||||
});
|
||||
|
||||
let opt_in_status = Services.prefs.getIntPref(
|
||||
|
|
|
@ -1669,9 +1669,13 @@ shopping2023:
|
|||
the shopping feature.
|
||||
variables:
|
||||
enabled:
|
||||
description: True if the experience is enabled
|
||||
description: True if the experience is enabled (experimental treatment group)
|
||||
type: boolean
|
||||
fallbackPref: browser.shopping.experience2023.enabled
|
||||
control:
|
||||
description: True if the experiment is enabled but experience is disabled (experimental control group)
|
||||
type: boolean
|
||||
fallbackPref: browser.shopping.experience2023.control
|
||||
adsEnabled:
|
||||
description: True if showing recommended products is enabled
|
||||
type: boolean
|
||||
|
|
|
@ -9231,6 +9231,22 @@ shopping:
|
|||
- 'firefox'
|
||||
record_in_processes:
|
||||
- main
|
||||
product_page_visits:
|
||||
bug_numbers:
|
||||
- 1848160
|
||||
description: >
|
||||
Counts number of visits to a supported retailer product page while
|
||||
enrolled in either the control or treatment branches of the shopping
|
||||
experiment.
|
||||
expires: "122"
|
||||
kind: uint
|
||||
notification_emails:
|
||||
- betling@mozilla.com
|
||||
- fx-desktop-shopping-eng@mozilla.com
|
||||
products:
|
||||
- 'firefox'
|
||||
record_in_processes:
|
||||
- main
|
||||
|
||||
power:
|
||||
cpu_time_bogus_values:
|
||||
|
|
Загрузка…
Ссылка в новой задаче