Do not announce robot account token before account ID is available
[chromium-blink-merge.git] / chrome / test / data / extensions / activity_log / ad_injection / content_script.js
blob686e2db4879330d0fba1f58d3d27ac02cf3118dc
1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 //
5 // This file contains the tests for detecting extension's ad injection in
6 // Chrome. This contains many different, independent tests, but it is all run
7 // as a "single" browser test. The reason for this is that we want to do many
8 // short tests for ad injection, and the set-up/tear-down time for a browsertest
9 // implementation of each would be prohibitive.
10 // See also chrome/browser/extensions/activity_log/ad_injection_browsertest.cc
12 // For all you data lovers, a few quick benchmarks.
13 // Taken on a HP Z620 Goobuntu Workstation.
14 // Method 1: Running all tests within a single browsertest, and cleaning up on
15 //           the JS side after each (i.e., the method we're using).
16 // Method 2: Running so that every test has it's own browsertest.
18 // ----------------------------------------------------------
19 // Number of Tests | Method |   Real   |   User   |   Sys   |
20 //        1        |    1   |  6.002s  |  2.850s  |  0.580s |
21 //        10       |    1   |  8.387s  |  3.610s  |  0.920s |
22 //        100      |    1   | 33.765s  | 11.740s  |  3.000s |
23 //        1        |    2   |  6.020s  |  2.940s  |  0.470s |
24 //        10       |    2   | 57.945s  | 26.580s  |  4.950s |
25 //        100      |    2   |   N/A    |   N/A    |   N/A   |
26 // ----------------------------------------------------------
28 // This html block is just designed to be a massive playground for ad injectors,
29 // where they can let loose and have fun.
30 // We use this to populate our page at the start of every test.
31 var kBodyHtml =
32     '<iframe id="ad-iframe" src="http://www.known-ads.adnetwork"></iframe>' +
33     '<iframe id="non-ad-iframe" src="http://www.not-ads.adnetwork"></iframe>' +
34     '<a id="ad-anchor" href="http://www.known-ads.adnetwork"></a>' +
35     '<a id="non-ad-anchor" href="http://www.not-ads.adnetwork"></a>' +
36     '<div id="empty-div"></div>';
38 /**
39  * The AdInjectorTest infrastructure. Basically, this allows the test to follow
40  * a simple iteration cycle:
41  * - Signal the C++ test that we're going to do page setup, and we shouldn't
42  *   record any ad-injector events (as some of the page setup could qualify).
43  * - Do the page setup, which involves creating a 'playground' for ad injectors
44  *   of iframes, etc. See also ad_injectors.html. We do this set up for each
45  *   test, so that none of the tests conflicts with each other.
46  * - Signal that we are done with page setup, and should start recording events.
47  * - Run the next test.
48  * - Signal that the test is done, and we should check for the result.
49  *
50  * This cycle repeats, and should be done synchronously so that we don't end up
51  * recording events which we shouldn't, and can attribute each event to its
52  * cause.
53  * @constructor
54  */
55 function AdInjectorTest(functions) {
56   /*
57    * The list of functions to run in order to test ad injection.
58    * @type {Array.<Function>}
59    * @private
60    */
61   this.functions_ = functions;
64 AdInjectorTest.prototype = {
65   /**
66    * The index of the function that is next in line.
67    * @type {int}
68    * @private
69    */
70   currentFunction_: 0,
72   /**
73    * Start the page reset process. This includes sending a message to Chrome
74    * and verifying receipt (by waiting for a response, and then triggering the
75    * reset.
76    * @private
77    */
78   resetPageAndRunNextFunction_: function() {
79     chrome.test.sendMessage('Page Reset Begin', function(response) {
80       this.resetPageAndRunNextFunctionImpl_();
81     }.bind(this));
82   },
84   /**
85    * Reset the page. This means eliminating any previous "content" from the
86    * body, and refreshing it with a new, clean slate.
87    * @private
88    */
89   resetPageAndRunNextFunctionImpl_: function() {
90     document.body.innerHTML = kBodyHtml;
91     chrome.test.sendMessage('Page Reset End', function(response) {
92       this.runNextFunctionImpl_();
93     }.bind(this));
94   },
96   /**
97    * Start the process to run the next function (which begins by resetting the
98    * page). If there is no next function, send the "Test Complete" message.
99    */
100   runNextFunction: function() {
101     if (this.currentFunction_ < this.functions_.length)
102       this.resetPageAndRunNextFunction_();
103     else
104       chrome.test.sendMessage('Test Complete');
105   },
107   /**
108    * The implementation of runNextFunction().
109    * @private
110    */
111   runNextFunctionImpl_: function() {
112     var f = this.functions_[this.currentFunction_++];
113     var message = '';
115     // Try and catch any errors so one bad function does cause the whole suite
116     // to crash.
117     try {
118       var change = f();
119       message = f.name + ':' + change;
120     } catch(e) {
121       message = 'Testing Error:' + f.name + ':' + e.message + ', ' + e.stack;
122     }
124     chrome.test.sendMessage(message, function(response) {
125       this.runNextFunction();
126     }.bind(this));
127   }
130 // Return values to signal the result of a test. Each test should return the
131 // appropriate value in order to indicate if an ad was injected, and, if so,
132 // what kind of injection.
133 // These match the enum values in
134 // chrome/browser/extensions/activity_log/activity_actions.h.
135 // The signal that there was no ad injection.
136 var NO_AD_INJECTION = 0;
137 // The signal that there was a new ad injected.
138 var INJECTION_NEW_AD = 1;
139 // The signal that an ad was removed.
140 var INJECTION_REMOVED_AD = 2;
141 // The signal that an ad was replaced.
142 var INJECTION_REPLACED_AD = 3;
143 // The signal that an ad was likely injected, but we didn't detect it fully.
144 var INJECTION_LIKELY_NEW_AD = 4;
145 // The signal that an ad was likely replaced, but we didn't detect it fully.
146 var INJECTION_LIKELY_REPLACED_AD = 5;
148 /* The "ad network" url to use for tests. */
149 var kAdNetwork = 'http://www.known-ads.adnetwork';
150 var kAdNetwork2 = 'http://www.also-known-ads.adnetwork';
152 /* The "non ad network" url to use for tests. */
153 var kMaybeAdNetwork = 'http://www.maybe-ads.adnetwork';
155 /* @return {?HTMLElement} The element with the given id. */
156 var $ = function(id) { return document.getElementById(id); }
158 // The following is a collection of functions to "get" an HTML ad. We need to do
159 // this for internal counting in testing, because if we simply do:
160 //   var iframe = document.createElement('iframe');
161 //   iframe.src = kAdNetwork;
162 //   document.body.appendChild(iframe);
163 // We end up double counting ad injections. We count one for modifying the src
164 // of the iframe (on line 2) and a second for appending an iframe with an ad
165 // (on line 3). Instead of doing this, use the get*Ad() functions below. The
166 // example above would become:
167 //   document.body.appendChild(getIframeAd());
169 // Creates an iframe ad, like so:
170 // <iframe src="http://www.known-ads.adnetwork"></iframe>
171 var kIframeAdTemplate = document.createElement('iframe');
172 kIframeAdTemplate.src = kAdNetwork;
175  * @return An iframe element which will count as ad injection in the tests.
176  */
177 var getIframeAd = function() {
178   return kIframeAdTemplate.cloneNode(true);
181 // Creates an anchor ad, like so:
182 // <a href="http://www.known-ads.adnetwork"></a>
183 var kAnchorAdTemplate = document.createElement('a');
184 kAnchorAdTemplate.href = kAdNetwork;
187  * @return An anchor ('a') element which will count as ad injection in the
188  *     tests.
189  */
190 var getAnchorAd = function() {
191   return kAnchorAdTemplate.cloneNode(true);
194 // Creates a script ad, like so:
195 // <script href="http://www.known-ads.adnetwork"></a>
196 var kScriptAdTemplate = document.createElement('script');
197 kScriptAdTemplate.src = kAdNetwork;
200  * @return A script element which will count as ad injection in the tests.
201  */
202 var getScriptAd = function() {
203   return kScriptAdTemplate.cloneNode(true);
206 // This series constructs a nested ad, which looks like this:
207 // <div>
208 //   <div>
209 //     <span></span>
210 //     <iframe src="http://www.known-ads.adnetwork"></iframe>
211 //   </div>
212 // </div>
213 var div = document.createElement('div');
214 div.appendChild(document.createElement('span'));
215 div.appendChild(getIframeAd());
216 var kNestedAdTemplate = document.createElement('div');
217 kNestedAdTemplate.appendChild(div);
220  * @return A div with a nested element which will count as ad injection in the
221  *     tests.
222  */
223 var getNestedAd = function() {
224   return kNestedAdTemplate.cloneNode(true);
228  * The collection of functions to use for testing.
229  * In order to add a new test, simply append it to the collection of functions.
230  * All functions will be run in the test, and each will report its success or
231  * failure independently of the others.
232  * All test functions must be synchronous.
233  * @type {Array.<Function>}
234  */
235 var functions = [];
237 // Add a bunch of elements, but nothing that looks like ad injection (no
238 // elements with an external source, no modifying existing sources).
239 functions.push(function NoAdInjection() {
240   var div = document.createElement('div');
241   var iframe = document.createElement('iframe');
242   var anchor = document.createElement('anchor');
243   var span = document.createElement('span');
244   span.textContent = 'Hello, world';
245   div.appendChild(iframe);
246   div.appendChild(anchor);
247   div.appendChild(span);
248   document.body.appendChild(div);
249   return NO_AD_INJECTION;
252 // Add a new iframe with an AdNetwork source by creating the element and
253 // appending it.
254 functions.push(function NewIframeAdNetwork() {
255   document.body.appendChild(getIframeAd());
256   return INJECTION_NEW_AD;
259 // Add a new iframe which does not serve ads. We should not record anything.
260 functions.push(function NewIframeLikelyAdNetwork() {
261   var frame = document.createElement('iframe');
262   document.body.appendChild(frame).src = kMaybeAdNetwork;
263   return INJECTION_LIKELY_NEW_AD;
266 // Modify an iframe which is currently in the DOM, switching the src to an
267 // ad network.
268 functions.push(function ModifyExistingIframeToAdNetwork1() {
269   var frame = $('non-ad-iframe');
270   frame.src = kAdNetwork;
271   return INJECTION_NEW_AD;
274 functions.push(function ModifyExistingIframeToAdNetwork2() {
275   var frame = $('non-ad-iframe');
276   frame.setAttribute('src', kAdNetwork);
277   return INJECTION_NEW_AD;
280 // Add a new anchor element which serves ads.
281 functions.push(function NewAnchorAd() {
282   document.body.appendChild(getAnchorAd());
283   return INJECTION_NEW_AD;
286 functions.push(function NewAnchorLikelyAd() {
287   var anchor = document.createElement('a');
288   document.body.appendChild(anchor).href = kMaybeAdNetwork;
289   return INJECTION_LIKELY_NEW_AD;
292 // Test that an extension adding an element is not considered likely ad
293 // injection if the element has a local resource.
294 functions.push(function LocalResourceNotConsideredAd() {
295   var anchor = document.createElement('a');
296   document.body.appendChild(anchor).href = chrome.extension.getURL('foo.html');
297   return NO_AD_INJECTION;
300 // Test that an extension adding an element with the same host as the current
301 // page is not considered ad injection.
302 functions.push(function SamePageUrlNotConsideredAd() {
303   var anchor = document.createElement('a');
304   // This source is something like 'http://127.0.0.1:49725/foo.html'.
305   document.body.appendChild(anchor).href = document.URL + 'foo.html';
306   return NO_AD_INJECTION;
309 functions.push(function ModifyExistingAnchorToAdNetwork1() {
310   var anchor = $('non-ad-anchor');
311   anchor.href = kAdNetwork;
312   return INJECTION_NEW_AD;
315 functions.push(function ModifyExistingAnchorToAdNetwork2() {
316   var anchor = $('non-ad-anchor');
317   anchor.setAttribute('href', kAdNetwork);
318   return INJECTION_NEW_AD;
321 // Add a new element which has a nested ad, to ensure we do a deep check of
322 // elements appended to the dom.
323 functions.push(function NewNestedAd() {
324   document.body.appendChild(getNestedAd());
325   return INJECTION_NEW_AD;
328 // Switch an existing iframe ad to a new ad network.
329 functions.push(function ReplaceIframeAd1() {
330   $('ad-iframe').src = kAdNetwork2;
331   return INJECTION_REPLACED_AD;
334 functions.push(function ReplaceIframeAd2() {
335   $('ad-iframe').setAttribute('src', kAdNetwork2);
336   return INJECTION_REPLACED_AD;
339 // Switch an existing anchor ad to a new ad network.
340 functions.push(function ReplaceAnchorAd1() {
341   $('ad-anchor').href = kAdNetwork2;
342   return INJECTION_REPLACED_AD;
345 functions.push(function ReplaceAnchorAd2() {
346   $('ad-anchor').setAttribute('href', kAdNetwork2);
347   return INJECTION_REPLACED_AD;
350 // Remove an existing iframe ad by setting it's src to a non-ad network.
351 functions.push(function RemoveAdBySettingSrc() {
352   $('ad-iframe').src = kMaybeAdNetwork;
353   return INJECTION_REMOVED_AD;
356 // Add an ad network script to the DOM.
357 functions.push(function AddNewScriptAd() {
358   document.body.appendChild(getScriptAd());
359   return INJECTION_NEW_AD;
362 // Add an ad network script by modifying the src attribute of a script element.
363 functions.push(function ModifyScriptSrcAttribute() {
364   document.body.appendChild(document.createElement('script')).src = kAdNetwork;
365   return INJECTION_NEW_AD;
368 // Ensure that we flag actions that look a lot like ad injection, even if we're
369 // not sure.
370 functions.push(function LikelyReplacedAd() {
371   // Switching from one valid url src to another valid url src is very
372   // suspicious behavior, and should be relatively rare. This helps us determine
373   // the effectiveness of our ad network recognition.
374   $('non-ad-iframe').src = 'http://www.thismightbeanadnetwork.ads';
375   return INJECTION_LIKELY_REPLACED_AD;
378 // Verify that we do not enter the javascript world when we check for ad
379 // injection.
380 functions.push(function VerifyNoAccess() {
381   var frame = document.createElement('iframe');
382   frame.__defineGetter__('src', function() {
383     throw new Error('Forbidden access into javascript execution!');
384     return kAdNetwork;
385   });
386   return NO_AD_INJECTION;
389 // Test that using "eval" is not a way around detection.
390 functions.push(function EvalAdInjection() {
391   var code = 'document.body.appendChild(' +
392              '    document.createElement("iframe")).src = kAdNetwork;';
393   eval(code);
394   return INJECTION_NEW_AD;
397 // TODO(rdevlin.cronin): We are not covering every case yet. Fix this.
398 // See crbug.com/357204.
400 // Kick off the tests.
401 var test = new AdInjectorTest(functions);
402 test.runNextFunction();