OLD | NEW |
| (Empty) |
1 var getURL = chrome.extension.getURL; | |
2 var deepEq = chrome.test.checkDeepEq; | |
3 var expectedEventData; | |
4 var capturedEventData; | |
5 var expectedEventOrder; | |
6 var tabId; | |
7 var testServerPort; | |
8 var eventsCaptured; | |
9 | |
10 function runTests(tests) { | |
11 chrome.tabs.create({url: "about:blank"}, function(tab) { | |
12 tabId = tab.id; | |
13 chrome.test.getConfig(function(config) { | |
14 testServerPort = config.testServer.port; | |
15 chrome.test.runTests(tests); | |
16 }); | |
17 }); | |
18 } | |
19 | |
20 // Returns an URL from the test server, fixing up the port. Must be called | |
21 // from within a test case passed to runTests. | |
22 function getServerURL(path) { | |
23 if (!testServerPort) | |
24 throw new Error("Called getServerURL outside of runTests."); | |
25 return "http://www.a.com:" + testServerPort + "/" + path; | |
26 } | |
27 | |
28 // Helper to advance to the next test only when the tab has finished loading. | |
29 // This is because tabs.update can sometimes fail if the tab is in the middle | |
30 // of a navigation (from the previous test), resulting in flakiness. | |
31 function navigateAndWait(url, callback) { | |
32 var done = chrome.test.listenForever(chrome.tabs.onUpdated, | |
33 function (_, info, tab) { | |
34 if (tab.id == tabId && info.status == "complete") { | |
35 if (callback) callback(); | |
36 done(); | |
37 } | |
38 }); | |
39 chrome.tabs.update(tabId, {url: url}); | |
40 } | |
41 | |
42 // data: array of extected events, each one is a dictionary: | |
43 // { label: "<unique identifier>", | |
44 // event: "<webrequest event type>", | |
45 // details: { <expected details of the webrequest event> }, | |
46 // retval: { <dictionary that the event handler shall return> } (optional) | |
47 // } | |
48 // order: an array of sequences, e.g. [ ["a", "b", "c"], ["d", "e"] ] means that | |
49 // event with label "a" needs to occur before event with label "b". The | |
50 // relative order of "a" and "d" does not matter. | |
51 // filter: filter dictionary passed on to the event subscription of the | |
52 // webRequest API. | |
53 // extraInfoSpec: the union of all desired extraInfoSpecs for the events. | |
54 function expect(data, order, filter, extraInfoSpec) { | |
55 expectedEventData = data; | |
56 capturedEventData = []; | |
57 expectedEventOrder = order; | |
58 eventsCaptured = chrome.test.callbackAdded(); | |
59 tabAndFrameUrls = {}; // Maps "{tabId}-{frameId}" to the URL of the frame. | |
60 removeListeners(); | |
61 initListeners(filter || {}, extraInfoSpec || []); | |
62 } | |
63 | |
64 function checkExpectations() { | |
65 if (capturedEventData.length < expectedEventData.length) { | |
66 return; | |
67 } | |
68 if (capturedEventData.length > expectedEventData.length) { | |
69 chrome.test.fail("Recorded too many events. " + | |
70 JSON.stringify(capturedEventData)); | |
71 return; | |
72 } | |
73 // We have ensured that capturedEventData contains exactly the same elements | |
74 // as expectedEventData. Now we need to verify the ordering. | |
75 // Step 1: build positions such that | |
76 // positions[<event-label>]=<position of this event in capturedEventData> | |
77 var curPos = 0; | |
78 var positions = {} | |
79 capturedEventData.forEach(function (event) { | |
80 chrome.test.assertTrue(event.hasOwnProperty("label")); | |
81 positions[event.label] = curPos; | |
82 curPos++; | |
83 }); | |
84 // Step 2: check that elements arrived in correct order | |
85 expectedEventOrder.forEach(function (order) { | |
86 var previousLabel = undefined; | |
87 order.forEach(function(label) { | |
88 if (previousLabel === undefined) { | |
89 previousLabel = label; | |
90 return; | |
91 } | |
92 chrome.test.assertTrue(positions[previousLabel] < positions[label], | |
93 "Event " + previousLabel + " is supposed to arrive before " + | |
94 label + "."); | |
95 previousLabel = label; | |
96 }); | |
97 }); | |
98 | |
99 eventsCaptured(); | |
100 } | |
101 | |
102 // Simple check to see that we have a User-Agent header, and that it contains | |
103 // an expected value. This is a basic check that the request headers are valid. | |
104 function checkUserAgent(headers) { | |
105 for (var i in headers) { | |
106 if (headers[i].name.toLowerCase() == "user-agent") | |
107 return headers[i].value.toLowerCase().indexOf("chrome") != -1; | |
108 } | |
109 return false; | |
110 } | |
111 | |
112 function captureEvent(name, details) { | |
113 // Ignore system-level requests like safebrowsing updates and favicon fetches | |
114 // since they are unpredictable. | |
115 if (details.tabId == -1 || details.type == "other" || | |
116 details.url.match(/\/favicon.ico$/) || | |
117 details.url.match(/https:\/\/dl.google.com/)) | |
118 return; | |
119 | |
120 // Pull the extra per-event options out of the expected data. These let | |
121 // us specify special return values per event. | |
122 var currentIndex = capturedEventData.length; | |
123 var extraOptions; | |
124 if (expectedEventData.length > currentIndex) { | |
125 retval = expectedEventData[currentIndex].retval; | |
126 } | |
127 | |
128 // Check that the frameId can be used to reliably determine the URL of the | |
129 // frame that caused requests. | |
130 if (name == "onBeforeRequest") { | |
131 chrome.test.assertTrue('frameId' in details && | |
132 typeof details.frameId === 'number'); | |
133 chrome.test.assertTrue('tabId' in details && | |
134 typeof details.tabId === 'number'); | |
135 var key = details.tabId + "-" + details.frameId; | |
136 if (details.type == "main_frame" || details.type == "sub_frame") { | |
137 tabAndFrameUrls[key] = details.url; | |
138 } | |
139 details.frameUrl = tabAndFrameUrls[key] || "unknown frame URL"; | |
140 } | |
141 delete details.frameId; | |
142 | |
143 delete details.requestId; | |
144 delete details.timeStamp; | |
145 if (details.requestHeaders) { | |
146 details.requestHeadersValid = checkUserAgent(details.requestHeaders); | |
147 delete details.requestHeaders; | |
148 } | |
149 if (details.responseHeaders) { | |
150 details.responseHeadersExist = true; | |
151 delete details.responseHeaders; | |
152 } | |
153 | |
154 // find |details| in expectedEventData | |
155 var found = false; | |
156 var label = undefined; | |
157 expectedEventData.forEach(function (exp) { | |
158 if (deepEq(exp.event, name) && deepEq(exp.details, details)) { | |
159 if (found) { | |
160 chrome.test.fail("Received event twice '" + name + "':" + | |
161 JSON.stringify(details)); | |
162 } else { | |
163 found = true; | |
164 label = exp.label; | |
165 } | |
166 } | |
167 }); | |
168 if (!found) { | |
169 chrome.test.fail("Received unexpected event '" + name + "':" + | |
170 JSON.stringify(details)); | |
171 } | |
172 | |
173 capturedEventData.push({label: label, event: name, details: details}); | |
174 checkExpectations(); | |
175 return retval; | |
176 } | |
177 | |
178 // Simple array intersection. We use this to filter extraInfoSpec so | |
179 // that only the allowed specs are sent to each listener. | |
180 function intersect(array1, array2) { | |
181 return array1.filter(function(x) { return array2.indexOf(x) != -1; }); | |
182 } | |
183 | |
184 function initListeners(filter, extraInfoSpec) { | |
185 chrome.experimental.webRequest.onBeforeRequest.addListener( | |
186 function(details) { | |
187 return captureEvent("onBeforeRequest", details); | |
188 }, filter, intersect(extraInfoSpec, ["blocking"])); | |
189 chrome.experimental.webRequest.onBeforeSendHeaders.addListener( | |
190 function(details) { | |
191 return captureEvent("onBeforeSendHeaders", details); | |
192 }, filter, intersect(extraInfoSpec, ["blocking", "requestHeaders"])); | |
193 chrome.experimental.webRequest.onSendHeaders.addListener( | |
194 function(details) { | |
195 return captureEvent("onSendHeaders", details); | |
196 }, filter, intersect(extraInfoSpec, ["requestHeaders"])); | |
197 chrome.experimental.webRequest.onAuthRequired.addListener( | |
198 function(details) { | |
199 return captureEvent("onAuthRequired", details); | |
200 }, filter, intersect(extraInfoSpec, ["responseHeaders", "statusLine"])); | |
201 chrome.experimental.webRequest.onResponseStarted.addListener( | |
202 function(details) { | |
203 return captureEvent("onResponseStarted", details); | |
204 }, filter, intersect(extraInfoSpec, ["responseHeaders", "statusLine"])); | |
205 chrome.experimental.webRequest.onBeforeRedirect.addListener( | |
206 function(details) { | |
207 return captureEvent("onBeforeRedirect", details); | |
208 }, filter, intersect(extraInfoSpec, ["responseHeaders", "statusLine"])); | |
209 chrome.experimental.webRequest.onCompleted.addListener( | |
210 function(details) { | |
211 return captureEvent("onCompleted", details); | |
212 }, filter, intersect(extraInfoSpec, ["responseHeaders", "statusLine"])); | |
213 chrome.experimental.webRequest.onErrorOccurred.addListener( | |
214 function(details) { | |
215 return captureEvent("onErrorOccurred", details); | |
216 }, filter); | |
217 } | |
218 | |
219 function removeListeners() { | |
220 function helper(event) { | |
221 // Note: We're poking at the internal event data, but it's easier than | |
222 // the alternative. If this starts failing, we just need to update this | |
223 // helper. | |
224 for (var cb in event.callbackMap_) { | |
225 event.removeListener(cb); | |
226 } | |
227 chrome.test.assertFalse(event.hasListeners()); | |
228 } | |
229 helper(chrome.experimental.webRequest.onBeforeRequest); | |
230 helper(chrome.experimental.webRequest.onBeforeSendHeaders); | |
231 helper(chrome.experimental.webRequest.onAuthRequired); | |
232 helper(chrome.experimental.webRequest.onSendHeaders); | |
233 helper(chrome.experimental.webRequest.onResponseStarted); | |
234 helper(chrome.experimental.webRequest.onBeforeRedirect); | |
235 helper(chrome.experimental.webRequest.onCompleted); | |
236 helper(chrome.experimental.webRequest.onErrorOccurred); | |
237 } | |
OLD | NEW |