- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / test / data / extensions / api_test / webrequest / framework.js
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.
4
5 var getURL = chrome.extension.getURL;
6 var deepEq = chrome.test.checkDeepEq;
7 var expectedEventData;
8 var capturedEventData;
9 var capturedUnexpectedData;
10 var expectedEventOrder;
11 var tabId;
12 var tabIdMap;
13 var frameIdMap;
14 var testServerPort;
15 var testServer = "www.a.com";
16 var defaultScheme = "http";
17 var eventsCaptured;
18
19 // If true, don't bark on events that were not registered via expect().
20 // These events are recorded in capturedUnexpectedData instead of
21 // capturedEventData.
22 var ignoreUnexpected = false;
23
24 // This is a debugging aid to print all received events as well as the
25 // information whether they were expected.
26 var logAllRequests = false;
27
28 function runTests(tests) {
29   var waitForAboutBlank = function(_, info, tab) {
30     if (info.status == "complete" && tab.url == "about:blank") {
31       tabId = tab.id;
32       tabIdMap = {};
33       tabIdMap[tabId] = 0;
34       chrome.tabs.onUpdated.removeListener(waitForAboutBlank);
35       chrome.test.getConfig(function(config) {
36         testServerPort = config.testServer.port;
37         chrome.test.runTests(tests);
38       });
39     }
40   };
41   chrome.tabs.onUpdated.addListener(waitForAboutBlank);
42   chrome.tabs.create({url: "about:blank"});
43 }
44
45 // Returns an URL from the test server, fixing up the port. Must be called
46 // from within a test case passed to runTests.
47 function getServerURL(path, opt_host, opt_scheme) {
48   if (!testServerPort)
49     throw new Error("Called getServerURL outside of runTests.");
50   var host = opt_host || testServer;
51   var scheme = opt_scheme || defaultScheme;
52   return scheme + "://" + host + ":" + testServerPort + "/" + path;
53 }
54
55 // Helper to advance to the next test only when the tab has finished loading.
56 // This is because tabs.update can sometimes fail if the tab is in the middle
57 // of a navigation (from the previous test), resulting in flakiness.
58 function navigateAndWait(url, callback) {
59   var done = chrome.test.listenForever(chrome.tabs.onUpdated,
60       function (_, info, tab) {
61     if (tab.id == tabId && info.status == "complete") {
62       if (callback) callback();
63       done();
64     }
65   });
66   chrome.tabs.update(tabId, {url: url});
67 }
68
69 // data: array of extected events, each one is a dictionary:
70 //     { label: "<unique identifier>",
71 //       event: "<webrequest event type>",
72 //       details: { <expected details of the webrequest event> },
73 //       retval: { <dictionary that the event handler shall return> } (optional)
74 //     }
75 // order: an array of sequences, e.g. [ ["a", "b", "c"], ["d", "e"] ] means that
76 //     event with label "a" needs to occur before event with label "b". The
77 //     relative order of "a" and "d" does not matter.
78 // filter: filter dictionary passed on to the event subscription of the
79 //     webRequest API.
80 // extraInfoSpec: the union of all desired extraInfoSpecs for the events.
81 function expect(data, order, filter, extraInfoSpec) {
82   expectedEventData = data || [];
83   capturedEventData = [];
84   capturedUnexpectedData = [];
85   expectedEventOrder = order || [];
86   if (expectedEventData.length > 0) {
87     eventsCaptured = chrome.test.callbackAdded();
88   }
89   tabAndFrameUrls = {};  // Maps "{tabId}-{frameId}" to the URL of the frame.
90   frameIdMap = {"-1": -1};
91   removeListeners();
92   resetDeclarativeRules();
93   initListeners(filter || {urls: ["<all_urls>"]}, extraInfoSpec || []);
94   // Fill in default values.
95   for (var i = 0; i < expectedEventData.length; ++i) {
96     if (!('method' in expectedEventData[i].details)) {
97       expectedEventData[i].details.method = "GET";
98     }
99     if (!('tabId' in expectedEventData[i].details)) {
100       expectedEventData[i].details.tabId = tabIdMap[tabId];
101     }
102     if (!('frameId' in expectedEventData[i].details)) {
103       expectedEventData[i].details.frameId = 0;
104     }
105     if (!('parentFrameId' in expectedEventData[i].details)) {
106       expectedEventData[i].details.parentFrameId = -1;
107     }
108     if (!('type' in expectedEventData[i].details)) {
109       expectedEventData[i].details.type = "main_frame";
110     }
111   }
112 }
113
114 function checkExpectations() {
115   if (capturedEventData.length < expectedEventData.length) {
116     return;
117   }
118   if (capturedEventData.length > expectedEventData.length) {
119     chrome.test.fail("Recorded too many events. " +
120         JSON.stringify(capturedEventData));
121     return;
122   }
123   // We have ensured that capturedEventData contains exactly the same elements
124   // as expectedEventData. Now we need to verify the ordering.
125   // Step 1: build positions such that
126   //     positions[<event-label>]=<position of this event in capturedEventData>
127   var curPos = 0;
128   var positions = {}
129   capturedEventData.forEach(function (event) {
130     chrome.test.assertTrue(event.hasOwnProperty("label"));
131     positions[event.label] = curPos;
132     curPos++;
133   });
134   // Step 2: check that elements arrived in correct order
135   expectedEventOrder.forEach(function (order) {
136     var previousLabel = undefined;
137     order.forEach(function(label) {
138       if (previousLabel === undefined) {
139         previousLabel = label;
140         return;
141       }
142       chrome.test.assertTrue(positions[previousLabel] < positions[label],
143           "Event " + previousLabel + " is supposed to arrive before " +
144           label + ".");
145       previousLabel = label;
146     });
147   });
148
149   eventsCaptured();
150 }
151
152 // Simple check to see that we have a User-Agent header, and that it contains
153 // an expected value. This is a basic check that the request headers are valid.
154 function checkUserAgent(headers) {
155   for (var i in headers) {
156     if (headers[i].name.toLowerCase() == "user-agent")
157       return headers[i].value.toLowerCase().indexOf("chrome") != -1;
158   }
159   return false;
160 }
161
162 function captureEvent(name, details, callback) {
163   // Ignore system-level requests like safebrowsing updates and favicon fetches
164   // since they are unpredictable.
165   if (details.tabId == -1 || details.type == "other" ||
166       details.url.match(/\/favicon.ico$/) ||
167       details.url.match(/https:\/\/dl.google.com/))
168     return;
169
170   // Pull the extra per-event options out of the expected data. These let
171   // us specify special return values per event.
172   var currentIndex = capturedEventData.length;
173   var extraOptions;
174   var retval;
175   if (expectedEventData.length > currentIndex) {
176     retval =
177         expectedEventData[currentIndex].retval_function ?
178         expectedEventData[currentIndex].retval_function(name, details) :
179         expectedEventData[currentIndex].retval;
180   }
181
182   // Check that the frameId can be used to reliably determine the URL of the
183   // frame that caused requests.
184   if (name == "onBeforeRequest") {
185     chrome.test.assertTrue('frameId' in details &&
186                            typeof details.frameId === 'number');
187     chrome.test.assertTrue('tabId' in details &&
188                             typeof details.tabId === 'number');
189     var key = details.tabId + "-" + details.frameId;
190     if (details.type == "main_frame" || details.type == "sub_frame") {
191       tabAndFrameUrls[key] = details.url;
192     }
193     details.frameUrl = tabAndFrameUrls[key] || "unknown frame URL";
194   }
195
196   // This assigns unique IDs to frames. The new IDs are only deterministic, if
197   // the frames documents are loaded in order. Don't write browser tests with
198   // more than one frame ID and rely on their numbers.
199   if (!(details.frameId in frameIdMap)) {
200     // Subtract one to discount for {"-1": -1} mapping that always exists.
201     // This gives the first frame the ID 0.
202     frameIdMap[details.frameId] = Object.keys(frameIdMap).length - 1;
203   }
204   details.frameId = frameIdMap[details.frameId];
205   details.parentFrameId = frameIdMap[details.parentFrameId];
206
207   // This assigns unique IDs to newly opened tabs. However, the new IDs are only
208   // deterministic, if the order in which the tabs are opened is deterministic.
209   if (!(details.tabId in tabIdMap)) {
210     tabIdMap[details.tabId] = Object.keys(tabIdMap).length;
211   }
212   details.tabId = tabIdMap[details.tabId];
213
214   delete details.requestId;
215   delete details.timeStamp;
216   if (details.requestHeaders) {
217     details.requestHeadersValid = checkUserAgent(details.requestHeaders);
218     delete details.requestHeaders;
219   }
220   if (details.responseHeaders) {
221     details.responseHeadersExist = true;
222     delete details.responseHeaders;
223   }
224
225   // find |details| in expectedEventData
226   var found = false;
227   var label = undefined;
228   expectedEventData.forEach(function (exp) {
229     if (deepEq(exp.event, name) && deepEq(exp.details, details)) {
230       if (found) {
231         chrome.test.fail("Received event twice '" + name + "':" +
232             JSON.stringify(details));
233       } else {
234         found = true;
235         label = exp.label;
236       }
237     }
238   });
239   if (!found && !ignoreUnexpected) {
240     console.log("Expected events: " +
241         JSON.stringify(expectedEventData, null, 2));
242     chrome.test.fail("Received unexpected event '" + name + "':" +
243         JSON.stringify(details, null, 2));
244   }
245
246   if (found) {
247     if (logAllRequests) {
248       console.log("Expected: " + name + ": " + JSON.stringify(details));
249     }
250     capturedEventData.push({label: label, event: name, details: details});
251
252     // checkExpecations decrements the counter of pending events. We may only
253     // call it if an expected event has occurred.
254     checkExpectations();
255   } else {
256     if (logAllRequests) {
257       console.log("NOT Expected: " + name + ": " + JSON.stringify(details));
258     }
259     capturedUnexpectedData.push({label: label, event: name, details: details});
260   }
261
262   if (callback) {
263     window.setTimeout(callback, 0, retval);
264   } else {
265     return retval;
266   }
267 }
268
269 // Simple array intersection. We use this to filter extraInfoSpec so
270 // that only the allowed specs are sent to each listener.
271 function intersect(array1, array2) {
272   return array1.filter(function(x) { return array2.indexOf(x) != -1; });
273 }
274
275 function initListeners(filter, extraInfoSpec) {
276   chrome.webRequest.onBeforeRequest.addListener(
277       function(details) {
278     return captureEvent("onBeforeRequest", details);
279   }, filter, intersect(extraInfoSpec, ["blocking", "requestBody"]));
280   chrome.webRequest.onBeforeSendHeaders.addListener(
281       function(details) {
282     return captureEvent("onBeforeSendHeaders", details);
283   }, filter, intersect(extraInfoSpec, ["blocking", "requestHeaders"]));
284   chrome.webRequest.onSendHeaders.addListener(
285       function(details) {
286     return captureEvent("onSendHeaders", details);
287   }, filter, intersect(extraInfoSpec, ["requestHeaders"]));
288   chrome.webRequest.onHeadersReceived.addListener(
289       function(details) {
290     return captureEvent("onHeadersReceived", details);
291   }, filter, intersect(extraInfoSpec, ["blocking", "responseHeaders"]));
292   chrome.webRequest.onAuthRequired.addListener(
293       function(details, callback) {
294     return captureEvent("onAuthRequired", details, callback);
295   }, filter, intersect(extraInfoSpec, ["asyncBlocking", "blocking",
296                                        "responseHeaders"]));
297   chrome.webRequest.onResponseStarted.addListener(
298       function(details) {
299     return captureEvent("onResponseStarted", details);
300   }, filter, intersect(extraInfoSpec, ["responseHeaders"]));
301   chrome.webRequest.onBeforeRedirect.addListener(
302       function(details) {
303     return captureEvent("onBeforeRedirect", details);
304   }, filter, intersect(extraInfoSpec, ["responseHeaders"]));
305   chrome.webRequest.onCompleted.addListener(
306       function(details) {
307     return captureEvent("onCompleted", details);
308   }, filter, intersect(extraInfoSpec, ["responseHeaders"]));
309   chrome.webRequest.onErrorOccurred.addListener(
310       function(details) {
311     return captureEvent("onErrorOccurred", details);
312   }, filter);
313 }
314
315 function removeListeners() {
316   function helper(event) {
317     // Note: We're poking at the internal event data, but it's easier than
318     // the alternative. If this starts failing, we just need to update this
319     // helper.
320     for (var i in event.subEvents_) {
321       event.removeListener(event.subEvents_[i].callback);
322     }
323     chrome.test.assertFalse(event.hasListeners());
324   }
325   helper(chrome.webRequest.onBeforeRequest);
326   helper(chrome.webRequest.onBeforeSendHeaders);
327   helper(chrome.webRequest.onAuthRequired);
328   helper(chrome.webRequest.onSendHeaders);
329   helper(chrome.webRequest.onHeadersReceived);
330   helper(chrome.webRequest.onResponseStarted);
331   helper(chrome.webRequest.onBeforeRedirect);
332   helper(chrome.webRequest.onCompleted);
333   helper(chrome.webRequest.onErrorOccurred);
334 }
335
336 function resetDeclarativeRules() {
337   chrome.declarativeWebRequest.onRequest.removeRules();
338 }