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();