summaryrefslogtreecommitdiffstats
path: root/browser/components/urlbar/UrlbarController.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/urlbar/UrlbarController.sys.mjs')
-rw-r--r--browser/components/urlbar/UrlbarController.sys.mjs121
1 files changed, 82 insertions, 39 deletions
diff --git a/browser/components/urlbar/UrlbarController.sys.mjs b/browser/components/urlbar/UrlbarController.sys.mjs
index 9bfc3a645d..7e4d0ff1c5 100644
--- a/browser/components/urlbar/UrlbarController.sys.mjs
+++ b/browser/components/urlbar/UrlbarController.sys.mjs
@@ -133,6 +133,7 @@ export class UrlbarController {
// notifications related to the previous query.
this.notify(NOTIFICATIONS.QUERY_STARTED, queryContext);
await this.manager.startQuery(queryContext, this);
+
// If the query has been cancelled, onQueryFinished was notified already.
// Note this._lastQueryContextWrapper may have changed in the meanwhile.
if (
@@ -144,6 +145,16 @@ export class UrlbarController {
this.manager.cancelQuery(queryContext);
this.notify(NOTIFICATIONS.QUERY_FINISHED, queryContext);
}
+
+ // Record a potential exposure if the current search string matches one of
+ // the registered keywords.
+ if (!queryContext.isPrivate) {
+ let searchStr = queryContext.trimmedLowerCaseSearchString;
+ if (lazy.UrlbarPrefs.get("potentialExposureKeywords").has(searchStr)) {
+ this.engagementEvent.addPotentialExposure(searchStr);
+ }
+ }
+
return queryContext;
}
@@ -335,7 +346,7 @@ export class UrlbarController {
}
event.preventDefault();
break;
- case KeyEvent.DOM_VK_TAB:
+ case KeyEvent.DOM_VK_TAB: {
// It's always possible to tab through results when the urlbar was
// focused with the mouse or has a search string, or when the view
// already has a selection.
@@ -368,6 +379,7 @@ export class UrlbarController {
event.preventDefault();
}
break;
+ }
case KeyEvent.DOM_VK_PAGE_DOWN:
case KeyEvent.DOM_VK_PAGE_UP:
if (event.ctrlKey) {
@@ -592,8 +604,8 @@ export class UrlbarController {
/**
* Triggers a "dismiss" engagement for the selected result if one is selected
* and it's not the heuristic. Providers that can respond to dismissals of
- * their results should implement `onEngagement()`, handle the dismissal, and
- * call `controller.removeResult()`.
+ * their results should implement `onLegacyEngagement()`, handle the
+ * dismissal, and call `controller.removeResult()`.
*
* @param {Event} event
* The event that triggered dismissal.
@@ -783,13 +795,6 @@ class TelemetryEvent {
interactionType: this._getStartInteractionType(event, searchString),
searchString,
};
-
- this._controller.manager.notifyEngagementChange(
- "start",
- queryContext,
- {},
- this._controller
- );
}
/**
@@ -821,17 +826,31 @@ class TelemetryEvent {
* @param {DOMElement} [details.element] The picked view element.
*/
record(event, details) {
+ // Prevent re-entering `record()`. This can happen because
+ // `#internalRecord()` will notify an engagement to the provider, that may
+ // execute an action blurring the input field. Then both an engagement
+ // and an abandonment would be recorded for the same session.
+ // Nulling out `_startEventInfo` doesn't save us in this case, because it
+ // happens after `#internalRecord()`, and `isSessionOngoing` must be
+ // calculated inside it.
+ if (this.#handlingRecord) {
+ return;
+ }
+
// This should never throw, or it may break the urlbar.
try {
- this._internalRecord(event, details);
+ this.#handlingRecord = true;
+ this.#internalRecord(event, details);
} catch (ex) {
console.error("Could not record event: ", ex);
} finally {
+ this.#handlingRecord = false;
+
// Reset the start event info except for engagements that do not end the
// search session. In that case, the view stays open and further
// engagements are possible and should be recorded when they occur.
// (`details.isSessionOngoing` is not a param; rather, it's set by
- // `_internalRecord()`.)
+ // `#internalRecord()`.)
if (!details.isSessionOngoing) {
this._startEventInfo = null;
this._discarded = false;
@@ -839,19 +858,10 @@ class TelemetryEvent {
}
}
- _internalRecord(event, details) {
+ #internalRecord(event, details) {
const startEventInfo = this._startEventInfo;
if (!this._category || !startEventInfo) {
- if (this._discarded && this._category && details?.selType !== "dismiss") {
- let { queryContext } = this._controller._lastQueryContextWrapper || {};
- this._controller.manager.notifyEngagementChange(
- "discard",
- queryContext,
- {},
- this._controller
- );
- }
return;
}
if (
@@ -938,6 +948,10 @@ class TelemetryEvent {
}
);
+ if (!details.isSessionOngoing) {
+ this.#recordEndOfSessionTelemetry(details.searchString);
+ }
+
if (skipLegacyTelemetry) {
this._controller.manager.notifyEngagementChange(
method,
@@ -1091,23 +1105,6 @@ class TelemetryEvent {
return;
}
- // First check to see if we can record an exposure event
- if (method === "abandonment" || method === "engagement") {
- if (this.#exposureResultTypes.size) {
- let exposure = {
- results: [...this.#exposureResultTypes].sort().join(","),
- };
- this._controller.logger.debug(
- `exposure event: ${JSON.stringify(exposure)}`
- );
- Glean.urlbar.exposure.record(exposure);
- }
-
- // reset the provider list on the controller
- this.#exposureResultTypes.clear();
- this.#tentativeExposureResultTypes.clear();
- }
-
this._controller.logger.info(
`${method} event: ${JSON.stringify(eventInfo)}`
);
@@ -1115,6 +1112,38 @@ class TelemetryEvent {
Glean.urlbar[method].record(eventInfo);
}
+ #recordEndOfSessionTelemetry(searchString) {
+ // exposures
+ if (this.#exposureResultTypes.size) {
+ let exposure = {
+ results: [...this.#exposureResultTypes].sort().join(","),
+ };
+ this._controller.logger.debug(
+ `exposure event: ${JSON.stringify(exposure)}`
+ );
+ Glean.urlbar.exposure.record(exposure);
+ this.#exposureResultTypes.clear();
+ }
+ this.#tentativeExposureResultTypes.clear();
+
+ // potential exposures
+ if (this.#potentialExposureKeywords.size) {
+ let normalizedSearchString = searchString.trim().toLowerCase();
+ for (let keyword of this.#potentialExposureKeywords) {
+ let data = {
+ keyword,
+ terminal: keyword == normalizedSearchString,
+ };
+ this._controller.logger.debug(
+ `potential_exposure event: ${JSON.stringify(data)}`
+ );
+ Glean.urlbar.potentialExposure.record(data);
+ }
+ GleanPings.urlbarPotentialExposure.submit();
+ this.#potentialExposureKeywords.clear();
+ }
+ }
+
/**
* Registers an exposure for a result in the current urlbar session. All
* exposures that are added during a session are recorded in an exposure event
@@ -1164,6 +1193,16 @@ class TelemetryEvent {
this.#tentativeExposureResultTypes.clear();
}
+ /**
+ * Registers a potential exposure in the current urlbar session.
+ *
+ * @param {string} keyword
+ * The keyword that was matched.
+ */
+ addPotentialExposure(keyword) {
+ this.#potentialExposureKeywords.add(keyword);
+ }
+
#getInteractionType(
method,
startEventInfo,
@@ -1350,8 +1389,12 @@ class TelemetryEvent {
}
}
+ // Used to avoid re-entering `record()`.
+ #handlingRecord = false;
+
#previousSearchWordsSet = null;
#exposureResultTypes = new Set();
#tentativeExposureResultTypes = new Set();
+ #potentialExposureKeywords = new Set();
}