1 // Copyright (c) 2012 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.
5 var getURL = chrome.extension.getURL;
6 var deepEq = chrome.test.checkDeepEq;
9 var capturedUnexpectedData;
10 var expectedEventOrder;
15 var testServer = "www.a.com";
16 var defaultScheme = "http";
19 'onBeforeRequest': [],
20 'onBeforeSendHeaders': [],
23 'onHeadersReceived': [],
24 'onResponseStarted': [],
25 'onBeforeRedirect': [],
30 // If true, don't bark on events that were not registered via expect().
31 // These events are recorded in capturedUnexpectedData instead of
33 var ignoreUnexpected = false;
35 // This is a debugging aid to print all received events as well as the
36 // information whether they were expected.
37 var logAllRequests = false;
39 function runTests(tests) {
40 var waitForAboutBlank = function(_, info, tab) {
41 if (info.status == "complete" && tab.url == "about:blank") {
45 chrome.tabs.onUpdated.removeListener(waitForAboutBlank);
46 chrome.test.getConfig(function(config) {
47 testServerPort = config.testServer.port;
48 chrome.test.runTests(tests);
52 chrome.tabs.onUpdated.addListener(waitForAboutBlank);
53 chrome.tabs.create({url: "about:blank"});
56 // Returns an URL from the test server, fixing up the port. Must be called
57 // from within a test case passed to runTests.
58 function getServerURL(path, opt_host, opt_scheme) {
60 throw new Error("Called getServerURL outside of runTests.");
61 var host = opt_host || testServer;
62 var scheme = opt_scheme || defaultScheme;
63 return scheme + "://" + host + ":" + testServerPort + "/" + path;
66 // Helper to advance to the next test only when the tab has finished loading.
67 // This is because tabs.update can sometimes fail if the tab is in the middle
68 // of a navigation (from the previous test), resulting in flakiness.
69 function navigateAndWait(url, callback) {
70 var done = chrome.test.listenForever(chrome.tabs.onUpdated,
71 function (_, info, tab) {
72 if (tab.id == tabId && info.status == "complete") {
73 if (callback) callback();
77 chrome.tabs.update(tabId, {url: url});
80 // data: array of extected events, each one is a dictionary:
81 // { label: "<unique identifier>",
82 // event: "<webrequest event type>",
83 // details: { <expected details of the webrequest event> },
84 // retval: { <dictionary that the event handler shall return> } (optional)
86 // order: an array of sequences, e.g. [ ["a", "b", "c"], ["d", "e"] ] means that
87 // event with label "a" needs to occur before event with label "b". The
88 // relative order of "a" and "d" does not matter.
89 // filter: filter dictionary passed on to the event subscription of the
91 // extraInfoSpec: the union of all desired extraInfoSpecs for the events.
92 function expect(data, order, filter, extraInfoSpec) {
93 expectedEventData = data || [];
94 capturedEventData = [];
95 capturedUnexpectedData = [];
96 expectedEventOrder = order || [];
97 if (expectedEventData.length > 0) {
98 eventsCaptured = chrome.test.callbackAdded();
100 tabAndFrameUrls = {}; // Maps "{tabId}-{frameId}" to the URL of the frame.
101 frameIdMap = {"-1": -1};
103 resetDeclarativeRules();
104 initListeners(filter || {urls: ["<all_urls>"]}, extraInfoSpec || []);
105 // Fill in default values.
106 for (var i = 0; i < expectedEventData.length; ++i) {
107 if (!('method' in expectedEventData[i].details)) {
108 expectedEventData[i].details.method = "GET";
110 if (!('tabId' in expectedEventData[i].details)) {
111 expectedEventData[i].details.tabId = tabIdMap[tabId];
113 if (!('frameId' in expectedEventData[i].details)) {
114 expectedEventData[i].details.frameId = 0;
116 if (!('parentFrameId' in expectedEventData[i].details)) {
117 expectedEventData[i].details.parentFrameId = -1;
119 if (!('type' in expectedEventData[i].details)) {
120 expectedEventData[i].details.type = "main_frame";
125 function checkExpectations() {
126 if (capturedEventData.length < expectedEventData.length) {
129 if (capturedEventData.length > expectedEventData.length) {
130 chrome.test.fail("Recorded too many events. " +
131 JSON.stringify(capturedEventData));
134 // We have ensured that capturedEventData contains exactly the same elements
135 // as expectedEventData. Now we need to verify the ordering.
136 // Step 1: build positions such that
137 // positions[<event-label>]=<position of this event in capturedEventData>
140 capturedEventData.forEach(function (event) {
141 chrome.test.assertTrue(event.hasOwnProperty("label"));
142 positions[event.label] = curPos;
145 // Step 2: check that elements arrived in correct order
146 expectedEventOrder.forEach(function (order) {
147 var previousLabel = undefined;
148 order.forEach(function(label) {
149 if (previousLabel === undefined) {
150 previousLabel = label;
153 chrome.test.assertTrue(positions[previousLabel] < positions[label],
154 "Event " + previousLabel + " is supposed to arrive before " +
156 previousLabel = label;
163 // Simple check to see that we have a User-Agent header, and that it contains
164 // an expected value. This is a basic check that the request headers are valid.
165 function checkUserAgent(headers) {
166 for (var i in headers) {
167 if (headers[i].name.toLowerCase() == "user-agent")
168 return headers[i].value.toLowerCase().indexOf("chrome") != -1;
173 function captureEvent(name, details, callback) {
174 // Ignore system-level requests like safebrowsing updates and favicon fetches
175 // since they are unpredictable.
176 if (details.tabId == -1 || details.type == "other" ||
177 details.url.match(/\/favicon.ico$/) ||
178 details.url.match(/https:\/\/dl.google.com/))
181 // Pull the extra per-event options out of the expected data. These let
182 // us specify special return values per event.
183 var currentIndex = capturedEventData.length;
186 if (expectedEventData.length > currentIndex) {
188 expectedEventData[currentIndex].retval_function ?
189 expectedEventData[currentIndex].retval_function(name, details) :
190 expectedEventData[currentIndex].retval;
193 // Check that the frameId can be used to reliably determine the URL of the
194 // frame that caused requests.
195 if (name == "onBeforeRequest") {
196 chrome.test.assertTrue('frameId' in details &&
197 typeof details.frameId === 'number');
198 chrome.test.assertTrue('tabId' in details &&
199 typeof details.tabId === 'number');
200 var key = details.tabId + "-" + details.frameId;
201 if (details.type == "main_frame" || details.type == "sub_frame") {
202 tabAndFrameUrls[key] = details.url;
204 details.frameUrl = tabAndFrameUrls[key] || "unknown frame URL";
207 // This assigns unique IDs to frames. The new IDs are only deterministic, if
208 // the frames documents are loaded in order. Don't write browser tests with
209 // more than one frame ID and rely on their numbers.
210 if (!(details.frameId in frameIdMap)) {
211 // Subtract one to discount for {"-1": -1} mapping that always exists.
212 // This gives the first frame the ID 0.
213 frameIdMap[details.frameId] = Object.keys(frameIdMap).length - 1;
215 details.frameId = frameIdMap[details.frameId];
216 details.parentFrameId = frameIdMap[details.parentFrameId];
218 // This assigns unique IDs to newly opened tabs. However, the new IDs are only
219 // deterministic, if the order in which the tabs are opened is deterministic.
220 if (!(details.tabId in tabIdMap)) {
221 tabIdMap[details.tabId] = Object.keys(tabIdMap).length;
223 details.tabId = tabIdMap[details.tabId];
225 delete details.requestId;
226 delete details.timeStamp;
227 if (details.requestHeaders) {
228 details.requestHeadersValid = checkUserAgent(details.requestHeaders);
229 delete details.requestHeaders;
231 if (details.responseHeaders) {
232 details.responseHeadersExist = true;
233 delete details.responseHeaders;
236 // find |details| in expectedEventData
238 var label = undefined;
239 expectedEventData.forEach(function (exp) {
240 if (deepEq(exp.event, name) && deepEq(exp.details, details)) {
242 chrome.test.fail("Received event twice '" + name + "':" +
243 JSON.stringify(details));
250 if (!found && !ignoreUnexpected) {
251 console.log("Expected events: " +
252 JSON.stringify(expectedEventData, null, 2));
253 chrome.test.fail("Received unexpected event '" + name + "':" +
254 JSON.stringify(details, null, 2));
258 if (logAllRequests) {
259 console.log("Expected: " + name + ": " + JSON.stringify(details));
261 capturedEventData.push({label: label, event: name, details: details});
263 // checkExpecations decrements the counter of pending events. We may only
264 // call it if an expected event has occurred.
267 if (logAllRequests) {
268 console.log("NOT Expected: " + name + ": " + JSON.stringify(details));
270 capturedUnexpectedData.push({label: label, event: name, details: details});
274 window.setTimeout(callback, 0, retval);
280 // Simple array intersection. We use this to filter extraInfoSpec so
281 // that only the allowed specs are sent to each listener.
282 function intersect(array1, array2) {
283 return array1.filter(function(x) { return array2.indexOf(x) != -1; });
286 function initListeners(filter, extraInfoSpec) {
287 var onBeforeRequest = function(details) {
288 return captureEvent("onBeforeRequest", details);
290 listeners['onBeforeRequest'].push(onBeforeRequest);
292 var onBeforeSendHeaders = function(details) {
293 return captureEvent("onBeforeSendHeaders", details);
295 listeners['onBeforeSendHeaders'].push(onBeforeSendHeaders);
297 var onSendHeaders = function(details) {
298 return captureEvent("onSendHeaders", details);
300 listeners['onSendHeaders'].push(onSendHeaders);
302 var onHeadersReceived = function(details) {
303 return captureEvent("onHeadersReceived", details);
305 listeners['onHeadersReceived'].push(onHeadersReceived);
307 var onAuthRequired = function(details) {
308 return captureEvent("onAuthRequired", details, callback);
310 listeners['onAuthRequired'].push(onAuthRequired);
312 var onResponseStarted = function(details) {
313 return captureEvent("onResponseStarted", details);
315 listeners['onResponseStarted'].push(onResponseStarted);
317 var onBeforeRedirect = function(details) {
318 return captureEvent("onBeforeRedirect", details);
320 listeners['onBeforeRedirect'].push(onBeforeRedirect);
322 var onCompleted = function(details) {
323 return captureEvent("onCompleted", details);
325 listeners['onCompleted'].push(onCompleted);
327 var onErrorOccurred = function(details) {
328 return captureEvent("onErrorOccurred", details);
330 listeners['onErrorOccurred'].push(onErrorOccurred);
332 chrome.webRequest.onBeforeRequest.addListener(
333 onBeforeRequest, filter,
334 intersect(extraInfoSpec, ["blocking", "requestBody"]));
336 chrome.webRequest.onBeforeSendHeaders.addListener(
337 onBeforeSendHeaders, filter,
338 intersect(extraInfoSpec, ["blocking", "requestHeaders"]));
340 chrome.webRequest.onSendHeaders.addListener(
341 onSendHeaders, filter,
342 intersect(extraInfoSpec, ["requestHeaders"]));
344 chrome.webRequest.onHeadersReceived.addListener(
345 onHeadersReceived, filter,
346 intersect(extraInfoSpec, ["blocking", "responseHeaders"]));
348 chrome.webRequest.onAuthRequired.addListener(
349 onAuthRequired, filter,
350 intersect(extraInfoSpec, ["asyncBlocking", "blocking",
351 "responseHeaders"]));
353 chrome.webRequest.onResponseStarted.addListener(
354 onResponseStarted, filter,
355 intersect(extraInfoSpec, ["responseHeaders"]));
357 chrome.webRequest.onBeforeRedirect.addListener(
358 onBeforeRedirect, filter, intersect(extraInfoSpec, ["responseHeaders"]));
360 chrome.webRequest.onCompleted.addListener(
362 intersect(extraInfoSpec, ["responseHeaders"]));
364 chrome.webRequest.onErrorOccurred.addListener(onErrorOccurred, filter);
367 function removeListeners() {
368 function helper(eventName) {
369 for (var i in listeners[eventName]) {
370 chrome.webRequest[eventName].removeListener(listeners[eventName][i]);
372 listeners[eventName].length = 0;
373 chrome.test.assertFalse(chrome.webRequest[eventName].hasListeners());
375 helper('onBeforeRequest');
376 helper('onBeforeSendHeaders');
377 helper('onAuthRequired');
378 helper('onSendHeaders');
379 helper('onHeadersReceived');
380 helper('onResponseStarted');
381 helper('onBeforeRedirect');
382 helper('onCompleted');
383 helper('onErrorOccurred');
386 function resetDeclarativeRules() {
387 chrome.declarativeWebRequest.onRequest.removeRules();