Update On Thu Sep 14 20:53:30 CEST 2023

This commit is contained in:
github-action[bot] 2023-09-14 20:53:31 +02:00
parent dedbbf188a
commit d8b708e2ec
2325 changed files with 47579 additions and 30132 deletions

10
Cargo.lock generated
View file

@ -1036,15 +1036,6 @@ dependencies = [
"smallvec",
]
[[package]]
name = "cssparser-color"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556c099a61d85989d7af52b692e35a8d68a57e7df8c6d07563dc0778b3960c9f"
dependencies = [
"cssparser",
]
[[package]]
name = "cssparser-macros"
version = "0.6.1"
@ -5191,7 +5182,6 @@ dependencies = [
"bitflags 1.3.2",
"byteorder",
"cssparser",
"cssparser-color",
"derive_more 0.99.999",
"dom",
"euclid",

View file

@ -40,6 +40,7 @@
#include "mozilla/EditorBase.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/ipc/ProcessChild.h"
#include "mozilla/PerfStats.h"
#include "mozilla/PresShell.h"
#include "nsAccessibilityService.h"
#include "mozilla/a11y/DocAccessibleChild.h"
@ -1456,6 +1457,9 @@ void DocAccessible::ProcessInvalidationList() {
void DocAccessible::ProcessQueuedCacheUpdates() {
AUTO_PROFILER_MARKER_TEXT("DocAccessible::ProcessQueuedCacheUpdates", A11Y,
{}, ""_ns);
PerfStats::AutoMetricRecording<
PerfStats::Metric::A11Y_ProcessQueuedCacheUpdate>
autoRecording;
// DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
nsTArray<CacheData> data;
@ -1563,6 +1567,8 @@ void DocAccessible::NotifyOfLoading(bool aIsReloading) {
void DocAccessible::DoInitialUpdate() {
AUTO_PROFILER_MARKER_TEXT("DocAccessible::DoInitialUpdate", A11Y, {}, ""_ns);
PerfStats::AutoMetricRecording<PerfStats::Metric::A11Y_DoInitialUpdate>
autoRecording;
// DO NOT ADD CODE ABOVE THIS BLOCK: THIS CODE IS MEASURING TIMINGS.
if (nsCoreUtils::IsTopLevelContentDocInProcess(mDocumentNode)) {

View file

@ -357,7 +357,9 @@ using namespace mozilla::a11y;
TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
AutoTArray<Accessible*, 10> headerCells;
cell->RowHeaderCells(&headerCells);
if (cell) {
cell->RowHeaderCells(&headerCells);
}
return utils::ConvertToNSArray(headerCells);
}
@ -366,7 +368,9 @@ using namespace mozilla::a11y;
TableCellAccessible* cell = mGeckoAccessible->AsTableCell();
AutoTArray<Accessible*, 10> headerCells;
cell->ColHeaderCells(&headerCells);
if (cell) {
cell->ColHeaderCells(&headerCells);
}
return utils::ConvertToNSArray(headerCells);
}

View file

@ -115,6 +115,9 @@ static mozilla::freestanding::LoaderPrivateAPIImp gPrivateAPI;
static mozilla::nt::SRWLock gLoaderObserverLock;
static mozilla::nt::LoaderObserver* gLoaderObserver = &gDefaultObserver;
static CONDITION_VARIABLE gLoaderObserverNoOngoingLoadsCV =
CONDITION_VARIABLE_INIT;
static mozilla::Atomic<uint32_t> gLoaderObserverOngoingLoadsCount(0);
namespace mozilla {
namespace freestanding {
@ -177,7 +180,11 @@ ModuleLoadInfo LoaderPrivateAPIImp::ConstructAndNotifyBeginDllLoad(
bool LoaderPrivateAPIImp::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
PHANDLE aOutHandle) {
nt::AutoSharedLock lock(gLoaderObserverLock);
// This method should only be called between NotifyBeginDllLoad and
// NotifyEndDllLoad, so it is already protected against gLoaderObserver
// change, through gLoaderObserverOngoingLoadsCount.
MOZ_RELEASE_ASSERT(gLoaderObserverOngoingLoadsCount);
return gLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
}
@ -190,13 +197,21 @@ void LoaderPrivateAPIImp::NotifyEndDllLoad(void* aContext,
aModuleLoadInfo.CaptureBacktrace();
}
nt::AutoSharedLock lock(gLoaderObserverLock);
// This method should only be called after a matching call to
// NotifyBeginDllLoad, so it is already protected against gLoaderObserver
// change, through gLoaderObserverOngoingLoadsCount.
// We need to notify the observer that the DLL load has ended even when
// |aLoadNtStatus| indicates a failure. This is to ensure that any resources
// acquired by the observer during OnBeginDllLoad are cleaned up.
gLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
std::move(aModuleLoadInfo));
auto previousValue = gLoaderObserverOngoingLoadsCount--;
MOZ_RELEASE_ASSERT(previousValue);
if (previousValue == 1) {
::RtlWakeAllConditionVariable(&gLoaderObserverNoOngoingLoadsCV);
}
}
nt::AllocatedUnicodeString LoaderPrivateAPIImp::GetSectionName(
@ -246,6 +261,7 @@ nt::MemorySectionNameBuf LoaderPrivateAPIImp::GetSectionNameBuffer(
void LoaderPrivateAPIImp::NotifyBeginDllLoad(
void** aContext, PCUNICODE_STRING aRequestedDllName) {
nt::AutoSharedLock lock(gLoaderObserverLock);
++gLoaderObserverOngoingLoadsCount;
gLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
}
@ -260,6 +276,11 @@ void LoaderPrivateAPIImp::SetObserver(nt::LoaderObserver* aNewObserver) {
nt::LoaderObserver* prevLoaderObserver = nullptr;
nt::AutoExclusiveLock lock(gLoaderObserverLock);
while (gLoaderObserverOngoingLoadsCount) {
NTSTATUS status = ::RtlSleepConditionVariableSRW(
&gLoaderObserverNoOngoingLoadsCV, &gLoaderObserverLock, nullptr, 0);
MOZ_RELEASE_ASSERT(NT_SUCCESS(status) && status != STATUS_TIMEOUT);
}
MOZ_ASSERT(aNewObserver);
if (!aNewObserver) {

View file

@ -1,4 +0,0 @@
[DEFAULT]
[test_aboutCrashed.xhtml]
[test_aboutRestartRequired.xhtml]

View file

@ -0,0 +1,5 @@
[DEFAULT]
["test_aboutCrashed.xhtml"]
["test_aboutRestartRequired.xhtml"]

View file

@ -14,7 +14,7 @@ with Files("content/docs/sslerrorreport/**"):
SCHEDULES.exclusive = ["docs"]
MOCHITEST_CHROME_MANIFESTS += [
"content/test/chrome/chrome.ini",
"content/test/chrome/chrome.toml",
]
BROWSER_CHROME_MANIFESTS += [

View file

@ -3479,7 +3479,19 @@ BrowserGlue.prototype = {
if (bookmarksUrl) {
// Import from bookmarks.html file.
try {
if (Services.policies.isAllowed("defaultBookmarks")) {
if (
Services.policies.isAllowed("defaultBookmarks") &&
// Default bookmarks are imported after startup, and they may
// influence the outcome of tests, thus it's possible to use
// this test-only pref to skip the import.
!(
Cu.isInAutomation &&
Services.prefs.getBoolPref(
"browser.bookmarks.testing.skipDefaultBookmarksImport",
false
)
)
) {
await lazy.BookmarkHTMLUtils.importFromURL(bookmarksUrl, {
replace: true,
source: lazy.PlacesUtils.bookmarks.SOURCES.RESTORE_ON_STARTUP,

View file

@ -19,5 +19,5 @@ FINAL_TARGET_FILES.actors += [
]
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]

View file

@ -1,13 +0,0 @@
[DEFAULT]
scheme = https
prefs =
identity.fxaccounts.enabled=true
support-files =
aboutlogins_common.js
[test_confirm_delete_dialog.html]
[test_fxaccounts_button.html]
[test_login_filter.html]
[test_login_item.html]
[test_login_list.html]
[test_menu_button.html]

View file

@ -0,0 +1,16 @@
[DEFAULT]
scheme = "https"
prefs = ["identity.fxaccounts.enabled=true"]
support-files = ["aboutlogins_common.js"]
["test_confirm_delete_dialog.html"]
["test_fxaccounts_button.html"]
["test_login_filter.html"]
["test_login_item.html"]
["test_login_list.html"]
["test_menu_button.html"]

View file

@ -161,6 +161,15 @@ export const DoHController = {
lazy.Preferences.observe(NATIVE_FALLBACK_WARNING_HEURISTIC_LIST_PREF, this);
if (lazy.DoHConfigController.currentConfig.enabled) {
// At init time set these heuristics to false if we may run heuristics
for (let key of lazy.Heuristics.Telemetry.heuristicNames()) {
Services.telemetry.keyedScalarSet(
"networking.doh_heuristic_ever_tripped",
key,
false
);
}
await this.maybeEnableHeuristics();
} else if (lazy.Preferences.get(FIRST_RUN_PREF, false)) {
await this.rollback();
@ -216,8 +225,32 @@ export const DoHController = {
let policyResult = await lazy.Heuristics.checkEnterprisePolicy();
if (["policy_without_doh", "disable_doh"].includes(policyResult)) {
await this.setState("policyDisabled");
if (policyResult != "no_policy_set") {
switch (policyResult) {
case "policy_without_doh":
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.enterprisePresent
);
await this.setState("policyDisabled");
break;
case "disable_doh":
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.enterpriseDisabled
);
await this.setState("policyDisabled");
break;
case "enable_doh":
// The TRR mode has already been set, so theoretically we should not get here.
// XXX: should we skip heuristics or continue?
// TODO: Make sure we use the correct URL if the policy defines one.
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.enterpriseEnabled
);
break;
}
lazy.Preferences.set(SKIP_HEURISTICS_PREF, true);
return;
}
@ -326,6 +359,11 @@ export const DoHController = {
async runHeuristics(evaluateReason) {
let start = Date.now();
Services.telemetry.scalarAdd("networking.doh_heuristics_attempts", 1);
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.incomplete
);
let results = await lazy.Heuristics.run();
if (
@ -339,6 +377,10 @@ export const DoHController = {
// during this heuristics run. We simply discard the results in this case.
// Same thing if there was another heuristics run triggered or if we have
// detected a locked captive portal while this one was ongoing.
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.ignored
);
return;
}
@ -382,6 +424,11 @@ export const DoHController = {
this.setHeuristicResult(Ci.nsITRRSkipReason.TRR_UNSET);
if (decision === lazy.Heuristics.DISABLE_DOH) {
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.fromResults(results)
);
let fallbackHeuristicTripped = undefined;
if (lazy.Preferences.get(NATIVE_FALLBACK_WARNING_PREF, false)) {
let heuristics = lazy.Preferences.get(
@ -411,6 +458,11 @@ export const DoHController = {
await this.setState("disabled");
} else {
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.pass
);
Services.telemetry.scalarAdd("networking.doh_heuristics_pass_count", 1);
await this.setState("enabled");
}
@ -440,6 +492,14 @@ export const DoHController = {
} else if (["vpn", "proxy", "nrpt"].includes(heuristicName)) {
platform.push(heuristicName);
}
if (lazy.Heuristics.Telemetry.heuristicNames().includes(heuristicName)) {
Services.telemetry.keyedScalarSet(
"networking.doh_heuristic_ever_tripped",
heuristicName,
true
);
}
}
resultsForTelemetry.canaries = canaries.join(",");
@ -461,9 +521,6 @@ export const DoHController = {
case "disabled":
lazy.Preferences.set(ROLLOUT_MODE_PREF, 0);
break;
case "UIOk":
lazy.Preferences.set(BREADCRUMB_PREF, true);
break;
case "enabled":
lazy.Preferences.set(ROLLOUT_MODE_PREF, 2);
lazy.Preferences.set(BREADCRUMB_PREF, true);
@ -491,6 +548,32 @@ export const DoHController = {
state,
"null"
);
let modePref = lazy.Preferences.get(NETWORK_TRR_MODE_PREF);
if (state == "manuallyDisabled") {
if (
modePref == Ci.nsIDNSService.MODE_TRRFIRST ||
modePref == Ci.nsIDNSService.MODE_TRRONLY
) {
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.manuallyEnabled
);
} else if (
lazy.Preferences.get("doh-rollout.doorhanger-decision", "") ==
"UIDisabled"
) {
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.optOut
);
} else {
Services.telemetry.scalarSet(
"networking.doh_heuristics_result",
lazy.Heuristics.Telemetry.manuallyDisabled
);
}
}
},
async disableHeuristics(state) {

View file

@ -105,6 +105,55 @@ export const Heuristics = {
}
return Ci.nsITRRSkipReason.TRR_FAILED;
},
// Keep this in sync with the description of networking.doh_heuristics_result
// defined in Scalars.yaml
Telemetry: {
incomplete: 0,
pass: 1,
optOut: 2,
manuallyDisabled: 3,
manuallyEnabled: 4,
enterpriseDisabled: 5,
enterprisePresent: 6,
enterpriseEnabled: 7,
vpn: 8,
proxy: 9,
nrpt: 10,
browserParent: 11,
modifiedRoots: 12,
thirdPartyRoots: 13,
google: 14,
youtube: 15,
zscalerCanary: 16,
canary: 17,
ignored: 18,
heuristicNames() {
return [
"google",
"youtube",
"zscalerCanary",
"canary",
"modifiedRoots",
"browserParent",
"thirdPartyRoots",
"policy",
"vpn",
"proxy",
"nrpt",
];
},
fromResults(results) {
for (let label of Heuristics.Telemetry.heuristicNames()) {
if (results[label] == Heuristics.DISABLE_DOH) {
return Heuristics.Telemetry[label];
}
}
return Heuristics.Telemetry.pass;
},
},
};
async function dnsLookup(hostname, resolveCanonicalName = false) {

View file

@ -28,6 +28,14 @@ add_task(async function testDoorhangerUserReject() {
await ensureTRRMode(2);
await checkHeuristicsTelemetry("enable_doh", "startup");
checkScalars([
["networking.doh_heuristics_attempts", { value: 1 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
["networking.doh_heuristics_result", { value: Heuristics.Telemetry.pass }],
// All of the heuristics must be false.
falseExpectations([]),
]);
prefPromise = TestUtils.waitForPrefChange(
prefs.DOORHANGER_USER_DECISION_PREF
);
@ -52,6 +60,17 @@ add_task(async function testDoorhangerUserReject() {
ensureNoHeuristicsTelemetry();
is(Preferences.get(prefs.BREADCRUMB_PREF), undefined, "Breadcrumb cleared.");
checkScalars([
["networking.doh_heuristics_attempts", { value: 1 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.optOut },
],
// All of the heuristics must be false.
falseExpectations([]),
]);
// Simulate a network change.
simulateNetworkChange();
await ensureNoTRRModeChange(undefined);

View file

@ -12,7 +12,6 @@ add_task(setup);
add_task(async function testPlatformIndications() {
// Check if the platform heuristics actually cause a "disable_doh" event
let { MockRegistrar } = ChromeUtils.importESModule(
"resource://testing-common/MockRegistrar.sys.mjs"
);
@ -45,6 +44,14 @@ add_task(async function testPlatformIndications() {
is(Preferences.get(prefs.BREADCRUMB_PREF), true, "Breadcrumb saved.");
await checkHeuristicsTelemetry("enable_doh", "startup");
checkScalars([
["networking.doh_heuristics_attempts", { value: 1 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
["networking.doh_heuristics_result", { value: Heuristics.Telemetry.pass }],
// All of the heuristics must be false.
falseExpectations([]),
]);
await ensureTRRMode(2);
mockedLinkService.platformDNSIndications =
@ -52,22 +59,68 @@ add_task(async function testPlatformIndications() {
simulateNetworkChange();
await ensureTRRMode(0);
await checkHeuristicsTelemetry("disable_doh", "netchange");
checkScalars(
[
["networking.doh_heuristics_attempts", { value: 2 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
["networking.doh_heuristics_result", { value: Heuristics.Telemetry.vpn }],
["networking.doh_heuristic_ever_tripped", { value: true, key: "vpn" }],
].concat(falseExpectations(["vpn"]))
);
mockedLinkService.platformDNSIndications =
Ci.nsINetworkLinkService.PROXY_DETECTED;
simulateNetworkChange();
await ensureNoTRRModeChange(0);
await checkHeuristicsTelemetry("disable_doh", "netchange");
checkScalars(
[
["networking.doh_heuristics_attempts", { value: 3 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.proxy },
],
["networking.doh_heuristic_ever_tripped", { value: true, key: "vpn" }], // Was tripped earlier this session
["networking.doh_heuristic_ever_tripped", { value: true, key: "proxy" }],
].concat(falseExpectations(["vpn", "proxy"]))
);
mockedLinkService.platformDNSIndications =
Ci.nsINetworkLinkService.NRPT_DETECTED;
simulateNetworkChange();
await ensureNoTRRModeChange(0);
await checkHeuristicsTelemetry("disable_doh", "netchange");
checkScalars(
[
["networking.doh_heuristics_attempts", { value: 4 }],
["networking.doh_heuristics_pass_count", { value: 1 }],
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.nrpt },
],
["networking.doh_heuristic_ever_tripped", { value: true, key: "vpn" }], // Was tripped earlier this session
["networking.doh_heuristic_ever_tripped", { value: true, key: "proxy" }], // Was tripped earlier this session
["networking.doh_heuristic_ever_tripped", { value: true, key: "nrpt" }],
].concat(falseExpectations(["vpn", "proxy", "nrpt"]))
);
mockedLinkService.platformDNSIndications =
Ci.nsINetworkLinkService.NONE_DETECTED;
simulateNetworkChange();
await ensureTRRMode(2);
await checkHeuristicsTelemetry("enable_doh", "netchange");
checkScalars(
[
["networking.doh_heuristics_attempts", { value: 5 }],
["networking.doh_heuristics_pass_count", { value: 2 }],
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.pass },
],
["networking.doh_heuristic_ever_tripped", { value: true, key: "vpn" }], // Was tripped earlier this session
["networking.doh_heuristic_ever_tripped", { value: true, key: "proxy" }], // Was tripped earlier this session
["networking.doh_heuristic_ever_tripped", { value: true, key: "nrpt" }], // Was tripped earlier this session
].concat(falseExpectations(["vpn", "proxy", "nrpt"]))
);
});

View file

@ -47,6 +47,16 @@ add_task(async function testPolicyOverride() {
await ensureNoTRRModeChange(undefined);
ensureNoHeuristicsTelemetry();
checkScalars(
[
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.enterprisePresent },
],
// All of the heuristics must be false.
].concat(falseExpectations([]))
);
// Simulate a network change.
simulateNetworkChange();
await ensureNoTRRModeChange(undefined);

View file

@ -83,6 +83,19 @@ add_task(async function testProviderSteering() {
// Set enterprise roots enabled and ensure provider steering is disabled.
Preferences.set("security.enterprise_roots.enabled", true);
await testNetChangeResult(AUTO_TRR_URI, "disable_doh");
checkScalars(
[
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.modifiedRoots },
],
[
"networking.doh_heuristic_ever_tripped",
{ value: true, key: "modifiedRoots" },
],
// All of the other heuristics must be false.
].concat(falseExpectations(["modifiedRoots"]))
);
Preferences.reset("security.enterprise_roots.enabled");
// Check that provider steering is enabled again after we reset above.
@ -97,6 +110,20 @@ add_task(async function testProviderSteering() {
await testNetChangeResult(AUTO_TRR_URI, "disable_doh");
gDNSOverride.clearHostOverride(googleDomain);
gDNSOverride.addIPOverride(googleDomain, googleIP);
checkScalars(
[
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.google },
],
["networking.doh_heuristic_ever_tripped", { value: true, key: "google" }],
[
"networking.doh_heuristic_ever_tripped",
{ value: true, key: "modifiedRoots" },
],
// All of the other heuristics must be false.
].concat(falseExpectations(["modifiedRoots", "google"]))
);
// Check that provider steering is enabled again after we reset above.
await testNetChangeResult(provider.uri, "enable_doh", provider.id);
@ -104,4 +131,19 @@ add_task(async function testProviderSteering() {
// Finally, provider steering should be disabled once we clear the override.
gDNSOverride.clearHostOverride(TEST_DOMAIN);
await testNetChangeResult(AUTO_TRR_URI, "enable_doh");
checkScalars(
[
[
"networking.doh_heuristics_result",
{ value: Heuristics.Telemetry.pass },
],
["networking.doh_heuristic_ever_tripped", { value: true, key: "google" }],
[
"networking.doh_heuristic_ever_tripped",
{ value: true, key: "modifiedRoots" },
],
// All of the other heuristics must be false.
].concat(falseExpectations(["modifiedRoots", "google"]))
);
});

View file

@ -4,9 +4,11 @@ ChromeUtils.defineESModuleGetters(this, {
DoHConfigController: "resource:///modules/DoHConfig.sys.mjs",
DoHController: "resource:///modules/DoHController.sys.mjs",
DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs",
Heuristics: "resource:///modules/DoHHeuristics.sys.mjs",
Preferences: "resource://gre/modules/Preferences.sys.mjs",
Region: "resource://gre/modules/Region.sys.mjs",
RegionTestUtils: "resource://testing-common/RegionTestUtils.sys.mjs",
TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
RemoteSettings: "resource://services-settings/remote-settings.sys.mjs",
});
@ -70,6 +72,7 @@ async function setup() {
let oldCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
Services.telemetry.clearEvents();
Services.telemetry.clearScalars();
// Enable the CFR.
Preferences.set(CFR_PREF, JSON.stringify(CFR_JSON));
@ -225,6 +228,49 @@ async function checkHeuristicsTelemetry(
Services.telemetry.clearEvents();
}
// Generates an array of expectations for the ever_tripped scalar
// containing false and key, except for the keyes contained in
// the `except` parameter.
function falseExpectations(except) {
return Heuristics.Telemetry.heuristicNames()
.map(e => [
"networking.doh_heuristic_ever_tripped",
{ value: false, key: e },
])
.filter(e => except && !except.includes(e[1].key));
}
function checkScalars(expectations) {
// expectations: [[scalarname: expectationObject]]
// expectationObject: {value, key}
let snapshot = TelemetryTestUtils.getProcessScalars("parent", false, false);
let keyedSnapshot = TelemetryTestUtils.getProcessScalars(
"parent",
true,
false
);
for (let ex of expectations) {
let scalarName = ex[0];
let exObject = ex[1];
if (exObject.key) {
TelemetryTestUtils.assertKeyedScalar(
keyedSnapshot,
scalarName,
exObject.key,
exObject.value,
`${scalarName} expected to have ${exObject.value}, key: ${exObject.key}`
);
} else {
TelemetryTestUtils.assertScalar(
snapshot,
scalarName,
exObject.value,
`${scalarName} expected to have ${exObject.value}`
);
}
}
}
async function checkHeuristicsTelemetryMultiple(expectedEvaluateReasons) {
let events;
await TestUtils.waitForCondition(() => {

View file

@ -61,11 +61,11 @@ class CardContainer extends MozLitElement {
if (this.preserveCollapseState) {
Services.prefs.setBoolPref(this.openStatePref, this.isExpanded);
}
if (!this.isExpanded && this.shortPageName) {
// Record telemetry
// Record telemetry
if (this.shortPageName) {
Services.telemetry.recordEvent(
"firefoxview_next",
"card_collapsed",
this.isExpanded ? "card_expanded" : "card_collapsed",
"card_container",
null,
{
@ -75,6 +75,15 @@ class CardContainer extends MozLitElement {
}
}
viewAllClicked() {
this.dispatchEvent(
new CustomEvent("card-container-view-all", {
bubbles: true,
composed: true,
})
);
}
render() {
return html`
<link
@ -111,6 +120,7 @@ class CardContainer extends MozLitElement {
</summary>
<a
href="about:firefoxview-next#${this.shortPageName}"
@click=${this.viewAllClicked}
class="view-all-link"
data-l10n-id="firefoxview-view-all-link"
?hidden=${!this.showViewAll}

View file

@ -180,7 +180,7 @@ export const SyncedTabsErrorHandler = {
},
[ErrorType.SIGNED_OUT]: {
header: "firefoxview-tabpickup-signed-out-header",
description: "firefoxview-tabpickup-signed-out-description",
description: "firefoxview-tabpickup-signed-out-description2",
buttonLabel: "firefoxview-tabpickup-signed-out-primarybutton",
},
},

View file

@ -22,7 +22,15 @@ function changePage(page) {
);
(currentCategoryButton || navigation.categoryButtons[0]).focus();
}
}
function recordTelemetry(source, eventTarget) {
let page = "recentbrowsing";
if (source === "category-navigation") {
page = eventTarget.parentNode.currentCategory;
} else if (source === "view-all") {
page = eventTarget.shortPageName;
}
// Record telemetry
Services.telemetry.recordEvent(
"firefoxview_next",
@ -30,7 +38,8 @@ function changePage(page) {
"navigation",
null,
{
page: navigation.currentCategory,
page,
source,
}
);
}
@ -44,6 +53,10 @@ window.addEventListener("DOMContentLoaded", async () => {
window.addEventListener("hashchange", onHashChange);
window.addEventListener("change-category", function (event) {
location.hash = event.target.getAttribute("name");
recordTelemetry("category-navigation", event.target);
});
window.addEventListener("card-container-view-all", function (event) {
recordTelemetry("view-all", event.originalTarget);
});
if (document.location.hash) {
changePage(document.location.hash.substring(1));

View file

@ -105,6 +105,7 @@ class HistoryInView extends ViewPage {
migrationWizardDialog: "#migrationWizardDialog",
emptyState: "fxview-empty-state",
lists: { all: "fxview-tab-list" },
panelList: "panel-list",
};
static properties = {
@ -210,6 +211,7 @@ class HistoryInView extends ViewPage {
deleteFromHistory(e) {
lazy.PlacesUtils.history.remove(this.triggerNode.url);
this.recordContextMenuTelemetry("delete-from-history", e);
}
async onChangeSortOption(e) {
@ -268,7 +270,7 @@ class HistoryInView extends ViewPage {
panelListTemplate() {
return html`
<panel-list slot="menu">
<panel-list slot="menu" data-tab-type="history">
<panel-item
@click=${this.deleteFromHistory}
data-l10n-id="firefoxview-history-context-delete"

View file

@ -22,4 +22,4 @@ BROWSER_CHROME_MANIFESTS += [
"tests/browser/firefoxview-next/browser.ini",
]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]

View file

@ -257,18 +257,21 @@ class OpenTabsInViewCard extends ViewPage {
}
static queries = {
cardEl: "card-container",
panelList: "panel-list",
tabList: "fxview-tab-list",
};
closeTab() {
closeTab(e) {
const tab = this.triggerNode.tabElement;
const browserWindow = tab.ownerGlobal;
browserWindow.gBrowser.removeTab(tab, { animate: true });
this.recordContextMenuTelemetry("close-tab", e);
}
panelListTemplate() {
return html`
<panel-list slot="menu">
<panel-list slot="menu" data-tab-type="opentabs">
<panel-item
data-l10n-id="fxviewtabrow-close-tab"
data-l10n-attrs="accesskey"

View file

@ -269,7 +269,7 @@ class SyncedTabsInView extends ViewPage {
panelListTemplate() {
return html`
<panel-list slot="menu">
<panel-list slot="menu" data-tab-type="syncedtabs">
<panel-item
@click=${this.openInNewWindow}
data-l10n-id="fxviewtabrow-open-in-window"

View file

@ -2,6 +2,7 @@
support-files = ../head.js
[browser_firefoxview_next.js]
[browser_firefoxview_next_general_telemetry.js]
[browser_history_firefoxview_next.js]
[browser_recentlyclosed_firefoxview_next.js]
[browser_syncedtabs_errors_firefoxview_next.js]

View file

@ -0,0 +1,334 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/* import-globals-from ../head.js */
const FXVIEW_NEXT_ENABLED_PREF = "browser.tabs.firefox-view-next";
const CARD_COLLAPSED_EVENT = [
["firefoxview_next", "card_collapsed", "card_container", undefined],
];
const CARD_EXPANDED_EVENT = [
["firefoxview_next", "card_expanded", "card_container", undefined],
];
async function cardCollapsedTelemetry() {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for card_collapsed firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
CARD_COLLAPSED_EVENT,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
async function cardExpandedTelemetry() {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for card_expanded firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
CARD_EXPANDED_EVENT,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
async function navigationTelemetry(changePageEvent) {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for change_page firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
changePageEvent,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
async function contextMenuTelemetry(contextMenuEvent) {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for context_menu firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
contextMenuEvent,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
add_setup(async () => {
await SpecialPowers.pushPrefEnv({ set: [[FXVIEW_NEXT_ENABLED_PREF, true]] });
registerCleanupFunction(async () => {
await SpecialPowers.popPrefEnv();
clearHistory();
});
});
add_task(async function test_collapse_and_expand_card() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
// Test using Recently Closed card on Recent Browsing page
let recentlyClosedComponent = document.querySelector(
"view-recentlyclosed[slot=recentlyclosed]"
);
let cardContainer = recentlyClosedComponent.cardEl;
is(
cardContainer.isExpanded,
true,
"The card-container is expanded initially"
);
await clearAllParentTelemetryEvents();
// Click the summary to collapse the details disclosure
await EventUtils.synthesizeMouseAtCenter(
cardContainer.summaryEl,
{},
content
);
is(
cardContainer.detailsEl.hasAttribute("open"),
false,
"The card-container is collapsed"
);
await cardCollapsedTelemetry();
// Click the summary again to expand the details disclosure
await EventUtils.synthesizeMouseAtCenter(
cardContainer.summaryEl,
{},
content
);
is(
cardContainer.detailsEl.hasAttribute("open"),
true,
"The card-container is expanded"
);
await cardExpandedTelemetry();
});
});
add_task(async function test_change_page_telemetry() {
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
let changePageEvent = [
[
"firefoxview_next",
"change_page",
"navigation",
undefined,
{ page: "recentlyclosed", source: "category-navigation" },
],
];
await clearAllParentTelemetryEvents();
navigateToCategory(document, "recentlyclosed");
await navigationTelemetry(changePageEvent);
navigateToCategory(document, "recentbrowsing");
let openTabsComponent = document.querySelector(
"view-opentabs[slot=opentabs]"
);
let cardContainer =
openTabsComponent.shadowRoot.querySelector("view-opentabs-card").cardEl;
let viewAllLink = cardContainer.viewAllLink;
changePageEvent = [
[
"firefoxview_next",
"change_page",
"navigation",
undefined,
{ page: "opentabs", source: "view-all" },
],
];
await clearAllParentTelemetryEvents();
await EventUtils.synthesizeMouseAtCenter(viewAllLink, {}, content);
await navigationTelemetry(changePageEvent);
});
});
add_task(async function test_context_menu_telemetry() {
await PlacesUtils.history.insert({
url: URLs[0],
title: "Example Domain 1",
visits: [{ date: new Date() }],
});
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
// Test open tabs telemetry
let openTabsComponent = document.querySelector(
"view-opentabs[slot=opentabs]"
);
let tabList =
openTabsComponent.shadowRoot.querySelector("view-opentabs-card").tabList;
let firstItem = tabList.rowEls[0];
let panelList =
openTabsComponent.shadowRoot.querySelector(
"view-opentabs-card"
).panelList;
await EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let copyLinkOption = panelList.children[1];
let contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
"tabs",
undefined,
{ menu_action: "copy-link", data_type: "opentabs" },
],
];
await EventUtils.synthesizeMouseAtCenter(copyLinkOption, {}, content);
await contextMenuTelemetry(contextMenuEvent);
// Open new tab to test 'Close tab' menu option
window.openTrustedLinkIn("about:robots", "tab");
await switchToFxViewTab(browser.ownerGlobal);
firstItem = tabList.rowEls[0];
await EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let closeTabOption = panelList.children[0];
contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
"tabs",
undefined,
{ menu_action: "close-tab", data_type: "opentabs" },
],
];
await EventUtils.synthesizeMouseAtCenter(closeTabOption, {}, content);
await contextMenuTelemetry(contextMenuEvent);
// Test history context menu options
navigateToCategory(document, "history");
let historyComponent = document.querySelector("view-history");
await TestUtils.waitForCondition(() => historyComponent.fullyUpdated);
let firstTabList = historyComponent.lists[0];
firstItem = firstTabList.rowEls[0];
panelList = historyComponent.panelList;
await EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let panelItems = Array.from(panelList.children).filter(
panelItem => panelItem.nodeName === "PANEL-ITEM"
);
let openInNewWindowOption = panelItems[1];
contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
"tabs",
undefined,
{ menu_action: "open-in-new-window", data_type: "history" },
],
];
let newWindowPromise = BrowserTestUtils.waitForNewWindow({
url: URLs[0],
});
await EventUtils.synthesizeMouseAtCenter(
openInNewWindowOption,
{},
content
);
let win = await newWindowPromise;
await contextMenuTelemetry(contextMenuEvent);
await BrowserTestUtils.closeWindow(win);
await EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let openInPrivateWindowOption = panelItems[2];
contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
"tabs",
undefined,
{ menu_action: "open-in-private-window", data_type: "history" },
],
];
newWindowPromise = BrowserTestUtils.waitForNewWindow({
url: URLs[0],
});
await EventUtils.synthesizeMouseAtCenter(
openInPrivateWindowOption,
{},
content
);
win = await newWindowPromise;
await contextMenuTelemetry(contextMenuEvent);
ok(
PrivateBrowsingUtils.isWindowPrivate(win),
"Should have opened a private window."
);
await BrowserTestUtils.closeWindow(win);
await EventUtils.synthesizeMouseAtCenter(firstItem.buttonEl, {}, content);
await BrowserTestUtils.waitForEvent(panelList, "shown");
await clearAllParentTelemetryEvents();
let deleteFromHistoryOption = panelItems[0];
contextMenuEvent = [
[
"firefoxview_next",
"context_menu",
"tabs",
undefined,
{ menu_action: "delete-from-history", data_type: "history" },
],
];
await EventUtils.synthesizeMouseAtCenter(
deleteFromHistoryOption,
{},
content
);
await contextMenuTelemetry(contextMenuEvent);
// clean up extra tabs
while (gBrowser.tabs.length > 1) {
BrowserTestUtils.removeTab(gBrowser.tabs.at(-1));
}
});
});

View file

@ -47,17 +47,6 @@ async function openFirefoxView(win) {
);
}
function navigateToHistory(document) {
// Navigate to Recently closed tabs page/view
const navigation = document.querySelector("fxview-category-navigation");
let historyNavButton = Array.from(navigation.categoryButtons).find(
categoryButton => {
return categoryButton.name === "history";
}
);
historyNavButton.buttonEl.click();
}
async function addHistoryItems(dateAdded) {
await PlacesUtils.history.insert({
url: URLs[0],
@ -101,7 +90,7 @@ add_task(async function test_list_ordering() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
navigateToHistory(document);
navigateToCategory(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@ -158,7 +147,7 @@ add_task(async function test_empty_states() {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
navigateToHistory(document);
navigateToCategory(document, "history");
let historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
@ -246,7 +235,7 @@ add_task(async function test_observers_removed_when_view_is_hidden() {
);
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
navigateToHistory(document);
navigateToCategory(document, "history");
const historyComponent = document.querySelector("view-history");
historyComponent.profileAge = 8;
const visitList = await TestUtils.waitForCondition(() =>

View file

@ -15,12 +15,6 @@ const RECENTLY_CLOSED_EVENT = [
const DISMISS_CLOSED_TAB_EVENT = [
["firefoxview_next", "dismiss_closed_tab", "tabs", undefined],
];
const CHANGE_PAGE_EVENT = [
["firefoxview_next", "change_page", "navigation", undefined],
];
const CARD_COLLAPSED_EVENT = [
["firefoxview_next", "card_collapsed", "card_container", undefined],
];
const initialTab = gBrowser.selectedTab;
function isElInViewport(element) {
@ -248,48 +242,6 @@ async function recentlyClosedDismissTelemetry() {
);
}
async function navigationTelemetry() {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for change_page firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
CHANGE_PAGE_EVENT,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
async function cardCollapsedTelemetry() {
await TestUtils.waitForCondition(
() => {
let events = Services.telemetry.snapshotEvents(
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
false
).parent;
return events && events.length >= 1;
},
"Waiting for card_collapsed firefoxview_next telemetry event.",
200,
100
);
TelemetryTestUtils.assertEvents(
CARD_COLLAPSED_EVENT,
{ category: "firefoxview_next" },
{ clear: true, process: "parent" }
);
}
add_setup(async () => {
await SpecialPowers.pushPrefEnv({ set: [[FXVIEW_NEXT_ENABLED_PREF, true]] });
registerCleanupFunction(async () => {
@ -309,7 +261,6 @@ add_task(async function test_list_ordering() {
is(document.location.href, "about:firefoxview-next");
await clearAllParentTelemetryEvents();
navigateToCategory(document, "recentlyclosed");
await navigationTelemetry();
let [cardMainSlotNode, listItems] = await waitForRecentlyClosedTabsList(
document
);
@ -329,49 +280,6 @@ add_task(async function test_list_ordering() {
await cleanup();
});
add_task(async function test_collapse_card() {
const { cleanup } = await prepareSingleClosedTab();
await withFirefoxView({}, async browser => {
const { document } = browser.contentWindow;
is(document.location.href, "about:firefoxview-next");
let recentlyClosedComponent = document.querySelector(
"view-recentlyclosed[slot=recentlyclosed]"
);
let cardContainer = recentlyClosedComponent.cardEl;
is(
cardContainer.isExpanded,
true,
"The card-container is expanded initially"
);
await clearAllParentTelemetryEvents();
// Click the summary to collapse the details disclosure
await EventUtils.synthesizeMouseAtCenter(
cardContainer.summaryEl,
{},
content
);
is(
cardContainer.detailsEl.hasAttribute("open"),
false,
"The card-container is collapsed"
);
await cardCollapsedTelemetry();
// Click the summary again to expand the details disclosure
await EventUtils.synthesizeMouseAtCenter(
cardContainer.summaryEl,
{},
content
);
is(
cardContainer.detailsEl.hasAttribute("open"),
true,
"The card-container is expanded"
);
});
await cleanup();
});
/**
* Asserts that an out-of-band update to recently-closed tabs results in the correct update to the tab list
*/

View file

@ -1,6 +0,0 @@
[DEFAULT]
[test_card_container.html]
[test_fxview_category_navigation.html]
[test_fxview_tab_list.html]
[test_opentabs.html]

View file

@ -0,0 +1,9 @@
[DEFAULT]
["test_card_container.html"]
["test_fxview_category_navigation.html"]
["test_fxview_tab_list.html"]
["test_opentabs.html"]

View file

@ -35,7 +35,7 @@
}
function getRowsForCard(card) {
return card.shadowRoot.querySelector("fxview-tab-list").rowEls;
return card.tabList.rowEls;
}
add_setup(async function () {

View file

@ -121,17 +121,33 @@ export class ViewPage extends MozLitElement {
null,
Ci.nsIClipboard.kGlobalClipboard
);
this.recordContextMenuTelemetry("copy-link", e);
}
openInNewWindow(e) {
this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
private: false,
});
this.recordContextMenuTelemetry("open-in-new-window", e);
}
openInNewPrivateWindow(e) {
this.getWindow().openTrustedLinkIn(this.triggerNode.url, "window", {
private: true,
});
this.recordContextMenuTelemetry("open-in-private-window", e);
}
recordContextMenuTelemetry(menuAction, event) {
Services.telemetry.recordEvent(
"firefoxview_next",
"context_menu",
"tabs",
null,
{
menu_action: menuAction,
data_type: event.target.panel.dataset.tabType,
}
);
}
}

View file

@ -147,6 +147,7 @@ class MigrationUtils {
"MigrationWizard:BeginMigration": { wantUntrusted: true },
"MigrationWizard:RequestSafariPermissions": { wantUntrusted: true },
"MigrationWizard:SelectSafariPasswordFile": { wantUntrusted: true },
"MigrationWizard:OpenAboutAddons": { wantUntrusted: true },
},
},

View file

@ -148,6 +148,11 @@ export class MigrationWizardChild extends JSWindowActorChild {
}
break;
}
case "MigrationWizard:OpenAboutAddons": {
this.sendAsyncMessage("OpenAboutAddons");
break;
}
}
}

View file

@ -161,6 +161,12 @@ export class MigrationWizardParent extends JSWindowActorParent {
this.#recordEvent(message.data.type, message.data.args);
break;
}
case "OpenAboutAddons": {
let browser = this.browsingContext.top.embedderElement;
this.#openAboutAddons(browser);
break;
}
}
return null;
@ -769,4 +775,16 @@ export class MigrationWizardParent extends JSWindowActorParent {
resourceTypes: [],
};
}
/**
* Opens the about:addons page in a new background tab in the same window
* as the passed browser.
*
* @param {Element} browser
* The browser element requesting that about:addons opens.
*/
#openAboutAddons(browser) {
let window = browser.ownerGlobal;
window.openTrustedLinkIn("about:addons", "tab", { inBackground: true });
}
}

View file

@ -29,6 +29,7 @@ export class MigrationWizard extends HTMLElement {
#selectAllCheckbox = null;
#resourceSummary = null;
#expandedDetails = false;
#extensionsSuccessLink = null;
static get markup() {
return `
@ -133,7 +134,7 @@ export class MigrationWizard extends HTMLElement {
<div data-resource-type="EXTENSIONS" class="resource-progress-group">
<span class="progress-icon-parent"><span class="progress-icon" role="img"></span></span>
<span data-l10n-id="migration-extensions-option-label"></span>
<a class="message-text deemphasized-text"></a>
<a id="extensions-success-link" href="about:addons" class="message-text deemphasized-text"></a>
<span class="message-text deemphasized-text"></span>
<a class="support-text deemphasized-text"></a>
</div>
@ -333,6 +334,11 @@ export class MigrationWizard extends HTMLElement {
);
this.#safariPasswordImportSelectButton.addEventListener("click", this);
this.#extensionsSuccessLink = shadow.querySelector(
"#extensions-success-link"
);
this.#extensionsSuccessLink.addEventListener("click", this);
this.#shadowRoot = shadow;
}
@ -649,6 +655,8 @@ export class MigrationWizard extends HTMLElement {
let resourceGroups = progressPage.querySelectorAll(
".resource-progress-group"
);
this.#extensionsSuccessLink.textContent = "";
let totalProgressGroups = Object.keys(state.progress).length;
let remainingProgressGroups = totalProgressGroups;
let totalWarnings = 0;
@ -663,7 +671,6 @@ export class MigrationWizard extends HTMLElement {
let progressIcon = group.querySelector(".progress-icon");
let messageText = group.querySelector("span.message-text");
let extensionsSuccessLink = group.querySelector("a.message-text");
let supportLink = group.querySelector(".support-text");
let labelSpan = group.querySelector("span[default-data-l10n-id]");
@ -681,10 +688,7 @@ export class MigrationWizard extends HTMLElement {
}
}
messageText.textContent = "";
if (extensionsSuccessLink) {
extensionsSuccessLink.textContent = "";
extensionsSuccessLink.removeAttribute("href");
}
if (supportLink) {
supportLink.textContent = "";
supportLink.removeAttribute("href");
@ -698,10 +702,6 @@ export class MigrationWizard extends HTMLElement {
);
progressIcon.setAttribute("state", "loading");
messageText.textContent = "";
if (extensionsSuccessLink) {
extensionsSuccessLink.textContent = "";
extensionsSuccessLink.removeAttribute("href");
}
supportLink.textContent = "";
supportLink.removeAttribute("href");
// With no status text, we re-insert the &nbsp; so that the status
@ -721,8 +721,7 @@ export class MigrationWizard extends HTMLElement {
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
) {
messageText.textContent = "";
extensionsSuccessLink.href = "about:addons";
extensionsSuccessLink.textContent =
this.#extensionsSuccessLink.textContent =
state.progress[resourceType].message;
}
remainingProgressGroups--;
@ -755,8 +754,7 @@ export class MigrationWizard extends HTMLElement {
MigrationWizardConstants.DISPLAYED_RESOURCE_TYPES.EXTENSIONS
) {
messageText.textContent = "";
extensionsSuccessLink.href = "about:addons";
extensionsSuccessLink.textContent =
this.#extensionsSuccessLink.textContent =
state.progress[resourceType].message;
}
remainingProgressGroups--;
@ -1263,6 +1261,13 @@ export class MigrationWizard extends HTMLElement {
}
} else if (event.target == this.#safariPasswordImportSelectButton) {
this.#selectSafariPasswordFile();
} else if (event.target == this.#extensionsSuccessLink) {
this.dispatchEvent(
new CustomEvent("MigrationWizard:OpenAboutAddons", {
bubbles: true,
})
);
event.preventDefault();
}
break;
}

View file

@ -8,7 +8,7 @@ XPCSHELL_TESTS_MANIFESTS += ["tests/unit/xpcshell.ini"]
MARIONETTE_UNIT_MANIFESTS += ["tests/marionette/manifest.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.ini"]

View file

@ -29,8 +29,9 @@ let gFluentStrings = new Localization([
* @param {string} description.linkText
* The text content for the <a> element that should be displayed to the user
* for the particular state.
* @returns {Promise<undefined>}
*/
function assertExtensionsProgressState(wizard, state, description) {
async function assertExtensionsProgressState(wizard, state, description) {
let shadow = wizard.openOrClosedShadowRoot;
// Make sure that we're showing the progress page first.
@ -47,31 +48,52 @@ function assertExtensionsProgressState(wizard, state, description) {
let progressIcon = progressGroup.querySelector(".progress-icon");
let messageText = progressGroup.querySelector("span.message-text");
let supportLink = progressGroup.querySelector(".support-text");
let extensionsSuccessLink = progressGroup.querySelector("a.message-text");
let extensionsSuccessLink = progressGroup.querySelector(
"#extensions-success-link"
);
if (state == MigrationWizardConstants.PROGRESS_VALUE.SUCCESS) {
Assert.stringMatches(progressIcon.getAttribute("state"), "success");
Assert.stringMatches(messageText.textContent, "");
Assert.stringMatches(extensionsSuccessLink.href, "about:addons");
Assert.stringMatches(
extensionsSuccessLink.textContent,
description.message
);
Assert.stringMatches(supportLink.textContent, "");
await assertSuccessLink(extensionsSuccessLink, description.message);
} else if (state == MigrationWizardConstants.PROGRESS_VALUE.WARNING) {
Assert.stringMatches(progressIcon.getAttribute("state"), "warning");
Assert.stringMatches(messageText.textContent, description.message);
Assert.stringMatches(supportLink.textContent, description.linkText);
Assert.stringMatches(supportLink.href, description.linkURL);
await assertSuccessLink(extensionsSuccessLink, "");
} else if (state == MigrationWizardConstants.PROGRESS_VALUE.INFO) {
Assert.stringMatches(progressIcon.getAttribute("state"), "info");
Assert.stringMatches(extensionsSuccessLink.href, description.linkURL);
Assert.stringMatches(
extensionsSuccessLink.textContent,
description.message
);
Assert.stringMatches(supportLink.textContent, "");
await assertSuccessLink(extensionsSuccessLink, description.message);
}
}
/**
* Checks that the extensions migration success link has the right
* text content, and if the text content is non-blank, ensures that
* clicking on the link opens up about:addons in a background tab.
*
* The about:addons tab will be automatically closed before proceeding.
*
* @param {Element} link
* The extensions migration success link element.
* @param {string} message
* The expected string to appear in the link textContent. If the
* link is not expected to appear, this should be the empty string.
* @returns {Promise<undefined>}
*/
async function assertSuccessLink(link, message) {
Assert.stringMatches(link.textContent, message);
if (message) {
let aboutAddonsOpened = BrowserTestUtils.waitForNewTab(
gBrowser,
"about:addons"
);
EventUtils.synthesizeMouseAtCenter(link, {}, link.ownerGlobal);
let tab = await aboutAddonsOpened;
BrowserTestUtils.removeTab(tab);
}
}
@ -101,7 +123,7 @@ add_task(async function test_extension_migration_no_matched_extensions() {
]);
await migration;
await wizardDone;
assertExtensionsProgressState(
await assertExtensionsProgressState(
wizard,
MigrationWizardConstants.PROGRESS_VALUE.WARNING,
{
@ -149,7 +171,7 @@ add_task(
]);
await migration;
await wizardDone;
assertExtensionsProgressState(
await assertExtensionsProgressState(
wizard,
MigrationWizardConstants.PROGRESS_VALUE.INFO,
{
@ -160,7 +182,6 @@ add_task(
quantity: TOTAL_EXTENSIONS,
}
),
linkURL: "about:addons",
linkText: await gFluentStrings.formatValue(
"migration-wizard-progress-extensions-support-link"
),
@ -199,7 +220,7 @@ add_task(async function test_extension_migration_fully_matched_extensions() {
]);
await migration;
await wizardDone;
assertExtensionsProgressState(
await assertExtensionsProgressState(
wizard,
MigrationWizardConstants.PROGRESS_VALUE.SUCCESS,
{

View file

@ -1,6 +0,0 @@
[DEFAULT]
skip-if = os == 'android'
support-files =
../head-common.js
[test_migration_wizard.html]

View file

@ -0,0 +1,5 @@
[DEFAULT]
skip-if = ["os == 'android'"]
support-files = ["../head-common.js"]
["test_migration_wizard.html"]

View file

@ -592,33 +592,20 @@ XPCOMUtils.defineLazyPreferenceGetter(
0
);
XPCOMUtils.defineLazyPreferenceGetter(
lazy,
"optedIn",
"browser.shopping.experience2023.optedIn",
0
);
let optInDynamicContent;
// Limit pref increase to 5 as we don't need to count any higher than that
const MIN_VISITS_TO_SHOW_SURVEY = 5;
class AboutWelcomeShoppingChild extends AboutWelcomeChild {
// Static state used to track session in which user opted-in
static optedInSession = false;
actorCreated() {
super.actorCreated();
this.init();
}
// Static used to track PDP visits per session for showing survey
static eligiblePDPvisits = [];
init() {
// init called on Update Event when child actor created on PDP page open
if (!lazy.optedIn) {
return;
}
// Limit pref increase to 5 as we don't need to count any higher than that
if (lazy.pdpVisits < 5) {
computeEligiblePDPCount(data) {
// Increment our pref if this isn't a page we've already seen this session
if (lazy.pdpVisits < MIN_VISITS_TO_SHOW_SURVEY) {
this.AWSendToParent("SPECIAL_ACTION", {
type: "SET_PREF",
data: {
@ -630,48 +617,62 @@ class AboutWelcomeShoppingChild extends AboutWelcomeChild {
});
}
// Show micro survey to opted-in users at least 24 hours after theyve opted in,
// on the next session, and after 5 PDP visits
// Set `this.showMicroSurvey` when above states are met
// TBD: Wait 24 hrs after opt-in , check if existing shopping logic has when opted-in
// else use optInTime pref set when user click opt-in message primary CTA
// Add this product to our list of unique eligible PDPs visited
// to prevent errors caused by multiple events being fired simultaneously
AboutWelcomeShoppingChild.eligiblePDPvisits.push(data?.product_id);
}
evaluateAndShowSurvey() {
// Re-evaluate if we should show the survey
// Render survey if user is opted-in and has met survey seen conditions
this.showMicroSurvey =
!lazy.isSurveySeen &&
!AboutWelcomeShoppingChild.optedInSession &&
lazy.pdpVisits >= MIN_VISITS_TO_SHOW_SURVEY;
if (this.showMicroSurvey) {
this.renderMessage();
}
}
handleEvent(event) {
// Decide when to show/hide onboarding and survey message
const { productUrl, showOnboarding } = event.detail;
const { productUrl, showOnboarding, data } = event.detail;
// Display onboarding if a user hasn't opted-in
const optInReady = showOnboarding && productUrl;
// hide the message root if we shouldn't show the opt in card
// and if we shouldn't show a microsurvey
this.document.getElementById("multi-stage-message-root").hidden =
!optInReady && !this.showMicroSurvey;
// Render survey if user is opted-in and has met survey seen conditions
if (!showOnboarding && productUrl && this.showMicroSurvey) {
if (optInReady) {
// Render opt-in message
AboutWelcomeShoppingChild.optedInSession = true;
this.AWSetProductURL(new URL(productUrl).hostname);
this.renderMessage();
return;
}
if (!optInReady) {
// Hide the container until the user is eligible to see the survey
if (!lazy.isSurveySeen) {
this.document.getElementById("multi-stage-message-root").hidden = true;
}
// Early exit if user has seen survey, if we have no data,
// or if pdp is ineligible or not unique
if (
lazy.isSurveySeen ||
!data ||
!productUrl ||
(data?.needs_analysis &&
(!data?.product_id || !data?.grade || !data?.adjusted_rating)) ||
AboutWelcomeShoppingChild.eligiblePDPvisits.includes(data?.product_id)
) {
return;
}
// Render opt-in message
AboutWelcomeShoppingChild.optedInSession = true;
this.AWSetProductURL(new URL(productUrl).hostname);
this.renderMessage();
this.computeEligiblePDPCount(data, productUrl);
this.evaluateAndShowSurvey();
}
renderMessage() {
this.document.getElementById("multi-stage-message-root").hidden = false;
this.document.dispatchEvent(
new this.contentWindow.CustomEvent("RenderWelcome", {
bubbles: true,
@ -679,6 +680,16 @@ class AboutWelcomeShoppingChild extends AboutWelcomeChild {
);
}
// TODO - Move messages into an ASRouter message provider. See bug 1848251.
AWGetFeatureConfig() {
let messageContent = optInDynamicContent;
if (this.showMicroSurvey) {
messageContent = SHOPPING_MICROSURVEY;
this.setShoppingSurveySeen();
}
return Cu.cloneInto(messageContent, this.contentWindow);
}
setShoppingSurveySeen() {
this.AWSendToParent("SPECIAL_ACTION", {
type: "SET_PREF",
@ -750,15 +761,5 @@ class AboutWelcomeShoppingChild extends AboutWelcomeChild {
optInDynamicContent = content;
}
// TODO - Move messages into an ASRouter message provider. See bug 1848251.
AWGetFeatureConfig() {
let messageContent = optInDynamicContent;
if (this.showMicroSurvey) {
messageContent = SHOPPING_MICROSURVEY;
this.setShoppingSurveySeen();
}
return Cu.cloneInto(messageContent, this.contentWindow);
}
AWEnsureLangPackInstalled() {}
}

View file

@ -7,7 +7,7 @@
XPCSHELL_TESTS_MANIFESTS += [
"tests/unit/xpcshell.ini",
]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.ini"]
MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
BROWSER_CHROME_MANIFESTS += [
"tests/browser/browser.ini",
"tests/browser/interactions/browser.ini",

View file

@ -1,10 +0,0 @@
[DEFAULT]
support-files = head.js
[test_0_bug510634.xhtml]
[test_bug1163447_selectItems_through_shortcut.xhtml]
skip-if = (os == 'win' && processor == 'aarch64') # bug 1532775
[test_bug549192.xhtml]
[test_bug549491.xhtml]
[test_selectItems_on_nested_tree.xhtml]
[test_treeview_date.xhtml]

View file

@ -0,0 +1,17 @@
[DEFAULT]
support-files = ["head.js"]
["test_0_bug510634.xhtml"]
["test_bug1163447_selectItems_through_shortcut.xhtml"]
skip-if = [
"(os == 'win' && processor == 'aarch64')", # bug 1532775
]
["test_bug549192.xhtml"]
["test_bug549491.xhtml"]
["test_selectItems_on_nested_tree.xhtml"]
["test_treeview_date.xhtml"]

View file

@ -427,7 +427,7 @@ class TelemetryHandler {
adsReported: false,
adImpressionsReported: false,
impressionId,
hrefToComponentMap: null,
urlToComponentMap: null,
impressionInfo,
searchBoxSubmitted: false,
});
@ -440,7 +440,7 @@ class TelemetryHandler {
adsReported: false,
adImpressionsReported: false,
impressionId,
hrefToComponentMap: null,
urlToComponentMap: null,
impressionInfo,
searchBoxSubmitted: false,
}),
@ -482,6 +482,71 @@ class TelemetryHandler {
}
}
/**
* Calculate how close two urls are in equality.
*
* The scoring system:
* - If the URLs look exactly the same, including the ordering of query
* parameters, the score is Infinity.
* - If the origin is the same, the score is increased by 1. Otherwise the
* score is 0.
* - If the path is the same, the score is increased by 1.
* - For each query parameter, if the key exists the score is increased by 1.
* Likewise if the query parameter values match.
* - If the hash is the same, the score is increased by 1. This includes if
* the hash is missing in both URLs.
*
* @param {URL} url1
* Url to compare.
* @param {URL} url2
* Other url to compare. Ordering shouldn't matter.
* @param {object} [matchOptions]
* Options for checking equality.
* @param {boolean} [matchOptions.path]
* Whether the path must match. Default to false.
* @param {boolean} [matchOptions.paramValues]
* Whether the values of the query parameters must match if the query
* parameter key exists in the other. Defaults to false.
* @returns {number}
* A score of how closely the two URLs match. Returns 0 if there is no
* match or the equality check failed for an enabled match option.
*/
compareUrls(url1, url2, matchOptions = {}) {
// In case of an exact match, well, that's an obvious winner.
if (url1.href == url2.href) {
return Infinity;
}
// Each step we get closer to the two URLs being the same, we increase the
// score. The consumer of this method will use these scores to see which
// of the URLs is the best match.
let score = 0;
if (url1.origin == url2.origin) {
++score;
if (url1.pathname == url2.pathname) {
++score;
for (let [key1, value1] of url1.searchParams) {
// Let's not fuss about the ordering of search params, since the
// score effect will solve that.
if (url2.searchParams.has(key1)) {
++score;
if (url2.searchParams.get(key1) == value1) {
++score;
} else if (matchOptions.paramValues) {
return 0;
}
}
}
if (url1.hash == url2.hash) {
++score;
}
} else if (matchOptions.path) {
return 0;
}
}
return score;
}
/**
* Parts of the URL, like search params and hashes, may be mutated by scripts
* on a page we're tracking. Since we don't want to keep track of that
@ -506,38 +571,6 @@ class TelemetryHandler {
return null;
}
const compareURLs = (url1, url2) => {
// In case of an exact match, well, that's an obvious winner.
if (url1.href == url2.href) {
return Infinity;
}
// Each step we get closer to the two URLs being the same, we increase the
// score. The consumer of this method will use these scores to see which
// of the URLs is the best match.
let score = 0;
if (url1.hostname == url2.hostname) {
++score;
if (url1.pathname == url2.pathname) {
++score;
for (let [key1, value1] of url1.searchParams) {
// Let's not fuss about the ordering of search params, since the
// score effect will solve that.
if (url2.searchParams.has(key1)) {
++score;
if (url2.searchParams.get(key1) == value1) {
++score;
}
}
}
if (url1.hash == url2.hash) {
++score;
}
}
}
return score;
};
let item;
let currentBestMatch = 0;
for (let [trackingURL, candidateItem] of this._browserInfoByURL) {
@ -553,7 +586,7 @@ class TelemetryHandler {
} catch (ex) {
continue;
}
let score = compareURLs(url, trackingURL);
let score = this.compareUrls(url, trackingURL);
if (score > currentBestMatch) {
item = candidateItem;
currentBestMatch = score;
@ -949,7 +982,7 @@ class ContentHandler {
return;
}
let URL = wrappedChannel.finalURL;
let url = wrappedChannel.finalURL;
let providerInfo = item.info.provider;
let info = this._searchProviderInfo.find(provider => {
@ -984,7 +1017,7 @@ class ContentHandler {
lazy.serpEventsEnabled &&
channel.isDocument &&
(channel.loadInfo.isTopLevelLoad ||
info.nonAdsLinkRegexps.some(r => r.test(URL)))
info.nonAdsLinkRegexps.some(r => r.test(url)))
) {
let browser = wrappedChannel.browserElement;
// If the load is from history, don't record an event.
@ -1033,22 +1066,34 @@ class ContentHandler {
isSerp = true;
}
// Determine the "type" of the link.
let type = telemetryState.hrefToComponentMap?.get(URL);
// The SERP provider may have modified the url with different query
// parameters, so try checking all the recorded hrefs to see if any
// look similar.
if (!type) {
for (let [
href,
componentType,
] of telemetryState.hrefToComponentMap.entries()) {
if (URL.startsWith(href)) {
type = componentType;
break;
}
let startFindComponent = Cu.now();
let parsedUrl = new URL(url);
// Determine the component type of the link.
let type;
for (let [
storedUrl,
componentType,
] of telemetryState.urlToComponentMap.entries()) {
// The URL we're navigating to may have more query parameters if
// the provider adds query parameters when the user clicks on a link.
// On the other hand, the URL we are navigating to may have have
// fewer query parameters because of query param stripping.
// Thus, if a query parameter is missing, a match can still be made
// provided keys that exist in both URLs contain equal values.
let score = SearchSERPTelemetry.compareUrls(storedUrl, parsedUrl, {
paramValues: true,
path: true,
});
if (score) {
type = componentType;
break;
}
}
ChromeUtils.addProfilerMarker(
"SearchSERPTelemetry._observeActivity",
startFindComponent,
"Find component for URL"
);
// Default value for URLs that don't match any components categorized
// on the page.
@ -1082,7 +1127,7 @@ class ContentHandler {
lazy.logConsole.debug("Counting click:", {
impressionId: telemetryState.impressionId,
type,
URL,
URL: url,
});
// Prevent re-directed channels from being examined more than once.
wrappedChannel._recordedClick = true;
@ -1094,7 +1139,7 @@ class ContentHandler {
);
}
if (!info.extraAdServersRegexps?.some(regex => regex.test(URL))) {
if (!info.extraAdServersRegexps?.some(regex => regex.test(url))) {
return;
}
@ -1118,7 +1163,7 @@ class ContentHandler {
lazy.logConsole.debug("Counting ad click in page for:", {
source: item.source,
originURL,
URL,
URL: url,
});
} catch (e) {
console.error(e);
@ -1226,7 +1271,13 @@ class ContentHandler {
ads_hidden: data.adsHidden,
});
}
telemetryState.hrefToComponentMap = info.hrefToComponentMap;
// Convert hrefToComponentMap to a urlToComponentMap in order to cache
// the query parameters of the href.
let urlToComponentMap = new Map();
for (let [href, adType] of info.hrefToComponentMap) {
urlToComponentMap.set(new URL(href), adType);
}
telemetryState.urlToComponentMap = urlToComponentMap;
telemetryState.adImpressionsReported = true;
Services.obs.notifyObservers(null, "reported-page-with-ad-impressions");
}

View file

@ -57,6 +57,10 @@ support-files =
searchTelemetryAd_searchbox_with_content.html
searchTelemetryAd_searchbox_with_content.html^headers^
serp.css
[browser_search_telemetry_engagement_query_params.js]
support-files =
searchTelemetryAd_components_query_parameters.html
serp.css
[browser_search_telemetry_engagement_redirect.js]
support-files =
redirect_ad.sjs

View file

@ -0,0 +1,300 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* These tests load SERPs and check that query params that are changed either
* by the browser or in the page after click are still properly recognized
* as ads.
*
*/
"use strict";
const TEST_PROVIDER_INFO = [
{
telemetryId: "example",
searchPageRegexp:
/^https:\/\/example.org\/browser\/browser\/components\/search\/test\/browser\/telemetry\/searchTelemetryAd_/,
queryParamName: "s",
codeParamName: "abc",
taggedCodes: ["ff"],
adServerAttributes: ["mozAttr"],
nonAdsLinkRegexps: [],
extraAdServersRegexps: [/^https:\/\/example\.com\/ad/],
components: [
{
type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
included: {
parent: {
selector: ".moz_ad",
},
children: [
{
selector: ".multi-col",
type: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK,
},
],
},
},
{
type: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
default: true,
},
],
},
];
add_setup(async function () {
SearchSERPTelemetry.overrideSearchTelemetryForTests(TEST_PROVIDER_INFO);
await waitForIdle();
// Enable local telemetry recording for the duration of the tests.
let oldCanRecord = Services.telemetry.canRecordExtended;
Services.telemetry.canRecordExtended = true;
await SpecialPowers.pushPrefEnv({
set: [
["browser.search.log", true],
["browser.search.serpEventTelemetry.enabled", true],
],
});
registerCleanupFunction(async () => {
SearchSERPTelemetry.overrideSearchTelemetryForTests();
Services.telemetry.canRecordExtended = oldCanRecord;
resetTelemetry();
});
});
// Baseline test clicking on either link properly categorizes both properly.
add_task(async function test_click_links() {
let url = getSERPUrl("searchTelemetryAd_components_query_parameters.html");
info("Load SERP.");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await waitForPageWithAdImpressions();
info("Click on ad link.");
let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a#ad_link",
{},
tab.linkedBrowser
);
await pageLoadPromise;
assertImpressionEvents([
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
},
],
},
]);
info("Load SERP again.");
await BrowserTestUtils.loadURIString(gBrowser, url);
pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await waitForPageWithAdImpressions();
info("Click on site link.");
pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a#ad_sitelink",
{},
tab.linkedBrowser
);
await pageLoadPromise;
assertImpressionEvents([
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_LINK,
},
],
},
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK,
},
],
},
]);
// Clean up.
BrowserTestUtils.removeTab(tab);
resetTelemetry();
});
add_task(async function test_click_link_with_more_parameters() {
let url = getSERPUrl("searchTelemetryAd_components_query_parameters.html");
info("Load SERP.");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await waitForPageWithAdImpressions();
info("After ad impressions, add query parameters to DOM element.");
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let el = content.document.getElementById("ad_sitelink");
let domUrl = new URL(el.href);
domUrl.searchParams.set("example", "param");
el.setAttribute("href", domUrl.toString());
});
info("Click on site link.");
let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a#ad_sitelink",
{},
tab.linkedBrowser
);
await pageLoadPromise;
assertImpressionEvents([
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK,
},
],
},
]);
// Clean up.
BrowserTestUtils.removeTab(tab);
resetTelemetry();
});
add_task(async function test_click_link_with_fewer_parameters() {
let url = getSERPUrl("searchTelemetryAd_components_query_parameters.html");
info("Load SERP.");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await waitForPageWithAdImpressions();
info("After ad impressions, remove a query parameter from a DOM element.");
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let el = content.document.getElementById("ad_sitelink");
let domUrl = new URL(el.href);
domUrl.searchParams.delete("foo");
el.setAttribute("href", domUrl.toString());
});
info("Click on site link.");
let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a#ad_sitelink",
{},
tab.linkedBrowser
);
await pageLoadPromise;
assertImpressionEvents([
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK,
},
],
},
]);
// Clean up.
BrowserTestUtils.removeTab(tab);
resetTelemetry();
});
add_task(async function test_click_link_with_reordered_parameters() {
let url = getSERPUrl("searchTelemetryAd_components_query_parameters.html");
info("Load SERP.");
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, url);
await waitForPageWithAdImpressions();
info("After ad impressions, re-sort the query params.");
await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
let el = content.document.getElementById("ad_sitelink");
let domUrl = new URL(el.href);
domUrl.searchParams.sort();
el.setAttribute("href", domUrl.toString());
});
info("Click on site link.");
let pageLoadPromise = BrowserTestUtils.waitForLocationChange(gBrowser);
await BrowserTestUtils.synthesizeMouseAtCenter(
"a#ad_sitelink",
{},
tab.linkedBrowser
);
await pageLoadPromise;
assertImpressionEvents([
{
impression: {
provider: "example",
tagged: "true",
partner_code: "ff",
source: "unknown",
is_shopping_page: "false",
shopping_tab_displayed: "false",
},
engagements: [
{
action: SearchSERPTelemetryUtils.ACTIONS.CLICKED,
target: SearchSERPTelemetryUtils.COMPONENTS.AD_SITELINK,
},
],
},
]);
// Clean up.
BrowserTestUtils.removeTab(tab);
resetTelemetry();
});

View file

@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" type="text/css" href="./serp.css" />
</head>
<body>
<section id="searchresults">
<div class="lhs">
<div class="moz_ad">
<h5 test-label>ad_sitelink</h5>
<!--
Note that the query parameter keys are in reverse alphabetical order
that will be reversed in the tests.
-->
<a id="ad_sitelink" href="https://example.com/ad?foo=bar0&baz=bar0">
<h2>Example Result</h2>
</a>
<div class="multi-col">
<div>
<a href="https://example.com/ad?foo=bar1&baz=bar1">
<h2>New Releases</h2>
</a>
<span>Cras ac velit sed tellus</span>
</div>
</div>
</div>
<div class="moz_ad">
<h5 test-label>ad_link</h5>
<a id="ad_link" href="https://example.com/ad?foo=bar2&baz=bar2">
<h2>Example Result</h2>
</a>
</div>
</div>
</section>
</body>
</html>

View file

@ -0,0 +1,190 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* This test ensures we compare URLs correctly. For more info on the scores,
* please read the function definition.
*/
ChromeUtils.defineESModuleGetters(this, {
SearchSERPTelemetry: "resource:///modules/SearchSERPTelemetry.sys.mjs",
SearchUtils: "resource://gre/modules/SearchUtils.sys.mjs",
});
const TESTS = [
{
title: "No difference",
url1: "https://www.example.org/search?a=b&c=d#hash",
url2: "https://www.example.org/search?a=b&c=d#hash",
expected: Infinity,
},
{
// Since the ordering is different, a strict equality match is not going
// match. The score will be high, but not Infinity.
title: "Different ordering of query parameters",
url1: "https://www.example.org/search?c=d&a=b#hash",
url2: "https://www.example.org/search?a=b&c=d#hash",
expected: 7,
},
{
title: "Different protocol",
url1: "http://www.example.org/search",
url2: "https://www.example.org/search",
expected: 0,
},
{
title: "Different origin",
url1: "https://example.org/search",
url2: "https://www.example.org/search",
expected: 0,
},
{
title: "Different path",
url1: "https://www.example.org/serp",
url2: "https://www.example.org/search",
expected: 1,
},
{
title: "Different path, path on",
url1: "https://www.example.org/serp",
url2: "https://www.example.org/search",
options: {
path: true,
},
expected: 0,
},
{
title: "Different query parameter keys",
url1: "https://www.example.org/search?a=c",
url2: "https://www.example.org/search?b=c",
expected: 3,
},
{
title: "Different query parameter keys, paramValues on",
url1: "https://www.example.org/search?a=c",
url2: "https://www.example.org/search?b=c",
options: {
paramValues: true,
},
// Shouldn't change the score because the option should only nullify
// the result if one of the keys match but has different values.
expected: 3,
},
{
title: "Some different query parameter keys",
url1: "https://www.example.org/search?a=b&c=d",
url2: "https://www.example.org/search?a=b",
expected: 5,
},
{
title: "Some different query parameter keys, paramValues on",
url1: "https://www.example.org/search?a=b&c=d",
url2: "https://www.example.org/search?a=b",
options: {
paramValues: true,
},
// Shouldn't change the score because the option should only trigger
// if the keys match but values differ.
expected: 5,
},
{
title: "Different query parameter values",
url1: "https://www.example.org/search?a=b",
url2: "https://www.example.org/search?a=c",
expected: 4,
},
{
title: "Different query parameter values, paramValues on",
url1: "https://www.example.org/search?a=b&c=d",
url2: "https://www.example.org/search?a=b&c=e",
options: {
paramValues: true,
},
expected: 0,
},
{
title: "Some different query parameter values",
url1: "https://www.example.org/search?a=b&c=d",
url2: "https://www.example.org/search?a=b&c=e",
expected: 6,
},
{
title: "Different query parameter values, paramValues on",
url1: "https://www.example.org/search?a=b&c=d",
url2: "https://www.example.org/search?a=b&c=e",
options: {
paramValues: true,
},
expected: 0,
},
{
title: "Empty query parameter",
url1: "https://www.example.org/search?a=b&c",
url2: "https://www.example.org/search?c&a=b",
expected: 7,
},
{
title: "Empty query parameter, paramValues on",
url1: "https://www.example.org/search?a=b&c",
url2: "https://www.example.org/search?c&a=b",
options: {
paramValues: true,
},
expected: 7,
},
{
title: "Missing empty query parameter",
url1: "https://www.example.org/search?c&a=b",
url2: "https://www.example.org/search?a=b",
expected: 5,
},
{
title: "Missing empty query parameter, paramValues on",
url1: "https://www.example.org/search?c&a=b",
url2: "https://www.example.org/search?a=b",
options: {
paramValues: true,
},
expected: 5,
},
{
title: "Different empty query parameter",
url1: "https://www.example.org/search?c&a=b",
url2: "https://www.example.org/search?a=b&c=foo",
expected: 6,
},
{
title: "Different empty query parameter, paramValues on",
url1: "https://www.example.org/search?c&a=b",
url2: "https://www.example.org/search?a=b&c=foo",
options: {
paramValues: true,
},
expected: 0,
},
];
add_setup(async function () {
Services.prefs.setBoolPref(SearchUtils.BROWSER_SEARCH_PREF + "log", true);
await SearchSERPTelemetry.init();
});
add_task(async function test_parsing_extracted_urls() {
for (let test of TESTS) {
info(test.title);
let result = SearchSERPTelemetry.compareUrls(
new URL(test.url1),
new URL(test.url2),
test.options
);
Assert.equal(result, test.expected, "Equality: url1, url2");
// Flip the URLs to ensure order doesn't matter.
result = SearchSERPTelemetry.compareUrls(
new URL(test.url2),
new URL(test.url1),
test.options
);
Assert.equal(result, test.expected, "Equality: url2, url1");
}
});

View file

@ -3,6 +3,7 @@ skip-if = toolkit == 'android' # bug 1730213
firefox-appdir = browser
[test_search_telemetry_categorization_process_domains.js]
[test_search_telemetry_compare_urls.js]
[test_search_telemetry_config_validation.js]
support-files =
../../schema/search-telemetry-schema.json

View file

@ -4,21 +4,18 @@
ChromeUtils.defineESModuleGetters(this, {
ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
ExperimentFakes: "resource://testing-common/NimbusTestUtils.sys.mjs",
MockRegistrar: "resource://testing-common/MockRegistrar.sys.mjs",
NimbusFeatures: "resource://nimbus/ExperimentAPI.sys.mjs",
sinon: "resource://testing-common/Sinon.sys.mjs",
});
const mockedDefaultAgent = {
setDefaultBrowserUserChoice: sinon.stub(),
setDefaultExtensionHandlersUserChoice: sinon.stub(),
QueryInterface: ChromeUtils.generateQI(["nsIDefaultAgent"]),
};
const setDefaultBrowserUserChoiceStub = sinon.stub();
const setDefaultExtensionHandlersUserChoiceStub = sinon.stub();
const defaultAgentCID = MockRegistrar.register(
"@mozilla.org/default-agent;1",
mockedDefaultAgent
);
const defaultAgentStub = sinon.stub(ShellService, "defaultAgent").value({
setDefaultBrowserUserChoice: setDefaultBrowserUserChoiceStub,
setDefaultExtensionHandlersUserChoice:
setDefaultExtensionHandlersUserChoiceStub,
});
XPCOMUtils.defineLazyServiceGetter(
this,
@ -41,7 +38,7 @@ const shellStub = sinon.stub(ShellService, "shellService").value({
});
registerCleanupFunction(() => {
MockRegistrar.unregister(defaultAgentCID);
defaultAgentStub.restore();
_userChoiceImpossibleTelemetryResultStub.restore();
shellStub.restore();
@ -78,15 +75,15 @@ add_task(async function remoteEnableWithPDF() {
true
);
mockedDefaultAgent.setDefaultBrowserUserChoice.resetHistory();
setDefaultBrowserUserChoiceStub.resetHistory();
ShellService.setDefaultBrowser();
const aumi = XreDirProvider.getInstallHash();
Assert.ok(mockedDefaultAgent.setDefaultBrowserUserChoice.called);
Assert.deepEqual(
mockedDefaultAgent.setDefaultBrowserUserChoice.firstCall.args,
[aumi, [".pdf", "FirefoxPDF"]]
);
Assert.ok(setDefaultBrowserUserChoiceStub.called);
Assert.deepEqual(setDefaultBrowserUserChoiceStub.firstCall.args, [
aumi,
[".pdf", "FirefoxPDF"],
]);
await doCleanup();
});
@ -123,12 +120,12 @@ add_task(async function remoteEnableWithPDF_testOnlyReplaceBrowsers() {
for (let progId of ["", "MSEdgePDF"]) {
queryCurrentDefaultHandlerForStub.callsFake(() => progId);
mockedDefaultAgent.setDefaultBrowserUserChoice.resetHistory();
setDefaultBrowserUserChoiceStub.resetHistory();
ShellService.setDefaultBrowser();
Assert.ok(mockedDefaultAgent.setDefaultBrowserUserChoice.called);
Assert.ok(setDefaultBrowserUserChoiceStub.called);
Assert.deepEqual(
mockedDefaultAgent.setDefaultBrowserUserChoice.firstCall.args,
setDefaultBrowserUserChoiceStub.firstCall.args,
[aumi, [".pdf", "FirefoxPDF"]],
`Will take default from missing association or known browser with ProgID '${progId}'`
);
@ -137,12 +134,12 @@ add_task(async function remoteEnableWithPDF_testOnlyReplaceBrowsers() {
// But not from a non-browser.
queryCurrentDefaultHandlerForStub.callsFake(() => "Acrobat.Document.DC");
mockedDefaultAgent.setDefaultBrowserUserChoice.resetHistory();
setDefaultBrowserUserChoiceStub.resetHistory();
ShellService.setDefaultBrowser();
Assert.ok(mockedDefaultAgent.setDefaultBrowserUserChoice.called);
Assert.ok(setDefaultBrowserUserChoiceStub.called);
Assert.deepEqual(
mockedDefaultAgent.setDefaultBrowserUserChoice.firstCall.args,
setDefaultBrowserUserChoiceStub.firstCall.args,
[aumi, []],
`Will not take default from non-browser`
);
@ -169,15 +166,12 @@ add_task(async function remoteEnableWithoutPDF() {
false
);
mockedDefaultAgent.setDefaultBrowserUserChoice.resetHistory();
setDefaultBrowserUserChoiceStub.resetHistory();
ShellService.setDefaultBrowser();
const aumi = XreDirProvider.getInstallHash();
Assert.ok(mockedDefaultAgent.setDefaultBrowserUserChoice.called);
Assert.deepEqual(
mockedDefaultAgent.setDefaultBrowserUserChoice.firstCall.args,
[aumi, []]
);
Assert.ok(setDefaultBrowserUserChoiceStub.called);
Assert.deepEqual(setDefaultBrowserUserChoiceStub.firstCall.args, [aumi, []]);
await doCleanup();
});
@ -201,10 +195,10 @@ add_task(async function remoteDisable() {
true
);
mockedDefaultAgent.setDefaultBrowserUserChoice.resetHistory();
setDefaultBrowserUserChoiceStub.resetHistory();
ShellService.setDefaultBrowser();
Assert.ok(mockedDefaultAgent.setDefaultBrowserUserChoice.notCalled);
Assert.ok(setDefaultBrowserUserChoiceStub.notCalled);
Assert.ok(setDefaultStub.called);
await doCleanup();
@ -225,51 +219,51 @@ add_task(async function test_setAsDefaultPDFHandler_knownBrowser() {
info("Testing setAsDefaultPDFHandler(true) when knownBrowser = true");
ShellService.setAsDefaultPDFHandler(true);
Assert.ok(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.called,
setDefaultExtensionHandlersUserChoiceStub.called,
"Called default browser agent"
);
Assert.deepEqual(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.firstCall.args,
setDefaultExtensionHandlersUserChoiceStub.firstCall.args,
expectedArguments,
"Called default browser agent with expected arguments"
);
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.resetHistory();
setDefaultExtensionHandlersUserChoiceStub.resetHistory();
info("Testing setAsDefaultPDFHandler(false) when knownBrowser = true");
ShellService.setAsDefaultPDFHandler(false);
Assert.ok(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.called,
setDefaultExtensionHandlersUserChoiceStub.called,
"Called default browser agent"
);
Assert.deepEqual(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.firstCall.args,
setDefaultExtensionHandlersUserChoiceStub.firstCall.args,
expectedArguments,
"Called default browser agent with expected arguments"
);
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.resetHistory();
setDefaultExtensionHandlersUserChoiceStub.resetHistory();
pdfHandlerResult.knownBrowser = false;
info("Testing setAsDefaultPDFHandler(true) when knownBrowser = false");
ShellService.setAsDefaultPDFHandler(true);
Assert.ok(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.notCalled,
setDefaultExtensionHandlersUserChoiceStub.notCalled,
"Did not call default browser agent"
);
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.resetHistory();
setDefaultExtensionHandlersUserChoiceStub.resetHistory();
info("Testing setAsDefaultPDFHandler(false) when knownBrowser = false");
ShellService.setAsDefaultPDFHandler(false);
Assert.ok(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.called,
setDefaultExtensionHandlersUserChoiceStub.called,
"Called default browser agent"
);
Assert.deepEqual(
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.firstCall.args,
setDefaultExtensionHandlersUserChoiceStub.firstCall.args,
expectedArguments,
"Called default browser agent with expected arguments"
);
mockedDefaultAgent.setDefaultExtensionHandlersUserChoice.resetHistory();
setDefaultExtensionHandlersUserChoiceStub.resetHistory();
} finally {
sandbox.restore();
}

View file

@ -211,12 +211,31 @@ export class ShoppingSidebarChild extends RemotePageChild {
isPolledRequestDone = true;
}
try {
if (!isPolledRequest) {
data = await this.#product.requestAnalysis();
let analysisStatus;
if (isPolledRequest) {
// Request a new analysis.
let { status } = await this.#product.requestCreateAnalysis();
analysisStatus = status;
} else {
data = await this.#product.pollForAnalysisCompleted();
// Check if there is an analysis in progress.
let { status } = await this.#product.requestAnalysisCreationStatus();
analysisStatus = status;
}
if (
analysisStatus &&
(analysisStatus == "pending" || analysisStatus == "in_progress")
) {
// TODO: Send content update to show analysis in progress message,
// if not already shown (Bug 1851629).
await this.#product.pollForAnalysisCompleted({
pollInitialWait: analysisStatus == "in_progress" ? 0 : undefined,
});
isPolledRequestDone = true;
}
data = await this.#product.requestAnalysis();
} catch (err) {
console.error("Failed to fetch product analysis data", err);
data = { error: err };
@ -225,6 +244,7 @@ export class ShoppingSidebarChild extends RemotePageChild {
if (!canContinue(uri)) {
return;
}
this.sendToContent("Update", {
showOnboarding: false,
data,
@ -232,7 +252,7 @@ export class ShoppingSidebarChild extends RemotePageChild {
isPolledRequestDone,
});
if (data.error) {
if (!data || data.error) {
return;
}
@ -246,9 +266,18 @@ export class ShoppingSidebarChild extends RemotePageChild {
);
}
if (!this.canFetchAndShowAd || !this.userHasAdsEnabled) {
return;
}
this.#product.requestRecommendations().then(recommendationData => {
// Check if the product URI or opt in changed while we waited.
if (uri != this.#productURI || !this.canFetchAndShowData) {
if (
uri != this.#productURI ||
!this.canFetchAndShowData ||
!this.canFetchAndShowAd ||
!this.userHasAdsEnabled
) {
return;
}

View file

@ -139,7 +139,7 @@ class AnalysisExplainer extends MozLitElement {
>
<a
is="moz-support-link"
support-page="todo"
support-page="review-checker-review-quality"
data-l10n-name="review-quality-url"
></a>
</p>

View file

@ -6,8 +6,8 @@
display: flex;
flex-direction: column;
box-sizing: border-box;
position: fixed;
height: 100%;
width: 100%;
border-inline-start: 1px solid var(--in-content-box-border-color);
}

View file

@ -71,11 +71,7 @@ class ShoppingMessageBar extends MozLitElement {
></span>
<a
id="message-bar-reanalysis-link"
target="_blank"
data-l10n-id="shopping-message-bar-warning-stale-analysis-link"
href="https://fakespot.com/analyze?url=${encodeURIComponent(
this.productUrl
)}"
@click=${this.onClickAnalysisLink}
></a>
</article>

View file

@ -10,8 +10,6 @@ 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";
import { FAKESPOT_ANALYSIS_URL } from "chrome://global/content/shopping/ProductConfig.mjs";
class UnanalyzedProductCard extends MozLitElement {
static properties = {
productURL: { type: String, reflect: true },
@ -55,10 +53,6 @@ class UnanalyzedProductCard extends MozLitElement {
<a
id="unanalyzed-product-analysis-link"
data-l10n-id="shopping-unanalyzed-product-analyze-link"
target="_blank"
href="${FAKESPOT_ANALYSIS_URL}${encodeURIComponent(
this.productURL
)}"
@click=${this.onClickAnalysisLink}
></a>
</div>

View file

@ -738,8 +738,8 @@ var TranslationsPanel = new (class {
* pertain to languages.
*/
async #updateSettingsMenuLanguageCheckboxStates() {
const { docLangTag, isDocLangTagSupported } =
await this.#getCachedDetectedLanguages();
const langTags = await this.#getCachedDetectedLanguages();
const { docLangTag, isDocLangTagSupported } = langTags;
const { panel } = this.elements;
const alwaysTranslateMenuItems = panel.ownerDocument.querySelectorAll(
@ -756,7 +756,7 @@ var TranslationsPanel = new (class {
const alwaysOfferTranslations =
TranslationsParent.shouldAlwaysOfferTranslations();
const alwaysTranslateLanguage =
TranslationsParent.shouldAlwaysTranslateLanguage(docLangTag);
TranslationsParent.shouldAlwaysTranslateLanguage(langTags);
const neverTranslateLanguage =
TranslationsParent.shouldNeverTranslateLanguage(docLangTag);
const shouldDisable =
@ -1354,14 +1354,15 @@ var TranslationsPanel = new (class {
* If auto-translate is currently inactive for the doc language, activates it.
*/
async onAlwaysTranslateLanguage() {
const { docLangTag } = await this.#getCachedDetectedLanguages();
const langTags = await this.#getCachedDetectedLanguages();
const { docLangTag } = langTags;
if (!docLangTag) {
throw new Error("Expected to have a document language tag.");
}
const pageAction =
this.getCheckboxPageActionFor().alwaysTranslateLanguage();
const toggledOn =
TranslationsParent.toggleAlwaysTranslateLanguagePref(docLangTag);
TranslationsParent.toggleAlwaysTranslateLanguagePref(langTags);
TranslationsParent.telemetry()
.panel()
.onAlwaysTranslateLanguage(docLangTag, toggledOn);

View file

@ -5,6 +5,7 @@ support-files =
!/toolkit/components/translations/tests/browser/translations-test.mjs
[browser_manage_languages.js]
[browser_translations_panel_a11y_focus.js]
[browser_translations_panel_always_translate_language_bad_data.js]
[browser_translations_panel_always_translate_language_basic.js]
[browser_translations_panel_always_translate_language_manual.js]
[browser_translations_panel_always_translate_language_restore.js]

View file

@ -0,0 +1,40 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Tests that having an "always translate" set to your app locale doesn't break things.
*/
add_task(async function test_always_translate_with_bad_data() {
const { cleanup, runInPage } = await loadTestPage({
page: ENGLISH_PAGE_URL,
languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.alwaysTranslateLanguages", "en,fr"]],
});
await openTranslationsPanel({
onOpenPanel: assertPanelDefaultView,
openFromAppMenu: true,
});
await openTranslationsSettingsMenu();
await assertIsAlwaysTranslateLanguage("en", {
checked: false,
disabled: true,
});
await closeSettingsMenuIfOpen();
await closeTranslationsPanelIfOpen();
info("Checking that the page is untranslated");
await runInPage(async TranslationsTest => {
const { getH1 } = TranslationsTest.getSelectors();
await TranslationsTest.assertTranslationResult(
"The page's H1 is untranslated and in the original English.",
getH1,
'"The Wonderful Wizard of Oz" by L. Frank Baum'
);
});
await cleanup();
});

View file

@ -358,6 +358,12 @@ class ProviderQuickSuggest extends UrlbarProvider {
result.isRichSuggestion = true;
result.richSuggestionIconSize ||= 52;
result.suggestedIndex = 1;
} else if (
suggestion.is_sponsored &&
lazy.UrlbarPrefs.get("quickSuggestSponsoredPriority")
) {
result.isBestMatch = true;
result.suggestedIndex = 1;
} else if (
!isNaN(suggestion.position) &&
lazy.UrlbarPrefs.get("quickSuggestAllowPositionInSuggestions")

View file

@ -2129,6 +2129,12 @@ export class UrlbarView {
return { id: "urlbar-group-mdn" };
case "pocket":
return { id: "urlbar-group-pocket" };
case "adm_sponsored": {
if (lazy.UrlbarPrefs.get("quickSuggestSponsoredPriority")) {
return { id: "urlbar-group-sponsored" };
}
break;
}
}
}

View file

@ -36,6 +36,10 @@ urlbar-group-mdn =
urlbar-group-pocket =
.label = Recommended reads
# A label shown above sponsored suggestions in the urlbar results if priority.
urlbar-group-sponsored =
.label = Sponsored
# Tooltip text for the block button shown in top pick rows.
firefox-suggest-urlbar-block =
.title = Dismiss this suggestion

View file

@ -198,9 +198,15 @@ export class AdmWikipedia extends BaseFeature {
);
if (suggestion.is_sponsored) {
// If quickSuggestSponsoredPriority is enabled, as "Sponsored" label is
// shown as group label, no need here.
if (!lazy.UrlbarPrefs.get("quickSuggestSponsoredPriority")) {
result.payload.descriptionL10n = {
id: "urlbar-result-action-sponsored",
};
result.richSuggestionIconSize = 16;
}
result.isRichSuggestion = true;
result.richSuggestionIconSize = 16;
result.payload.descriptionL10n = { id: "urlbar-result-action-sponsored" };
}
return result;

View file

@ -5,6 +5,8 @@
[DEFAULT]
support-files =
head.js
prefs=
browser.bookmarks.testing.skipDefaultBookmarksImport=true
[browser_interventions.js]
[browser_picks.js]

View file

@ -5,6 +5,8 @@
[DEFAULT]
support-files =
head.js
prefs=
browser.bookmarks.testing.skipDefaultBookmarksImport=true
[browser_appendSpanCount.js]
[browser_noUpdateResultsFromOtherProviders.js]

View file

@ -11,6 +11,7 @@ skip-if =
os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
prefs =
browser.bookmarks.testing.skipDefaultBookmarksImport=true
browser.urlbar.trending.featureGate=false
extensions.screenshots.disabled=false
screenshots.browser.component.enabled=true

View file

@ -10,6 +10,7 @@ add_setup(async function () {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// Disable placeholder completion. The point of this test is to make sure the
// first result is autofilled (or not) correctly. Autofilling the placeholder

View file

@ -9,6 +9,7 @@ add_task(async function test() {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
registerCleanupFunction(async () => {
await PlacesUtils.history.clear();
});

View file

@ -884,6 +884,7 @@ async function addVisits(...urls) {
await PlacesTestUtils.addVisits(url);
}
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
}
async function cleanUp() {

View file

@ -12,6 +12,7 @@ add_setup(async function () {
add_task(async function origin() {
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// all lowercase
await typeAndCheck([
["e", "example.com/"],
@ -48,6 +49,7 @@ add_task(async function origin() {
add_task(async function url() {
await PlacesTestUtils.addVisits(["http://example.com/foo/bar"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// all lowercase
await typeAndCheck([
["e", "example.com/"],

View file

@ -10,6 +10,7 @@ add_task(async function test() {
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// Search for "ex". It should autofill to example.com/.
await UrlbarTestUtils.promiseAutocompleteResultPopup({

View file

@ -5,6 +5,8 @@
* Tests turning non-url-looking values typed in the input field into proper URLs.
*/
requestLongerTimeout(2);
const TEST_ENGINE_BASENAME = "searchSuggestionEngine.xml";
add_task(async function checkCtrlWorks() {
@ -61,6 +63,7 @@ add_task(async function checkCtrlWorks() {
undefined,
true
);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.inputIntoURLBar(win, inputValue);
EventUtils.synthesizeKey("KEY_Enter", options, win);
await Promise.all([promiseLoad, promiseStopped]);
@ -167,17 +170,17 @@ add_task(async function autofill() {
["@goo", "https://www.goo.com/", { ctrlKey: true }],
];
function promiseAutofill() {
return BrowserTestUtils.waitForEvent(win.gURLBar.inputField, "select");
}
for (let [inputValue, expectedURL, options] of testcases) {
let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
expectedURL,
win.gBrowser.selectedBrowser
);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
win.gURLBar.select();
let autofillPromise = promiseAutofill();
let autofillPromise = BrowserTestUtils.waitForEvent(
win.gURLBar.inputField,
"select"
);
EventUtils.sendString(inputValue, win);
await autofillPromise;
EventUtils.synthesizeKey("KEY_Enter", options, win);

View file

@ -420,6 +420,7 @@ add_task(async function includingProtocol() {
await PlacesTestUtils.clearInputHistory();
await PlacesTestUtils.addVisits(["https://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// If the url is autofilled, the protocol should be included in the copied
// value.

View file

@ -23,6 +23,7 @@ add_setup(async function () {
add_task(async function url() {
await BrowserTestUtils.withNewTab("http://example.com/", async () => {
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
gURLBar.focus();
gURLBar.selectionEnd = gURLBar.untrimmedValue.length;
gURLBar.selectionStart = gURLBar.untrimmedValue.length;

View file

@ -44,6 +44,7 @@ add_task(async function () {
url: "http://example.com/",
parentGuid: PlacesUtils.bookmarks.menuGuid,
});
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await SearchTestUtils.installSearchExtension(
{

View file

@ -52,6 +52,7 @@ add_task(async function bumped() {
info("Running subtest: " + JSON.stringify({ url, searchString }));
await PlacesTestUtils.addVisits(url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarUtils.addToInputHistory(url, input);
addToInputHistorySpy.resetHistory();
@ -100,6 +101,7 @@ add_task(async function notBumped_origin() {
for (let i = 0; i < 5; i++) {
await PlacesTestUtils.addVisits(url);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await triggerAutofillAndPickResult("exam", "example.com/");
@ -120,6 +122,7 @@ add_task(async function notBumped_origin() {
add_task(async function notBumped_url() {
let url = "http://example.com/test";
await PlacesTestUtils.addVisits(url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await triggerAutofillAndPickResult("example.com/t", "example.com/test");

View file

@ -80,6 +80,7 @@ add_task(async function move_tab_into_new_window_and_open_new_tab() {
);
let newWindow = gBrowser.replaceTabWithWindow(tab);
await swapDocShellPromise;
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
info("Type in the urlbar to open it and see an autofill suggestion.");
await UrlbarTestUtils.promisePopupOpen(newWindow, async () => {

View file

@ -12,7 +12,7 @@ add_setup(async function () {
for (let i = 0; i < 5; i++) {
await PlacesTestUtils.addVisits([{ uri: "http://example.com/" }]);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await SearchTestUtils.installSearchExtension({}, { setAsDefault: true });
let defaultEngine = Services.search.getEngineByName("Example");
await Services.search.moveEngine(defaultEngine, 0);

View file

@ -132,7 +132,7 @@ add_task(async function test_autofill() {
let connectionNumber = server.connectionNumber;
let searchString = serverInfo.host;
info(`Searching for '${searchString}'`);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: searchString,
@ -162,7 +162,7 @@ add_task(async function test_autofill_privateContext() {
let connectionNumber = server.connectionNumber;
let searchString = serverInfo.host;
info(`Searching for '${searchString}'`);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window: privateWin,
value: searchString,

View file

@ -36,7 +36,7 @@ add_setup(async function () {
for (let i = 0; i < 3; i++) {
await PlacesTestUtils.addVisits([`https://${TEST_ENGINE_DOMAIN}/`]);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
registerCleanupFunction(async () => {
await PlacesUtils.history.clear();
});

View file

@ -366,6 +366,7 @@ const tests = [
info("Type an autofilled string, Enter.");
win.gURLBar.select();
let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window: win,
value: "exa",
@ -837,6 +838,7 @@ const tests = [
win.gURLBar.value = "example.org";
win.gURLBar.setPageProxyState("invalid");
let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promisePopupOpen(win, () => {
win.document.getElementById("Browser:OpenLocation").doCommand();
});
@ -866,6 +868,7 @@ const tests = [
win.gURLBar.value = "example.com";
win.gURLBar.setPageProxyState("invalid");
let promise = BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promisePopupOpen(win, () => {
win.document.getElementById("Browser:OpenLocation").doCommand();
});

View file

@ -158,18 +158,6 @@ if (AppConstants.platform == "macosx") {
}
add_setup(async function () {
// The following initialization code is necessary to avoid a frequent
// intermittent failure in verify-fission where, due to timings, we may or
// may not import default bookmarks.
info("Ensure Places init is complete");
let placesInitCompleteObserved = TestUtils.topicObserved(
"places-browser-init-complete"
);
Cc["@mozilla.org/browser/browserglue;1"]
.getService(Ci.nsIObserver)
.observe(null, "browser-glue-test", "places-browser-init-complete");
await placesInitCompleteObserved;
await PlacesUtils.bookmarks.eraseEverything();
await PlacesUtils.history.clear();
await PlacesTestUtils.clearInputHistory();
@ -294,6 +282,7 @@ add_task(async function history() {
for (const { uri, input } of inputHistory) {
await UrlbarUtils.addToInputHistory(uri, input);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
UrlbarPrefs.set("autoFill.adaptiveHistory.enabled", useAdaptiveHistory);
@ -490,7 +479,7 @@ add_task(async function impression() {
await UrlbarUtils.addToInputHistory(uri, input);
}
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await triggerAutofillAndPickResult(
userInput,
autofilled,
@ -538,6 +527,7 @@ add_task(async function impression() {
// Checks autofill deletion telemetry.
add_task(async function deletion() {
await PlacesTestUtils.addVisits(["http://example.com/"]);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
info("Delete autofilled value by DELETE key");
await doDeletionTest({

View file

@ -109,6 +109,7 @@ add_task(async function test() {
for (let i = 0; i < 3; i++) {
await PlacesTestUtils.addVisits([`https://${ENGINE_DOMAIN}/`]);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
@ -196,6 +197,7 @@ async function impressions_test(isOnboarding) {
await PlacesTestUtils.addVisits([`https://${firstEngineHost}-2.com`]);
await PlacesTestUtils.addVisits([`https://${ENGINE_DOMAIN}/`]);
}
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
// First do multiple searches for substrings of firstEngineHost. The view
// should show the same tab-to-search onboarding result the entire time, so

View file

@ -18,6 +18,8 @@ support-files =
../../ext/schema.json
skip-if =
os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
prefs =
browser.bookmarks.testing.skipDefaultBookmarksImport=true
[browser_glean_telemetry_abandonment_groups.js]
[browser_glean_telemetry_abandonment_interaction.js]
@ -40,6 +42,7 @@ skip-if =
[browser_glean_telemetry_engagement_selected_result.js]
support-files =
../../../../search/test/browser/trendingSuggestionEngine.sjs
skip-if = verify # Bug 1852375 - MerinoTestUtils.initWeather() doesn't play well with pushPrefEnv()
[browser_glean_telemetry_engagement_tips.js]
[browser_glean_telemetry_engagement_type.js]
[browser_glean_telemetry_exposure.js]

View file

@ -67,6 +67,7 @@ add_task(async function selected_result_autofill_adaptive() {
add_task(async function selected_result_autofill_origin() {
await doTest(async browser => {
await PlacesTestUtils.addVisits("https://example.com/test");
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await openPopup("exa");
await doEnter();
@ -85,6 +86,7 @@ add_task(async function selected_result_autofill_origin() {
add_task(async function selected_result_autofill_url() {
await doTest(async browser => {
await PlacesTestUtils.addVisits("https://example.com/test");
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
await openPopup("https://example.com/test");
await doEnter();

View file

@ -35,6 +35,12 @@ ChromeUtils.defineESModuleGetters(lazy, {
sinon: "resource://testing-common/Sinon.sys.mjs",
});
ChromeUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => {
return Cc["@mozilla.org/places/frecency-recalculator;1"].getService(
Ci.nsIObserver
).wrappedJSObject;
});
async function addTopSites(url) {
for (let i = 0; i < 5; i++) {
await PlacesTestUtils.addVisits(url);

View file

@ -8,6 +8,8 @@ support-files =
../api.js
../schema.json
head.js
prefs =
browser.bookmarks.testing.skipDefaultBookmarksImport=true
[browser_ext_urlbar_attributionURL.js]
[browser_ext_urlbar_clearInput.js]

View file

@ -423,6 +423,8 @@ class _QuickSuggestTestUtils {
* Whether the result is expected to be sponsored.
* @param {boolean} [options.isBestMatch]
* Whether the result is expected to be a best match.
* @param {boolean} [options.isEmptyDescription]
* Whether the description text is empty or not.
* @returns {result}
* The quick suggest result.
*/
@ -433,6 +435,7 @@ class _QuickSuggestTestUtils {
index = -1,
isSponsored = true,
isBestMatch = false,
isEmptyDescription = false,
} = {}) {
this.Assert.ok(
url || originalUrl,
@ -495,7 +498,7 @@ class _QuickSuggestTestUtils {
this.Assert.ok(sponsoredElement, "Result sponsored label element exists");
this.Assert.equal(
sponsoredElement.textContent,
isSponsored ? "Sponsored" : "",
isSponsored && !isEmptyDescription ? "Sponsored" : "",
"Result sponsored label"
);
} else {

View file

@ -8,6 +8,8 @@ support-files =
searchSuggestionEngine.xml
searchSuggestionEngine.sjs
subdialog.xhtml
prefs =
browser.bookmarks.testing.skipDefaultBookmarksImport=true
[browser_quicksuggest.js]
[browser_quicksuggest_addons.js]

View file

@ -86,3 +86,77 @@ add_task(async function nonSponsored() {
});
await UrlbarTestUtils.promisePopupClose(window);
});
// Tests sponsored priority feature.
add_task(async function sponsoredPriority() {
const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({
quickSuggestSponsoredPriority: true,
});
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: "fra",
});
await QuickSuggestTestUtils.assertIsQuickSuggest({
window,
index: 1,
isSponsored: true,
isBestMatch: true,
isEmptyDescription: true,
url: `${TEST_URL}?q=frabbits`,
});
let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
Assert.equal(
row.querySelector(".urlbarView-title").firstChild.textContent,
"fra",
"The part of the keyword that matches users input is not bold."
);
Assert.equal(
row.querySelector(".urlbarView-title > strong").textContent,
"b",
"The auto completed section of the keyword is bolded."
);
// Group label.
let before = window.getComputedStyle(row, "::before");
Assert.equal(before.content, "attr(label)", "::before.content is enabled");
Assert.equal(
row.getAttribute("label"),
"Sponsored",
"Row has 'Sponsored' group label"
);
await UrlbarTestUtils.promisePopupClose(window);
await cleanUpNimbus();
});
// Tests sponsored priority feature does not affect to non-sponsored suggestion.
add_task(async function sponsoredPriorityButNotSponsoredSuggestion() {
const cleanUpNimbus = await UrlbarTestUtils.initNimbusFeature({
quickSuggestSponsoredPriority: true,
});
await UrlbarTestUtils.promiseAutocompleteResultPopup({
window,
value: "nonspon",
});
await QuickSuggestTestUtils.assertIsQuickSuggest({
window,
index: 1,
isSponsored: false,
url: `${TEST_URL}?q=nonsponsored`,
});
let row = await UrlbarTestUtils.waitForAutocompleteResultAt(window, 1);
let before = window.getComputedStyle(row, "::before");
Assert.equal(before.content, "attr(label)", "::before.content is enabled");
Assert.equal(
row.getAttribute("label"),
"Firefox Suggest",
"Row has general group label for quick suggest"
);
await UrlbarTestUtils.promisePopupClose(window);
await cleanUpNimbus();
});

View file

@ -294,6 +294,7 @@ async function doTest({
recordNavigationalSuggestionTelemetry: true,
},
}) {
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
MerinoTestUtils.server.response.body.suggestions = suggestion
? [suggestion]
: [];

View file

@ -35,6 +35,12 @@ ChromeUtils.defineLazyGetter(this, "MerinoTestUtils", () => {
return module;
});
ChromeUtils.defineLazyGetter(this, "PlacesFrecencyRecalculator", () => {
return Cc["@mozilla.org/places/frecency-recalculator;1"].getService(
Ci.nsIObserver
).wrappedJSObject;
});
registerCleanupFunction(async () => {
// Ensure the popup is always closed at the end of each test to avoid
// interfering with the next test.

View file

@ -1373,3 +1373,86 @@ add_task(async function remoteSettingsDataType() {
await cleanUpNimbus();
}
});
// For priority sponsored suggestion,
// always isBestMatch will be true and suggestIndex will be 1.
// It does not have descriptionL10n.
const EXPECTED_SPONSORED_PRIORITY_RESULT = {
...EXPECTED_SPONSORED_RESULT,
isBestMatch: true,
payload: {
...EXPECTED_SPONSORED_RESULT.payload,
descriptionL10n: undefined,
},
};
add_task(async function sponsoredPriority_normal() {
await doSponsoredPriorityTest({
searchWord: SPONSORED_SEARCH_STRING,
remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
});
});
add_task(async function sponsoredPriority_nonsponsoredSuggestion() {
// Not affect to except sponsored suggestion.
await doSponsoredPriorityTest({
searchWord: NONSPONSORED_SEARCH_STRING,
remoteSettingsData: [REMOTE_SETTINGS_RESULTS[1]],
expectedMatches: [EXPECTED_NONSPONSORED_RESULT],
});
});
add_task(async function sponsoredPriority_sponsoredIndex() {
await doSponsoredPriorityTest({
nimbusSettings: { quickSuggestSponsoredIndex: 2 },
searchWord: SPONSORED_SEARCH_STRING,
remoteSettingsData: [REMOTE_SETTINGS_RESULTS[0]],
expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
});
});
add_task(async function sponsoredPriority_position() {
await doSponsoredPriorityTest({
nimbusSettings: { quickSuggestAllowPositionInSuggestions: true },
searchWord: SPONSORED_SEARCH_STRING,
remoteSettingsData: [
Object.assign({}, REMOTE_SETTINGS_RESULTS[0], { position: 2 }),
],
expectedMatches: [EXPECTED_SPONSORED_PRIORITY_RESULT],
});
});
async function doSponsoredPriorityTest({
remoteSettingsConfig = {},
nimbusSettings = {},
searchWord,
remoteSettingsData,
expectedMatches,
}) {
UrlbarPrefs.set("suggest.quicksuggest.nonsponsored", true);
UrlbarPrefs.set("suggest.quicksuggest.sponsored", true);
const cleanUpNimbusEnable = await UrlbarTestUtils.initNimbusFeature({
...nimbusSettings,
quickSuggestSponsoredPriority: true,
});
await QuickSuggestTestUtils.setRemoteSettingsResults([
{
type: "data",
attachment: remoteSettingsData,
},
]);
await QuickSuggestTestUtils.setConfig(remoteSettingsConfig);
await check_results({
context: createContext(searchWord, {
providers: [UrlbarProviderQuickSuggest.name],
isPrivate: false,
}),
matches: expectedMatches,
});
await cleanUpNimbusEnable();
}

View file

@ -872,7 +872,7 @@ async function check_results({
// updates.
// This is not a problem in real life, but autocomplete tests should
// return reliable resultsets, thus we have to wait.
await PlacesTestUtils.promiseAsyncUpdates();
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
const controller = UrlbarTestUtils.newMockController({
input: {

View file

@ -30,6 +30,7 @@ add_task(async function () {
}
async function check_autofill() {
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let threshold = await getOriginAutofillThreshold();
let httpOriginFrecency = await getOriginFrecency("http://", host);
Assert.less(

View file

@ -1025,7 +1025,7 @@ async function doTitleTest({ visits, input, expected }) {
url: uri,
}
);
await db.executeCached("DELETE FROM moz_updateoriginsupdate_temp");
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
});
}

View file

@ -946,7 +946,7 @@ add_autofill_task(async function zeroThreshold() {
await db.execute("UPDATE moz_places SET frecency = -1 WHERE url = :url", {
url: pageUrl,
});
await db.executeCached("DELETE FROM moz_updateoriginsupdate_temp");
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
});
// Make sure the place's frecency is -1.
@ -1120,14 +1120,14 @@ add_autofill_task(async function suggestHistoryFalse_bookmark_0() {
// Make the bookmark fall below the autofill frecency threshold so we ensure
// the bookmark is always autofilled in this case, even if it doesn't meet
// the threshold.
let meetsThreshold = true;
while (meetsThreshold) {
await TestUtils.waitForCondition(async () => {
// Add a visit to another origin to boost the threshold.
await PlacesTestUtils.addVisits("http://foo-" + url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let originFrecency = await getOriginFrecency("http://", host);
let threshold = await getOriginAutofillThreshold();
meetsThreshold = threshold <= originFrecency;
}
return threshold > originFrecency;
}, "Make the bookmark fall below the frecency threshold");
// At this point, the bookmark doesn't meet the threshold, but it should
// still be autofilled.
@ -1227,14 +1227,14 @@ add_autofill_task(async function suggestHistoryFalse_bookmark_prefix_0() {
// Make the bookmark fall below the autofill frecency threshold so we ensure
// the bookmark is always autofilled in this case, even if it doesn't meet
// the threshold.
let meetsThreshold = true;
while (meetsThreshold) {
await TestUtils.waitForCondition(async () => {
// Add a visit to another origin to boost the threshold.
await PlacesTestUtils.addVisits("http://foo-" + url);
await PlacesFrecencyRecalculator.recalculateAnyOutdatedFrecencies();
let originFrecency = await getOriginFrecency("http://", host);
let threshold = await getOriginAutofillThreshold();
meetsThreshold = threshold <= originFrecency;
}
return threshold > originFrecency;
}, "Make the bookmark fall below the frecency threshold");
// At this point, the bookmark doesn't meet the threshold, but it should
// still be autofilled.

View file

@ -74,6 +74,7 @@ add_task(
false,
"Search Service should have failed to initialize."
);
await cleanupPlaces();
}
);

View file

@ -256,6 +256,12 @@ let AVAILABLE_PIP_OVERRIDES;
},
},
viki: {
"https://*.viki.com/*": {
videoWrapperScriptPath: "video-wrappers/videojsWrapper.js",
},
},
voot: {
"https://*.voot.com/*": {
videoWrapperScriptPath: "video-wrappers/voot.js",

Some files were not shown because too many files have changed in this diff Show more