Bug 1656220 - Implement recording attributions for search engines. r=dao
Differential Revision: https://phabricator.services.mozilla.com/D87501
This commit is contained in:
@@ -253,6 +253,7 @@ let ContentSearch = {
|
|||||||
}
|
}
|
||||||
win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey, {
|
win.BrowserSearch.recordSearchInTelemetry(engine, data.healthReportKey, {
|
||||||
selection: data.selection,
|
selection: data.selection,
|
||||||
|
url: submission.uri,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1313,7 +1313,8 @@ pref("prompts.tab_modal.enabled", true);
|
|||||||
pref("prompts.defaultModalType", 3);
|
pref("prompts.defaultModalType", 3);
|
||||||
|
|
||||||
pref("browser.topsites.useRemoteSetting", false);
|
pref("browser.topsites.useRemoteSetting", false);
|
||||||
pref("browser.topsites.attributionURL", "");
|
|
||||||
|
pref("browser.partnerlink.attributionURL", "https://topsites.mozilla.io/cid/amzn_2020_a1");
|
||||||
|
|
||||||
// Whether to show tab level system prompts opened via nsIPrompt(Service) as
|
// Whether to show tab level system prompts opened via nsIPrompt(Service) as
|
||||||
// SubDialogs in the TabDialogBox (true) or as TabModalPrompt in the
|
// SubDialogs in the TabDialogBox (true) or as TabModalPrompt in the
|
||||||
|
|||||||
@@ -4346,7 +4346,7 @@ const BrowserSearch = {
|
|||||||
csp,
|
csp,
|
||||||
});
|
});
|
||||||
|
|
||||||
return engine;
|
return { engine, url: submission.uri };
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -4356,7 +4356,7 @@ const BrowserSearch = {
|
|||||||
* BrowserSearch.loadSearch for the preferred API.
|
* BrowserSearch.loadSearch for the preferred API.
|
||||||
*/
|
*/
|
||||||
async loadSearchFromContext(terms, usePrivate, triggeringPrincipal, csp) {
|
async loadSearchFromContext(terms, usePrivate, triggeringPrincipal, csp) {
|
||||||
let engine = await BrowserSearch._loadSearch(
|
let { engine, url } = await BrowserSearch._loadSearch(
|
||||||
terms,
|
terms,
|
||||||
usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)
|
usePrivate && !PrivateBrowsingUtils.isWindowPrivate(window)
|
||||||
? "window"
|
? "window"
|
||||||
@@ -4369,7 +4369,7 @@ const BrowserSearch = {
|
|||||||
csp
|
csp
|
||||||
);
|
);
|
||||||
if (engine) {
|
if (engine) {
|
||||||
BrowserSearch.recordSearchInTelemetry(engine, "contextmenu");
|
BrowserSearch.recordSearchInTelemetry(engine, "contextmenu", { url });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -4377,7 +4377,7 @@ const BrowserSearch = {
|
|||||||
* Perform a search initiated from the command line.
|
* Perform a search initiated from the command line.
|
||||||
*/
|
*/
|
||||||
async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
|
async loadSearchFromCommandLine(terms, usePrivate, triggeringPrincipal, csp) {
|
||||||
let engine = await BrowserSearch._loadSearch(
|
let { engine, url } = await BrowserSearch._loadSearch(
|
||||||
terms,
|
terms,
|
||||||
"current",
|
"current",
|
||||||
usePrivate,
|
usePrivate,
|
||||||
@@ -4386,7 +4386,7 @@ const BrowserSearch = {
|
|||||||
csp
|
csp
|
||||||
);
|
);
|
||||||
if (engine) {
|
if (engine) {
|
||||||
BrowserSearch.recordSearchInTelemetry(engine, "system");
|
BrowserSearch.recordSearchInTelemetry(engine, "system", { url });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ this.search = class extends ExtensionAPI {
|
|||||||
BrowserUsageTelemetry.recordSearch(
|
BrowserUsageTelemetry.recordSearch(
|
||||||
tabbrowser,
|
tabbrowser,
|
||||||
engine,
|
engine,
|
||||||
"webextension"
|
"webextension",
|
||||||
|
{ url: submission.uri }
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -421,6 +421,7 @@
|
|||||||
isOneOff: aOneOff,
|
isOneOff: aOneOff,
|
||||||
isSuggestion: !aOneOff && telemetrySearchDetails,
|
isSuggestion: !aOneOff && telemetrySearchDetails,
|
||||||
selection: telemetrySearchDetails,
|
selection: telemetrySearchDetails,
|
||||||
|
url: submission.uri,
|
||||||
};
|
};
|
||||||
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
|
BrowserSearch.recordSearchInTelemetry(engine, "searchbar", details);
|
||||||
// null parameter below specifies HTML response for search
|
// null parameter below specifies HTML response for search
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ class UrlbarInput {
|
|||||||
selectedOneOff.engine,
|
selectedOneOff.engine,
|
||||||
searchString
|
searchString
|
||||||
);
|
);
|
||||||
this._recordSearch(selectedOneOff.engine, event);
|
this._recordSearch(selectedOneOff.engine, event, { url });
|
||||||
|
|
||||||
UrlbarUtils.addToFormHistory(
|
UrlbarUtils.addToFormHistory(
|
||||||
this,
|
this,
|
||||||
@@ -776,6 +776,7 @@ class UrlbarInput {
|
|||||||
isSuggestion: !!result.payload.suggestion,
|
isSuggestion: !!result.payload.suggestion,
|
||||||
isFormHistory: result.source == UrlbarUtils.RESULT_SOURCE.HISTORY,
|
isFormHistory: result.source == UrlbarUtils.RESULT_SOURCE.HISTORY,
|
||||||
alias: result.payload.keyword,
|
alias: result.payload.keyword,
|
||||||
|
url,
|
||||||
};
|
};
|
||||||
const engine = Services.search.getEngineByName(result.payload.engine);
|
const engine = Services.search.getEngineByName(result.payload.engine);
|
||||||
this._recordSearch(engine, event, actionDetails);
|
this._recordSearch(engine, event, actionDetails);
|
||||||
@@ -1825,6 +1826,8 @@ class UrlbarInput {
|
|||||||
* True if this query was initiated via a search alias.
|
* True if this query was initiated via a search alias.
|
||||||
* @param {boolean} searchActionDetails.isFormHistory
|
* @param {boolean} searchActionDetails.isFormHistory
|
||||||
* True if this query was initiated from a form history result.
|
* True if this query was initiated from a form history result.
|
||||||
|
* @param {string} searchActionDetails.url
|
||||||
|
* The url this query was triggered with.
|
||||||
*/
|
*/
|
||||||
_recordSearch(engine, event, searchActionDetails = {}) {
|
_recordSearch(engine, event, searchActionDetails = {}) {
|
||||||
const isOneOff = this.view.oneOffSearchButtons.maybeRecordTelemetry(event);
|
const isOneOff = this.view.oneOffSearchButtons.maybeRecordTelemetry(event);
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ XPCOMUtils.defineLazyModuleGetters(this, {
|
|||||||
CustomizableUI: "resource:///modules/CustomizableUI.jsm",
|
CustomizableUI: "resource:///modules/CustomizableUI.jsm",
|
||||||
OS: "resource://gre/modules/osfile.jsm",
|
OS: "resource://gre/modules/osfile.jsm",
|
||||||
PageActions: "resource:///modules/PageActions.jsm",
|
PageActions: "resource:///modules/PageActions.jsm",
|
||||||
|
PartnerLinkAttribution: "resource:///modules/PartnerLinkAttribution.jsm",
|
||||||
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.jsm",
|
||||||
SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
|
SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
|
||||||
Services: "resource://gre/modules/Services.jsm",
|
Services: "resource://gre/modules/Services.jsm",
|
||||||
@@ -604,7 +605,11 @@ let BrowserUsageTelemetry = {
|
|||||||
this._handleSearchAction(engine, source, details);
|
this._handleSearchAction(engine, source, details);
|
||||||
},
|
},
|
||||||
|
|
||||||
_recordSearch(engine, source, action = null) {
|
_recordSearch(engine, url, source, action = null) {
|
||||||
|
PartnerLinkAttribution.makeSearchEngineRequest(engine, url).catch(
|
||||||
|
Cu.reportError
|
||||||
|
);
|
||||||
|
|
||||||
let scalarKey = action ? "search_" + action : "search";
|
let scalarKey = action ? "search_" + action : "search";
|
||||||
Services.telemetry.keyedScalarAdd(
|
Services.telemetry.keyedScalarAdd(
|
||||||
"browser.engagement.navigation." + source,
|
"browser.engagement.navigation." + source,
|
||||||
@@ -626,15 +631,15 @@ let BrowserUsageTelemetry = {
|
|||||||
this._handleSearchAndUrlbar(engine, source, details);
|
this._handleSearchAndUrlbar(engine, source, details);
|
||||||
break;
|
break;
|
||||||
case "abouthome":
|
case "abouthome":
|
||||||
this._recordSearch(engine, "about_home", "enter");
|
this._recordSearch(engine, details.url, "about_home", "enter");
|
||||||
break;
|
break;
|
||||||
case "newtab":
|
case "newtab":
|
||||||
this._recordSearch(engine, "about_newtab", "enter");
|
this._recordSearch(engine, details.url, "about_newtab", "enter");
|
||||||
break;
|
break;
|
||||||
case "contextmenu":
|
case "contextmenu":
|
||||||
case "system":
|
case "system":
|
||||||
case "webextension":
|
case "webextension":
|
||||||
this._recordSearch(engine, source);
|
this._recordSearch(engine, details.url, source);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -665,27 +670,27 @@ let BrowserUsageTelemetry = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If that's a legit one-off search signal, record it using the relative key.
|
// If that's a legit one-off search signal, record it using the relative key.
|
||||||
this._recordSearch(engine, sourceName, "oneoff");
|
this._recordSearch(engine, details.url, sourceName, "oneoff");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The search was not a one-off. It was a search with the default search engine.
|
// The search was not a one-off. It was a search with the default search engine.
|
||||||
if (details.isFormHistory) {
|
if (details.isFormHistory) {
|
||||||
// It came from a form history result.
|
// It came from a form history result.
|
||||||
this._recordSearch(engine, sourceName, "formhistory");
|
this._recordSearch(engine, details.url, sourceName, "formhistory");
|
||||||
return;
|
return;
|
||||||
} else if (details.isSuggestion) {
|
} else if (details.isSuggestion) {
|
||||||
// It came from a suggested search, so count it as such.
|
// It came from a suggested search, so count it as such.
|
||||||
this._recordSearch(engine, sourceName, "suggestion");
|
this._recordSearch(engine, details.url, sourceName, "suggestion");
|
||||||
return;
|
return;
|
||||||
} else if (details.alias) {
|
} else if (details.alias) {
|
||||||
// This one came from a search that used an alias.
|
// This one came from a search that used an alias.
|
||||||
this._recordSearch(engine, sourceName, "alias");
|
this._recordSearch(engine, details.url, sourceName, "alias");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// The search signal was generated by typing something and pressing enter.
|
// The search signal was generated by typing something and pressing enter.
|
||||||
this._recordSearch(engine, sourceName, "enter");
|
this._recordSearch(engine, details.url, sourceName, "enter");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -8,17 +8,14 @@ Cu.importGlobalProperties(["fetch"]);
|
|||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["PartnerLinkAttribution"];
|
var EXPORTED_SYMBOLS = ["PartnerLinkAttribution"];
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
this,
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
"Services",
|
|
||||||
"resource://gre/modules/Services.jsm"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
ChromeUtils.defineModuleGetter(
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
this,
|
Services: "resource://gre/modules/Services.jsm",
|
||||||
"Region",
|
Region: "resource://gre/modules/Region.jsm",
|
||||||
"resource://gre/modules/Region.jsm"
|
});
|
||||||
);
|
|
||||||
|
|
||||||
var PartnerLinkAttribution = {
|
var PartnerLinkAttribution = {
|
||||||
async makeRequest({ targetURL, source }) {
|
async makeRequest({ targetURL, source }) {
|
||||||
@@ -34,7 +31,7 @@ var PartnerLinkAttribution = {
|
|||||||
|
|
||||||
const attributionUrl = Services.prefs.getStringPref(
|
const attributionUrl = Services.prefs.getStringPref(
|
||||||
Services.prefs.getBoolPref("browser.topsites.useRemoteSetting")
|
Services.prefs.getBoolPref("browser.topsites.useRemoteSetting")
|
||||||
? "browser.topsites.attributionURL"
|
? "browser.partnerlink.attributionURL"
|
||||||
: `browser.newtabpage.searchTileOverride.${partner}.attributionURL`,
|
: `browser.newtabpage.searchTileOverride.${partner}.attributionURL`,
|
||||||
""
|
""
|
||||||
);
|
);
|
||||||
@@ -42,15 +39,67 @@ var PartnerLinkAttribution = {
|
|||||||
record("attribution", "abort");
|
record("attribution", "abort");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const request = new Request(attributionUrl);
|
let result = await sendRequest(attributionUrl, source, targetURL);
|
||||||
request.headers.set("X-Region", Region.home);
|
record("attribution", result ? "success" : "failure");
|
||||||
request.headers.set("X-Source", source);
|
},
|
||||||
request.headers.set("X-Target-URL", targetURL);
|
|
||||||
const response = await fetch(request);
|
/**
|
||||||
record("attribution", response.ok ? "success" : "failure");
|
* Makes a request to the attribution URL for a search engine search.
|
||||||
|
*
|
||||||
|
* @param {nsISearchEngine} engine
|
||||||
|
* The search engine to save the attribution for.
|
||||||
|
* @param {nsIURI} targetUrl
|
||||||
|
* The target URL to filter and include in the attribution.
|
||||||
|
*/
|
||||||
|
async makeSearchEngineRequest(engine, targetUrl) {
|
||||||
|
if (!engine.sendAttributionRequest) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let searchUrlQueryParamName = engine.searchUrlQueryParamName;
|
||||||
|
if (!searchUrlQueryParamName) {
|
||||||
|
Cu.reportError("makeSearchEngineRequest can't find search terms key");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = targetUrl;
|
||||||
|
if (typeof url == "string") {
|
||||||
|
url = Services.io.newURI(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
let targetParams = new URLSearchParams(url.query);
|
||||||
|
if (!targetParams.has(searchUrlQueryParamName)) {
|
||||||
|
Cu.reportError(
|
||||||
|
"makeSearchEngineRequest can't remove target search terms"
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const attributionUrl = Services.prefs.getStringPref(
|
||||||
|
"browser.partnerlink.attributionURL",
|
||||||
|
""
|
||||||
|
);
|
||||||
|
|
||||||
|
targetParams.delete(searchUrlQueryParamName);
|
||||||
|
let strippedTargetUrl = `${url.prePath}${url.filePath}`;
|
||||||
|
let newParams = targetParams.toString();
|
||||||
|
if (newParams) {
|
||||||
|
strippedTargetUrl += "?" + newParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
await sendRequest(attributionUrl, "searchurl", strippedTargetUrl);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function sendRequest(attributionUrl, source, targetURL) {
|
||||||
|
const request = new Request(attributionUrl);
|
||||||
|
request.headers.set("X-Region", Region.home);
|
||||||
|
request.headers.set("X-Source", source);
|
||||||
|
request.headers.set("X-Target-URL", targetURL);
|
||||||
|
const response = await fetch(request);
|
||||||
|
return response.ok;
|
||||||
|
}
|
||||||
|
|
||||||
function recordTelemetryEvent(method, objectString, value, extra) {
|
function recordTelemetryEvent(method, objectString, value, extra) {
|
||||||
Services.telemetry.setEventRecordingEnabled("partner_link", true);
|
Services.telemetry.setEventRecordingEnabled("partner_link", true);
|
||||||
Services.telemetry.recordEvent(
|
Services.telemetry.recordEvent(
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ skip-if = (debug && os == "linux" && bits == 64 && os_version == "18.04") # Bug
|
|||||||
[browser_EveryWindow.js]
|
[browser_EveryWindow.js]
|
||||||
[browser_LiveBookmarkMigrator.js]
|
[browser_LiveBookmarkMigrator.js]
|
||||||
[browser_PageActions.js]
|
[browser_PageActions.js]
|
||||||
|
[browser_PartnerLinkAttribution.js]
|
||||||
|
support-files =
|
||||||
|
search-engines/basic/manifest.json
|
||||||
|
search-engines/simple/manifest.json
|
||||||
|
search-engines/engines.json
|
||||||
[browser_PermissionUI.js]
|
[browser_PermissionUI.js]
|
||||||
[browser_PermissionUI_prompts.js]
|
[browser_PermissionUI_prompts.js]
|
||||||
[browser_preloading_tab_moving.js]
|
[browser_preloading_tab_moving.js]
|
||||||
|
|||||||
286
browser/modules/test/browser/browser_PartnerLinkAttribution.js
Normal file
286
browser/modules/test/browser/browser_PartnerLinkAttribution.js
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file tests urlbar telemetry with search related actions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const SCALAR_URLBAR = "browser.engagement.navigation.urlbar";
|
||||||
|
|
||||||
|
// The preference to enable suggestions in the urlbar.
|
||||||
|
const SUGGEST_URLBAR_PREF = "browser.urlbar.suggest.searches";
|
||||||
|
// The name of the search engine used to generate suggestions.
|
||||||
|
const SUGGESTION_ENGINE_NAME =
|
||||||
|
"browser_UsageTelemetry usageTelemetrySearchSuggestions.xml";
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
CustomizableUITestUtils:
|
||||||
|
"resource://testing-common/CustomizableUITestUtils.jsm",
|
||||||
|
Region: "resource://gre/modules/Region.jsm",
|
||||||
|
SearchTelemetry: "resource:///modules/SearchTelemetry.jsm",
|
||||||
|
SearchTestUtils: "resource://testing-common/SearchTestUtils.jsm",
|
||||||
|
UrlbarTestUtils: "resource://testing-common/UrlbarTestUtils.jsm",
|
||||||
|
HttpServer: "resource://testing-common/httpd.js",
|
||||||
|
});
|
||||||
|
|
||||||
|
let gCUITestUtils = new CustomizableUITestUtils(window);
|
||||||
|
SearchTestUtils.init(Assert, registerCleanupFunction);
|
||||||
|
|
||||||
|
var gHttpServer = null;
|
||||||
|
var gRequests = [];
|
||||||
|
|
||||||
|
function submitHandler(request, response) {
|
||||||
|
gRequests.push(request);
|
||||||
|
response.setStatusLine(request.httpVersion, 200, "Ok");
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function setup() {
|
||||||
|
// Ensure the initial init is complete.
|
||||||
|
await Services.search.init();
|
||||||
|
|
||||||
|
// Clear history so that history added by previous tests doesn't mess up this
|
||||||
|
// test when it selects results in the urlbar.
|
||||||
|
await PlacesUtils.history.clear();
|
||||||
|
|
||||||
|
let searchExtensions = getChromeDir(getResolvedURI(gTestPath));
|
||||||
|
searchExtensions.append("search-engines");
|
||||||
|
|
||||||
|
await SearchTestUtils.useMochitestEngines(searchExtensions);
|
||||||
|
|
||||||
|
SearchTestUtils.useMockIdleService();
|
||||||
|
let response = await fetch(`resource://search-extensions/engines.json`);
|
||||||
|
let json = await response.json();
|
||||||
|
await SearchTestUtils.updateRemoteSettingsConfig(json.data);
|
||||||
|
|
||||||
|
gHttpServer = new HttpServer();
|
||||||
|
gHttpServer.registerPathHandler("/", submitHandler);
|
||||||
|
gHttpServer.start(-1);
|
||||||
|
|
||||||
|
await SpecialPowers.pushPrefEnv({
|
||||||
|
set: [
|
||||||
|
// Enable search suggestions in the urlbar.
|
||||||
|
[SUGGEST_URLBAR_PREF, true],
|
||||||
|
// Clear historical search suggestions to avoid interference from previous
|
||||||
|
// tests.
|
||||||
|
["browser.urlbar.maxHistoricalSearchSuggestions", 0],
|
||||||
|
// Use the default matching bucket configuration.
|
||||||
|
["browser.urlbar.matchBuckets", "general:5,suggestion:4"],
|
||||||
|
//
|
||||||
|
[
|
||||||
|
"browser.partnerlink.attributionURL",
|
||||||
|
`http://localhost:${gHttpServer.identity.primaryPort}/`,
|
||||||
|
],
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
await gCUITestUtils.addSearchBar();
|
||||||
|
|
||||||
|
// Make sure to restore the engine once we're done.
|
||||||
|
registerCleanupFunction(async function() {
|
||||||
|
await SearchTestUtils.updateRemoteSettingsConfig();
|
||||||
|
await gHttpServer.stop();
|
||||||
|
gHttpServer = null;
|
||||||
|
await PlacesUtils.history.clear();
|
||||||
|
gCUITestUtils.removeSearchBar();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function searchInAwesomebar(value, win = window) {
|
||||||
|
return UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||||
|
window: win,
|
||||||
|
waitForFocus,
|
||||||
|
value,
|
||||||
|
fireInputEvent: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_simpleQuery_no_attribution() {
|
||||||
|
await Services.search.setDefault(
|
||||||
|
Services.search.getEngineByName("Simple Engine")
|
||||||
|
);
|
||||||
|
|
||||||
|
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||||
|
gBrowser,
|
||||||
|
"about:blank"
|
||||||
|
);
|
||||||
|
|
||||||
|
info("Simulate entering a simple search.");
|
||||||
|
let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||||
|
"https://example.com/?sourceId=Mozilla-search&search=simple+query",
|
||||||
|
tab
|
||||||
|
);
|
||||||
|
await searchInAwesomebar("simple query");
|
||||||
|
EventUtils.synthesizeKey("KEY_Enter");
|
||||||
|
await promiseLoad;
|
||||||
|
|
||||||
|
await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
|
||||||
|
|
||||||
|
Assert.equal(gRequests.length, 0, "Should not have submitted an attribution");
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab);
|
||||||
|
|
||||||
|
await Services.search.setDefault(Services.search.getEngineByName("basic"));
|
||||||
|
});
|
||||||
|
|
||||||
|
async function checkAttributionRecorded(actionFn, cleanupFn) {
|
||||||
|
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||||
|
gBrowser,
|
||||||
|
"data:text/plain;charset=utf8,simple%20query"
|
||||||
|
);
|
||||||
|
|
||||||
|
let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||||
|
"https://mochi.test:8888/browser/browser/components/search/test/browser/?search=simple+query&foo=1",
|
||||||
|
tab
|
||||||
|
);
|
||||||
|
await actionFn(tab);
|
||||||
|
await promiseLoad;
|
||||||
|
|
||||||
|
await BrowserTestUtils.waitForCondition(
|
||||||
|
() => gRequests.length == 1,
|
||||||
|
"Should have received an attribution submission"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("x-region"),
|
||||||
|
Region.home,
|
||||||
|
"Should have set the region correctly"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("X-Source"),
|
||||||
|
"searchurl",
|
||||||
|
"Should have set the source correctly"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("X-Target-url"),
|
||||||
|
"https://mochi.test:8888/browser/browser/components/search/test/browser/?foo=1",
|
||||||
|
"Should have set the target url correctly and stripped the search terms"
|
||||||
|
);
|
||||||
|
if (cleanupFn) {
|
||||||
|
await cleanupFn();
|
||||||
|
}
|
||||||
|
BrowserTestUtils.removeTab(tab);
|
||||||
|
gRequests = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test_urlbar() {
|
||||||
|
await checkAttributionRecorded(async tab => {
|
||||||
|
await searchInAwesomebar("simple query");
|
||||||
|
EventUtils.synthesizeKey("KEY_Enter");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_searchbar() {
|
||||||
|
await checkAttributionRecorded(async tab => {
|
||||||
|
let sb = BrowserSearch.searchBar;
|
||||||
|
// Write the search query in the searchbar.
|
||||||
|
sb.focus();
|
||||||
|
sb.value = "simple query";
|
||||||
|
sb.textbox.controller.startSearch("simple query");
|
||||||
|
// Wait for the popup to show.
|
||||||
|
await BrowserTestUtils.waitForEvent(sb.textbox.popup, "popupshown");
|
||||||
|
// And then for the search to complete.
|
||||||
|
await BrowserTestUtils.waitForCondition(
|
||||||
|
() =>
|
||||||
|
sb.textbox.controller.searchStatus >=
|
||||||
|
Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH,
|
||||||
|
"The search in the searchbar must complete."
|
||||||
|
);
|
||||||
|
EventUtils.synthesizeKey("KEY_Enter");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_context_menu() {
|
||||||
|
let contextMenu;
|
||||||
|
await checkAttributionRecorded(
|
||||||
|
async tab => {
|
||||||
|
info("Select all the text in the page.");
|
||||||
|
await SpecialPowers.spawn(tab.linkedBrowser, [""], async function() {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
content.document.addEventListener(
|
||||||
|
"selectionchange",
|
||||||
|
() => resolve(),
|
||||||
|
{
|
||||||
|
once: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
content.document
|
||||||
|
.getSelection()
|
||||||
|
.selectAllChildren(content.document.body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Open the context menu.");
|
||||||
|
contextMenu = document.getElementById("contentAreaContextMenu");
|
||||||
|
let popupPromise = BrowserTestUtils.waitForEvent(
|
||||||
|
contextMenu,
|
||||||
|
"popupshown"
|
||||||
|
);
|
||||||
|
BrowserTestUtils.synthesizeMouseAtCenter(
|
||||||
|
"body",
|
||||||
|
{ type: "contextmenu", button: 2 },
|
||||||
|
gBrowser.selectedBrowser
|
||||||
|
);
|
||||||
|
await popupPromise;
|
||||||
|
|
||||||
|
info("Click on search.");
|
||||||
|
let searchItem = contextMenu.getElementsByAttribute(
|
||||||
|
"id",
|
||||||
|
"context-searchselect"
|
||||||
|
)[0];
|
||||||
|
searchItem.click();
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
contextMenu.hidePopup();
|
||||||
|
BrowserTestUtils.removeTab(gBrowser.selectedTab);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
add_task(async function test_about_newtab() {
|
||||||
|
let tab = await BrowserTestUtils.openNewForegroundTab(
|
||||||
|
gBrowser,
|
||||||
|
"about:newtab",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
await SpecialPowers.spawn(tab.linkedBrowser, [], async function() {
|
||||||
|
await ContentTaskUtils.waitForCondition(() => !content.document.hidden);
|
||||||
|
});
|
||||||
|
|
||||||
|
info("Trigger a simple serch, just text + enter.");
|
||||||
|
let promiseLoad = BrowserTestUtils.waitForDocLoadAndStopIt(
|
||||||
|
"https://mochi.test:8888/browser/browser/components/search/test/browser/?search=simple+query&foo=1",
|
||||||
|
tab
|
||||||
|
);
|
||||||
|
await typeInSearchField(
|
||||||
|
tab.linkedBrowser,
|
||||||
|
"simple query",
|
||||||
|
"newtab-search-text"
|
||||||
|
);
|
||||||
|
await BrowserTestUtils.synthesizeKey("VK_RETURN", {}, tab.linkedBrowser);
|
||||||
|
await promiseLoad;
|
||||||
|
|
||||||
|
await BrowserTestUtils.waitForCondition(
|
||||||
|
() => gRequests.length == 1,
|
||||||
|
"Should have received an attribution submission"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("x-region"),
|
||||||
|
Region.home,
|
||||||
|
"Should have set the region correctly"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("X-Source"),
|
||||||
|
"searchurl",
|
||||||
|
"Should have set the source correctly"
|
||||||
|
);
|
||||||
|
Assert.equal(
|
||||||
|
gRequests[0].getHeader("X-Target-url"),
|
||||||
|
"https://mochi.test:8888/browser/browser/components/search/test/browser/?foo=1",
|
||||||
|
"Should have set the target url correctly and stripped the search terms"
|
||||||
|
);
|
||||||
|
|
||||||
|
BrowserTestUtils.removeTab(tab);
|
||||||
|
gRequests = [];
|
||||||
|
});
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "basic",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "basic",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "basic@search.mozilla.org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hidden": true,
|
||||||
|
"chrome_settings_overrides": {
|
||||||
|
"search_provider": {
|
||||||
|
"name": "basic",
|
||||||
|
"search_url": "https://mochi.test:8888/browser/browser/components/search/test/browser/?search={searchTerms}&foo=1",
|
||||||
|
"suggest_url": "https://mochi.test:8888/browser/browser/modules/test/browser/usageTelemetrySearchSuggestions.sjs?{searchTerms}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
browser/modules/test/browser/search-engines/engines.json
Normal file
24
browser/modules/test/browser/search-engines/engines.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"webExtension": {
|
||||||
|
"id":"basic@search.mozilla.org"
|
||||||
|
},
|
||||||
|
"telemetryId": "telemetry",
|
||||||
|
"appliesTo": [{
|
||||||
|
"included": { "everywhere": true },
|
||||||
|
"default": "yes",
|
||||||
|
"sendAttributionRequest": true
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"webExtension": {
|
||||||
|
"id":"simple@search.mozilla.org"
|
||||||
|
},
|
||||||
|
"appliesTo": [{
|
||||||
|
"included": { "everywhere": true },
|
||||||
|
"default": "yes"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "Simple Engine",
|
||||||
|
"manifest_version": 2,
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Simple engine with a different name from the WebExtension id prefix",
|
||||||
|
"applications": {
|
||||||
|
"gecko": {
|
||||||
|
"id": "simple@search.mozilla.org"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"hidden": true,
|
||||||
|
"chrome_settings_overrides": {
|
||||||
|
"search_provider": {
|
||||||
|
"name": "Simple Engine",
|
||||||
|
"search_url": "https://example.com",
|
||||||
|
"params": [
|
||||||
|
{
|
||||||
|
"name": "sourceId",
|
||||||
|
"value": "Mozilla-search"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "search",
|
||||||
|
"value": "{searchTerms}"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"suggest_url": "https://example.com?search={searchTerms}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -622,6 +622,9 @@ class SearchEngine {
|
|||||||
_definedAliases = [];
|
_definedAliases = [];
|
||||||
// The urls associated with this engine.
|
// The urls associated with this engine.
|
||||||
_urls = [];
|
_urls = [];
|
||||||
|
// The query parameter name of the search url, cached in memory to avoid
|
||||||
|
// repeated look-ups.
|
||||||
|
_searchUrlQueryParamName = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor.
|
* Constructor.
|
||||||
@@ -1506,6 +1509,32 @@ class SearchEngine {
|
|||||||
return url.getSubmission(submissionData, this, purpose);
|
return url.getSubmission(submissionData, this, purpose);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get searchUrlQueryParamName() {
|
||||||
|
if (this._searchUrlQueryParamName != null) {
|
||||||
|
return this._searchUrlQueryParamName;
|
||||||
|
}
|
||||||
|
|
||||||
|
let submission = this.getSubmission(
|
||||||
|
"{searchTerms}",
|
||||||
|
SearchUtils.URL_TYPE.SEARCH
|
||||||
|
);
|
||||||
|
|
||||||
|
if (submission.postData) {
|
||||||
|
Cu.reportError("searchUrlQueryParamName can't handle POST urls.");
|
||||||
|
return (this._searchUrlQueryParamName = "");
|
||||||
|
}
|
||||||
|
|
||||||
|
let queryParams = new URLSearchParams(submission.uri.query);
|
||||||
|
let searchUrlQueryParamName = "";
|
||||||
|
for (let [key, value] of queryParams) {
|
||||||
|
if (value == "{searchTerms}") {
|
||||||
|
searchUrlQueryParamName = key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (this._searchUrlQueryParamName = searchUrlQueryParamName);
|
||||||
|
}
|
||||||
|
|
||||||
// from nsISearchEngine
|
// from nsISearchEngine
|
||||||
supportsResponseType(type) {
|
supportsResponseType(type) {
|
||||||
return this._getURLOfType(type) != null;
|
return this._getURLOfType(type) != null;
|
||||||
|
|||||||
@@ -50,6 +50,15 @@ interface nsISearchEngine : nsISupports
|
|||||||
[optional] in AString responseType,
|
[optional] in AString responseType,
|
||||||
[optional] in AString purpose);
|
[optional] in AString purpose);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the parameter used for the search terms for a submission
|
||||||
|
* URL of type `SearchUtils.URL_TYPE.SEARCH`.
|
||||||
|
*
|
||||||
|
* @returns A string which is the name of the parameter, or empty string
|
||||||
|
* if no parameter cannot be found or is not supported (e.g. POST).
|
||||||
|
*/
|
||||||
|
readonly attribute AString searchUrlQueryParamName;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determines whether the engine can return responses in the given
|
* Determines whether the engine can return responses in the given
|
||||||
* MIME type. Returns true if the engine spec has a URL with the
|
* MIME type. Returns true if the engine spec has a URL with the
|
||||||
|
|||||||
@@ -108,6 +108,22 @@ var SearchTestUtils = Object.freeze({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async useMochitestEngines(testDir) {
|
||||||
|
// Replace the path we load search engines from with
|
||||||
|
// the path to our test data.
|
||||||
|
let resProt = Services.io
|
||||||
|
.getProtocolHandler("resource")
|
||||||
|
.QueryInterface(Ci.nsIResProtocolHandler);
|
||||||
|
let originalSubstitution = resProt.getSubstitution("search-extensions");
|
||||||
|
resProt.setSubstitution(
|
||||||
|
"search-extensions",
|
||||||
|
Services.io.newURI("file://" + testDir.path)
|
||||||
|
);
|
||||||
|
gTestGlobals.registerCleanupFunction(() => {
|
||||||
|
resProt.setSubstitution("search-extensions", originalSubstitution);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert a list of engine configurations into engine objects.
|
* Convert a list of engine configurations into engine objects.
|
||||||
*
|
*
|
||||||
@@ -284,10 +300,14 @@ var SearchTestUtils = Object.freeze({
|
|||||||
/**
|
/**
|
||||||
* Simulates an update to the RemoteSettings configuration.
|
* Simulates an update to the RemoteSettings configuration.
|
||||||
*
|
*
|
||||||
* @param {object} config
|
* @param {object} [config]
|
||||||
* The new configuration.
|
* The new configuration.
|
||||||
*/
|
*/
|
||||||
async updateRemoteSettingsConfig(config) {
|
async updateRemoteSettingsConfig(config) {
|
||||||
|
if (!config) {
|
||||||
|
let settings = RemoteSettings(SearchUtils.SETTINGS_KEY);
|
||||||
|
config = await settings.get();
|
||||||
|
}
|
||||||
const reloadObserved = SearchTestUtils.promiseSearchNotification(
|
const reloadObserved = SearchTestUtils.promiseSearchNotification(
|
||||||
"engines-reloaded"
|
"engines-reloaded"
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user