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:
Jared Hirsch 2023-09-20 17:35:23 +00:00
Родитель d15bfcf389
Коммит 47f610d245
8 изменённых файлов: 253 добавлений и 29 удалений

Просмотреть файл

@ -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: