summaryrefslogtreecommitdiffstats
path: root/chrome/test/data/extensions/api_test/executescript/http204/background.js
blob: ad6280f758a15373be7317dec275772825aa3d02 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var config;
var MAIN_HOST = 'b.com';
var OTHER_HOST = 'c.com';

var DOMContentLoadedEventsInFrame = [];

chrome.test.getConfig(function(config) {
  window.config = config;

  var testUrl = 'http://' + MAIN_HOST + ':' + config.testServer.port +
    '/extensions/api_test/executescript/http204/page_with_204_frame.html';
  chrome.runtime.onMessage.addListener(function listener(msg, sender) {
    // This message should be sent when the frame and all sub frames have
    // completely finished loading.
    chrome.test.assertEq('start the test', msg);
    // Should be the top-level frame with our test page.
    chrome.test.assertEq(0, sender.frameId);
    chrome.test.assertEq(testUrl, sender.url);
    chrome.test.assertTrue(sender.tab.id > 0);

    chrome.runtime.onMessage.removeListener(listener);
    chrome.webNavigation.onDOMContentLoaded.removeListener(onDOMContentLoaded);
    // Avoid flakiness by excluding all events that are not from our tab.
    DOMContentLoadedEventsInFrame =
      DOMContentLoadedEventsInFrame.filter(function(details) {
        return details.tabId === sender.tab.id;
      });

    startTest(sender.tab.id);
  });

  chrome.webNavigation.onDOMContentLoaded.addListener(onDOMContentLoaded);
  chrome.tabs.create({
    url: testUrl
  });

  function onDOMContentLoaded(details) {
    if (details.frameId > 0) {
      DOMContentLoadedEventsInFrame.push(details);
    }
  }
});

function startTest(tabId) {
  // The default font color of any document.
  var kDefaultColor = getComputedStyle(document.body).color;
  var kExpectedFontFamily = '\'expected font-family\'';
  var kExpectedColor = 'rgb(123, 123, 123)';

  // The page has a child frame containing a HTTP 204 page.
  // In response to HTTP 204 (No Content), the browser stops navigating away and
  // stays at the previous page. In this test, the URL leading to HTTP 204 was
  // the initial URL of the frame, so in response to HTTP 204, the frame should
  // end at about:blank.

  // Each chrome.tabs.insertCSS test is followed by a test using executeScript.
  // These executeScript tests exists for two reasons:
  // - They verify the result of insertCSS
  // - They show that executeScript is working as intended.

  chrome.test.runTests([
    function insertCssTopLevelOnly() {
      // Sanity check: insertCSS can change main frame's CSS.
      chrome.tabs.insertCSS(tabId, {
        code: 'body { font-family: ' + kExpectedFontFamily + ' !important;}',
      }, chrome.test.callbackPass());
      // The result is verified hereafter, in executeScriptTopLevelOnly.
    },

    function executeScriptTopLevelOnly() {
      // Sanity check: insertCSS should really have changed the CSS.
      // Depends on insertCssTopLevelOnly.
      chrome.tabs.executeScript(tabId, {
        code: 'getComputedStyle(document.body).fontFamily',
      }, chrome.test.callbackPass(function(results) {
        chrome.test.assertEq([kExpectedFontFamily], results);
      }));
    },

    // Now we know that executeScript works in the top-level frame, we will use
    // it to check whether executeScript can execute code in the child frame.
    function verifyManifestContentScriptInjected() {
      // Check whether the content scripts from manifest.json ran in the frame.
      chrome.tabs.executeScript(tabId, {
        code: '[' +
          '[window.documentStart,' +
          ' window.documentEnd,' +
          ' window.documentIdle],' +
          '[frames[0].documentStart,' +
          ' frames[0].documentEnd,' +
          ' frames[0].documentIdle],' +
          '[frames[0].didRunAtDocumentStartUnexpected,' +
          ' frames[0].didRunAtDocumentEndUnexpected,' +
          ' frames[0].didRunAtDocumentIdleUnexpected],' +
          ']',
      }, chrome.test.callbackPass(function(results) {
        chrome.test.assertEq([[
            // Should always run in top frame because of matching match pattern.
            [1, 1, 1],

            [
              // Before the response from the server is received, the frame
              // displays an empty document. This document has a <html> element,
              // it can be scripted by the parent frame and its URL as shown to
              // scripts is about:blank.
              // Because the content script's match_about_blank flag is set to
              // true in manifest.json, and its URL pattern matches the parent
              // frame's URL and, the document_start script should be run.
              // TODO(robwu): This should be 1 for the reason above, but it is
              // null because the script is not injected (crbug.com/511057).
              null,
              // Does not run at document_end and document_idle because the
              // DOMContentLoaded event is not triggered either.
              null,
              null,
            ],

            // Should not run scripts in child frame because the page load was
            // not committed, and the URL pattern (204 page) doesn't match.
            [null, null, null],
        ]], results);
      }));
    },

    // document_end and document_idle scripts are not run in the child frame
    // because we assume that the DOMContentLoaded event is not triggered in
    // frames after a failed provisional load. Verify that the DOMContentLoaded
    // event was indeed NOT triggered.
    function checkDOMContentLoadedEvent() {
      chrome.test.assertEq([], DOMContentLoadedEventsInFrame);
      chrome.test.succeed();
    },

    function insertCss204NoAbout() {
      // HTTP 204 = stay at previous page, which was a blank page, so insertCSS
      // without matchAboutBlank shouldn't change the frame's CSS.
      chrome.tabs.insertCSS(tabId, {
        code: 'body { color: ' + kExpectedColor + '; }',
        allFrames: true,
      }, chrome.test.callbackPass());
      // The result is verified hereafter, in verifyInsertCss204NoAbout.
    },

    function verifyInsertCss204NoAbout() {
      // Depends on insertCss204NoAbout.
      chrome.tabs.executeScript(tabId, {
        code: 'frames[0].getComputedStyle(frames[0].document.body).color',
      }, chrome.test.callbackPass(function(results) {
        // CSS should not be inserted in frame because it's about:blank.
        chrome.test.assertEq([kDefaultColor], results);
      }));
    },

    function insertCss204Blank() {
      chrome.tabs.insertCSS(tabId, {
        code: 'body { color: ' + kExpectedColor + '; }',
        allFrames: true,
        matchAboutBlank: true,
      }, chrome.test.callbackPass());
      // The result is verified hereafter, in verifyInsertCss204Blank.
    },

    function verifyInsertCss204Blank() {
      // Depends on insertCss204Blank.
      chrome.tabs.executeScript(tabId, {
        code: 'frames[0].getComputedStyle(frames[0].document.body).color',
      }, chrome.test.callbackPass(function(results) {
        // CSS should be inserted in frame because matchAboutBlank was true.
        chrome.test.assertEq([kExpectedColor], results);
      }));
    },

    function executeScript204NoAbout() {
      chrome.tabs.executeScript(tabId, {
        code: 'top === window',
        allFrames: true,
      }, chrome.test.callbackPass(function(results) {
        // Child frame should not be matched because it's about:blank.
        chrome.test.assertEq([true], results);
      }));
    },

    function executeScript204About() {
      chrome.tabs.executeScript(tabId, {
        code: 'top === window',
        allFrames: true,
        matchAboutBlank: true,
      }, chrome.test.callbackPass(function(results) {
        // Child frame should not be matched because matchAboutBlank was true.
        chrome.test.assertEq([true, false], results);
      }));
    },

    // Now we have verified that (programmatic) content script injection works
    // for a frame whose initial load resulted in a 204.
    // Continue with testing navigation from a child frame to a 204 page, with
    // a variety of origins for completeness.

    function loadSameOriginFrameAndWaitUntil204() {
      // This is not a test, just preparing for the next test.
      // All URLs are at the same origin.
      navigateToFrameAndWaitUntil204Loaded(tabId, MAIN_HOST, MAIN_HOST);
    },

    function verifySameOriginManifestAfterSameOrigin204() {
      checkManifestScriptsAfter204Navigation(tabId);
    },

    function loadSameOriginFrameAndWaitUntilCrossOrigin204() {
      // This is not a test, just preparing for the next test.
      // The frame is at the same origin as the top-level frame, but the 204
      // URL is at a different origin.
      navigateToFrameAndWaitUntil204Loaded(tabId, MAIN_HOST, OTHER_HOST);
    },

    function verifySameOriginManifestAfterCrossOrigin204() {
      checkManifestScriptsAfter204Navigation(tabId);
    },

    function loadCrossOriginFrameAndWaitUntil204() {
      // This is not a test, just preparing for the next test.
      // The frame's origin differs from the top-level frame, and the 204 URL is
      // at the same origin as the frame.
      navigateToFrameAndWaitUntil204Loaded(tabId, OTHER_HOST, OTHER_HOST);
    },

    function verifyCrossOriginManifestAfterSameOrigin204() {
      checkManifestScriptsAfter204Navigation(tabId);
    },

    function loadCrossOriginFrameAndWaitUntilCrossOrigin204() {
      // This is not a test, just preparing for the next test.
      // The frame's origin differs from the top-level frame, and the origin of
      // the 204 URL differs from the frame (it is incidentally the same as the
      // main frame's origin).
      navigateToFrameAndWaitUntil204Loaded(tabId, OTHER_HOST, MAIN_HOST);
    },

    function verifyCrossOriginManifestAfterCrossOrigin204() {
      checkManifestScriptsAfter204Navigation(tabId);
    },
  ]);
}

// Navigates to a page that navigates to a 204 page via a script.
function navigateToFrameAndWaitUntil204Loaded(tabId, hostname, hostname204) {
  var doneListening = chrome.test.listenForever(
      chrome.webNavigation.onErrorOccurred,
      function(details) {
        if (details.tabId === tabId && details.frameId > 0) {
          chrome.test.assertTrue(details.url.includes('page204.html'),
              'frame URL should be page204.html, but was ' + details.url);
          doneListening();
        }
      });

  var url = 'http://' + hostname + ':' + config.testServer.port +
    '/extensions/api_test/executescript/http204/navigate_to_204.html?' +
    hostname204;

  chrome.tabs.executeScript(tabId, {
    code: 'document.body.innerHTML = \'<iframe src="' + url + '"></iframe>\';',
  });
}

// Checks whether the content scripts were run as expected in the frame that
// just received a failed provisional load (=received 204 reply).
function checkManifestScriptsAfter204Navigation(tabId) {
  chrome.tabs.executeScript(tabId, {
    allFrames: true,
    code: '[' +
      '[window.documentStart,' +
      ' window.documentEnd],' +
      '[window.didRunAtDocumentStartUnexpected,' +
      ' window.didRunAtDocumentEndUnexpected],' +
      ']',
  }, chrome.test.callbackPass(function(results) {
    chrome.test.assertEq(2, results.length);
    // Main frame. Should not be affected by child frame navigations.
    chrome.test.assertEq([[1, 1], [null, null]], results[0]);

    // Child frame.
    chrome.test.assertEq([
        // Should run the content scripts even after a navigation to 204.
        [1, 1],
        // Should not inject non-matching scripts.
        [null, null],
    ], results[1]);
  }));
}