Source code

Revision control

Other Tools

1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
* License, v. 2.0. If a copy of the MPL was not distributed with this
3
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
"use strict";
5
6
var EXPORTED_SYMBOLS = ["SearchTelemetryChild"];
7
8
const { ActorChild } = ChromeUtils.import(
10
);
11
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
12
13
const SHARED_DATA_KEY = "SearchTelemetry:ProviderInfo";
14
15
/**
16
* SearchProviders looks after keeping track of the search provider information
17
* received from the main process.
18
*
19
* It is separate to SearchTelemetryChild so that it is not constructed for each
20
* tab, but once per process.
21
*/
22
class SearchProviders {
23
constructor() {
24
this._searchProviderInfo = null;
25
Services.cpmm.sharedData.addEventListener("change", this);
26
}
27
28
/**
29
* Gets the search provider information for any provider with advert information.
30
* If there is nothing in the cache, it will obtain it from shared data.
31
*
32
* @returns {object} Returns the search provider information. @see SearchTelemetry.jsm
33
*/
34
get info() {
35
if (this._searchProviderInfo) {
36
return this._searchProviderInfo;
37
}
38
39
this._searchProviderInfo = Services.cpmm.sharedData.get(SHARED_DATA_KEY);
40
41
if (!this._searchProviderInfo) {
42
return null;
43
}
44
45
// Filter-out non-ad providers so that we're not trying to match against
46
// those unnecessarily.
47
for (let [providerName, info] of Object.entries(this._searchProviderInfo)) {
48
if (!("extraAdServersRegexps" in info)) {
49
delete this._searchProviderInfo[providerName];
50
}
51
}
52
53
return this._searchProviderInfo;
54
}
55
56
/**
57
* Handles events received from sharedData notifications.
58
*
59
* @param {object} event The event details.
60
*/
61
handleEvent(event) {
62
switch (event.type) {
63
case "change": {
64
if (event.changedKeys.includes(SHARED_DATA_KEY)) {
65
// Just null out the provider information for now, we'll fetch it next
66
// time we need it.
67
this._searchProviderInfo = null;
68
}
69
break;
70
}
71
}
72
}
73
}
74
75
const searchProviders = new SearchProviders();
76
77
/**
78
* SearchTelemetryChild monitors for pages that are partner searches, and
79
* looks through them to find links which looks like adverts and sends back
80
* a notification to SearchTelemetry for possible telemetry reporting.
81
*
82
* Only the partner details and the fact that at least one ad was found on the
83
* page are returned to SearchTelemetry. If no ads are found, no notification is
84
* given.
85
*/
86
class SearchTelemetryChild extends ActorChild {
87
/**
88
* Determines if there is a provider that matches the supplied URL and returns
89
* the information associated with that provider.
90
*
91
* @param {string} url The url to check
92
* @returns {array|null} Returns null if there's no match, otherwise an array
93
* of provider name and the provider information.
94
*/
95
_getProviderInfoForUrl(url) {
96
return Object.entries(searchProviders.info || []).find(([_, info]) =>
97
info.regexp.test(url)
98
);
99
}
100
101
/**
102
* Checks to see if the page is a partner and has an ad link within it. If so,
103
* it will notify SearchTelemetry.
104
*
105
* @param {object} doc The document object to check.
106
*/
107
_checkForAdLink(doc) {
108
let providerInfo = this._getProviderInfoForUrl(doc.documentURI);
109
if (!providerInfo) {
110
return;
111
}
112
113
let regexps = providerInfo[1].extraAdServersRegexps;
114
115
let anchors = doc.getElementsByTagName("a");
116
let hasAds = false;
117
for (let anchor of anchors) {
118
if (!anchor.href) {
119
continue;
120
}
121
for (let regexp of regexps) {
122
if (regexp.test(anchor.href)) {
123
hasAds = true;
124
break;
125
}
126
}
127
if (hasAds) {
128
break;
129
}
130
}
131
if (hasAds) {
132
this.sendAsyncMessage("SearchTelemetry:PageInfo", {
133
hasAds: true,
134
url: doc.documentURI,
135
});
136
}
137
}
138
139
/**
140
* Handles events received from the actor child notifications.
141
*
142
* @param {object} event The event details.
143
*/
144
handleEvent(event) {
145
// We are only interested in the top-level frame.
146
if (event.target.ownerGlobal != this.content) {
147
return;
148
}
149
150
switch (event.type) {
151
case "pageshow": {
152
// If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
153
// event, so we need to rely on "pageshow" in this case. Note: we do this
154
// so that we remain consistent with the *.in-content:sap* count for the
155
// SEARCH_COUNTS histogram.
156
if (event.persisted) {
157
this._checkForAdLink(this.content.document);
158
}
159
break;
160
}
161
case "DOMContentLoaded": {
162
this._checkForAdLink(this.content.document);
163
break;
164
}
165
}
166
}
167
}