Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: chrome/test/data/extensions/api_test/webrequest_public_session/framework.js

Issue 2455393002: PS - Adjusting webRequest API for use in Public Sessions (Closed)
Patch Set: webRequest and webRequestBlocking are safe permissions now Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2016 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 var listeners = {
19 'onBeforeRequest': [],
20 'onBeforeSendHeaders': [],
21 'onAuthRequired': [],
22 'onSendHeaders': [],
23 'onHeadersReceived': [],
24 'onResponseStarted': [],
25 'onBeforeRedirect': [],
26 'onCompleted': [],
27 'onErrorOccurred': []
28 };
29
30 // If true, don't bark on events that were not registered via expect().
31 // These events are recorded in capturedUnexpectedData instead of
32 // capturedEventData.
33 var ignoreUnexpected = false;
34
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;
38
39 function runTests(tests) {
40 var waitForAboutBlank = function(_, info, tab) {
41 if (info.status == "complete" && tab.url == "about:blank") {
42 tabId = tab.id;
43 tabIdMap = {"-1": -1};
44 tabIdMap[tabId] = 0;
45 chrome.tabs.onUpdated.removeListener(waitForAboutBlank);
46 chrome.test.getConfig(function(config) {
47 testServerPort = config.testServer.port;
48 chrome.test.runTests(tests);
49 });
50 }
51 };
52 chrome.tabs.onUpdated.addListener(waitForAboutBlank);
53 chrome.tabs.create({url: "about:blank"});
54 }
55
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) {
59 if (!testServerPort)
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;
64 }
65
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();
74 done();
75 }
76 });
77 chrome.tabs.update(tabId, {url: url});
78 }
79
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)
85 // }
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
90 // webRequest API.
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();
99 }
100 tabAndFrameUrls = {}; // Maps "{tabId}-{frameId}" to the URL of the frame.
101 frameIdMap = {"-1": -1, "0": 0};
102 removeListeners();
103 initListeners(filter || {urls: ["<all_urls>"]}, extraInfoSpec || []);
104 // Fill in default values.
105 for (var i = 0; i < expectedEventData.length; ++i) {
106 if (!('method' in expectedEventData[i].details)) {
107 expectedEventData[i].details.method = "GET";
108 }
109 if (!('tabId' in expectedEventData[i].details)) {
110 expectedEventData[i].details.tabId = tabIdMap[tabId];
111 }
112 if (!('frameId' in expectedEventData[i].details)) {
113 expectedEventData[i].details.frameId = 0;
114 }
115 if (!('parentFrameId' in expectedEventData[i].details)) {
116 expectedEventData[i].details.parentFrameId = -1;
117 }
118 if (!('type' in expectedEventData[i].details)) {
119 expectedEventData[i].details.type = "main_frame";
120 }
121 }
122 }
123
124 function checkExpectations() {
125 if (capturedEventData.length < expectedEventData.length) {
126 return;
127 }
128 if (capturedEventData.length > expectedEventData.length) {
129 chrome.test.fail("Recorded too many events. " +
130 JSON.stringify(capturedEventData));
131 return;
132 }
133 // We have ensured that capturedEventData contains exactly the same elements
134 // as expectedEventData. Now we need to verify the ordering.
135 // Step 1: build positions such that
136 // positions[<event-label>]=<position of this event in capturedEventData>
137 var curPos = 0;
138 var positions = {}
139 capturedEventData.forEach(function (event) {
140 chrome.test.assertTrue(event.hasOwnProperty("label"));
141 positions[event.label] = curPos;
142 curPos++;
143 });
144 // Step 2: check that elements arrived in correct order
145 expectedEventOrder.forEach(function (order) {
146 var previousLabel = undefined;
147 order.forEach(function(label) {
148 if (previousLabel === undefined) {
149 previousLabel = label;
150 return;
151 }
152 chrome.test.assertTrue(positions[previousLabel] < positions[label],
153 "Event " + previousLabel + " is supposed to arrive before " +
154 label + ".");
155 previousLabel = label;
156 });
157 });
158
159 eventsCaptured();
160 }
161
162 // Simple check to see that we have a User-Agent header, and that it contains
163 // an expected value. This is a basic check that the request headers are valid.
164 function checkUserAgent(headers) {
165 for (var i in headers) {
166 if (headers[i].name.toLowerCase() == "user-agent")
167 return headers[i].value.toLowerCase().indexOf("chrome") != -1;
168 }
169 return false;
170 }
171
172 // Whether the request is missing a tabId and frameId and we're not expecting
173 // a request with the given details. If the method returns true, the event
174 // should be ignored.
175 function isUnexpectedDetachedRequest(name, details) {
176 // This function is responsible for marking detached requests as unexpected.
177 // Non-detached requests are not this function's concern.
178 if (details.tabId !== -1 || details.frameId >= 0)
179 return false;
180
181 // Only return true if there is no matching expectation for the given details.
182 return !expectedEventData.some(function(exp) {
183 var didMatchTabAndFrameId =
184 exp.details.tabId === -1 &&
185 exp.details.frameId === -1;
186
187 // Accept non-matching tabId/frameId for ping/beacon requests because these
188 // requests can continue after a frame is removed. And due to a bug, such
189 // requests have a tabId/frameId of -1.
190 // The test will fail anyway, but then with a helpful error (expectation
191 // differs from actual events) instead of an obscure test timeout.
192 // TODO(robwu): Remove this once https://crbug.com/522129 gets fixed.
193 didMatchTabAndFrameId = didMatchTabAndFrameId || details.type === 'ping';
194
195 return name === exp.event &&
196 didMatchTabAndFrameId &&
197 exp.details.method === details.method &&
198 exp.details.url === details.url &&
199 exp.details.type === details.type;
200 });
201 }
202
203 function captureEvent(name, details, callback) {
204 // Ignore system-level requests like safebrowsing updates and favicon fetches
205 // since they are unpredictable.
206 if ((details.type == "other" && !details.url.includes('dont-ignore-me')) ||
207 isUnexpectedDetachedRequest(name, details) ||
208 details.url.match(/\/favicon.ico$/) ||
209 details.url.match(/https:\/\/dl.google.com/))
210 return;
211
212 // Pull the extra per-event options out of the expected data. These let
213 // us specify special return values per event.
214 var currentIndex = capturedEventData.length;
215 var extraOptions;
216 var retval;
217 if (expectedEventData.length > currentIndex) {
218 retval =
219 expectedEventData[currentIndex].retval_function ?
220 expectedEventData[currentIndex].retval_function(name, details) :
221 expectedEventData[currentIndex].retval;
222 }
223
224 // Check that the frameId can be used to reliably determine the URL of the
225 // frame that caused requests.
226 if (name == "onBeforeRequest") {
227 chrome.test.assertTrue('frameId' in details &&
228 typeof details.frameId === 'number');
229 chrome.test.assertTrue('tabId' in details &&
230 typeof details.tabId === 'number');
231 var key = details.tabId + "-" + details.frameId;
232 if (details.type == "main_frame" || details.type == "sub_frame") {
233 tabAndFrameUrls[key] = details.url;
234 }
235 details.frameUrl = tabAndFrameUrls[key] || "unknown frame URL";
236 }
237
238 // This assigns unique IDs to frames. The new IDs are only deterministic, if
239 // the frames documents are loaded in order. Don't write browser tests with
240 // more than one frame ID and rely on their numbers.
241 if (!(details.frameId in frameIdMap)) {
242 // Subtract one to discount for {"-1": -1} mapping that always exists.
243 // This gives the first frame the ID 0.
244 frameIdMap[details.frameId] = Object.keys(frameIdMap).length - 1;
245 }
246 details.frameId = frameIdMap[details.frameId];
247 details.parentFrameId = frameIdMap[details.parentFrameId];
248
249 // This assigns unique IDs to newly opened tabs. However, the new IDs are only
250 // deterministic, if the order in which the tabs are opened is deterministic.
251 if (!(details.tabId in tabIdMap)) {
252 // Subtract one because the map is initialized with {"-1": -1}, and the
253 // first tab has ID 0.
254 tabIdMap[details.tabId] = Object.keys(tabIdMap).length - 1;
255 }
256 details.tabId = tabIdMap[details.tabId];
257
258 delete details.requestId;
259 delete details.timeStamp;
260 if (details.requestHeaders) {
261 details.requestHeadersValid = checkUserAgent(details.requestHeaders);
262 delete details.requestHeaders;
263 }
264 if (details.responseHeaders) {
265 details.responseHeadersExist = true;
266 delete details.responseHeaders;
267 }
268
269 // find |details| in expectedEventData
270 var found = false;
271 var label = undefined;
272 expectedEventData.forEach(function (exp) {
273 if (deepEq(exp.event, name) && deepEq(exp.details, details)) {
274 if (found) {
275 chrome.test.fail("Received event twice '" + name + "':" +
276 JSON.stringify(details));
277 } else {
278 found = true;
279 label = exp.label;
280 }
281 }
282 });
283 if (!found && !ignoreUnexpected) {
284 console.log("Expected events: " +
285 JSON.stringify(expectedEventData, null, 2));
286 chrome.test.fail("Received unexpected event '" + name + "':" +
287 JSON.stringify(details, null, 2));
288 }
289
290 if (found) {
291 if (logAllRequests) {
292 console.log("Expected: " + name + ": " + JSON.stringify(details));
293 }
294 capturedEventData.push({label: label, event: name, details: details});
295
296 // checkExpecations decrements the counter of pending events. We may only
297 // call it if an expected event has occurred.
298 checkExpectations();
299 } else {
300 if (logAllRequests) {
301 console.log("NOT Expected: " + name + ": " + JSON.stringify(details));
302 }
303 capturedUnexpectedData.push({label: label, event: name, details: details});
304 }
305
306 if (callback) {
307 window.setTimeout(callback, 0, retval);
308 } else {
309 return retval;
310 }
311 }
312
313 // Simple array intersection. We use this to filter extraInfoSpec so
314 // that only the allowed specs are sent to each listener.
315 function intersect(array1, array2) {
316 return array1.filter(function(x) { return array2.indexOf(x) != -1; });
317 }
318
319 function initListeners(filter, extraInfoSpec) {
320 var onBeforeRequest = function(details) {
321 return captureEvent("onBeforeRequest", details);
322 };
323 listeners['onBeforeRequest'].push(onBeforeRequest);
324
325 var onBeforeSendHeaders = function(details) {
326 return captureEvent("onBeforeSendHeaders", details);
327 };
328 listeners['onBeforeSendHeaders'].push(onBeforeSendHeaders);
329
330 var onSendHeaders = function(details) {
331 return captureEvent("onSendHeaders", details);
332 };
333 listeners['onSendHeaders'].push(onSendHeaders);
334
335 var onHeadersReceived = function(details) {
336 return captureEvent("onHeadersReceived", details);
337 };
338 listeners['onHeadersReceived'].push(onHeadersReceived);
339
340 var onAuthRequired = function(details) {
341 return captureEvent("onAuthRequired", details, callback);
342 };
343 listeners['onAuthRequired'].push(onAuthRequired);
344
345 var onResponseStarted = function(details) {
346 return captureEvent("onResponseStarted", details);
347 };
348 listeners['onResponseStarted'].push(onResponseStarted);
349
350 var onBeforeRedirect = function(details) {
351 return captureEvent("onBeforeRedirect", details);
352 };
353 listeners['onBeforeRedirect'].push(onBeforeRedirect);
354
355 var onCompleted = function(details) {
356 return captureEvent("onCompleted", details);
357 };
358 listeners['onCompleted'].push(onCompleted);
359
360 var onErrorOccurred = function(details) {
361 return captureEvent("onErrorOccurred", details);
362 };
363 listeners['onErrorOccurred'].push(onErrorOccurred);
364
365 chrome.webRequest.onBeforeRequest.addListener(
366 onBeforeRequest, filter,
367 intersect(extraInfoSpec, ["blocking", "requestBody"]));
368
369 chrome.webRequest.onBeforeSendHeaders.addListener(
370 onBeforeSendHeaders, filter,
371 intersect(extraInfoSpec, ["blocking", "requestHeaders"]));
372
373 chrome.webRequest.onSendHeaders.addListener(
374 onSendHeaders, filter,
375 intersect(extraInfoSpec, ["requestHeaders"]));
376
377 chrome.webRequest.onHeadersReceived.addListener(
378 onHeadersReceived, filter,
379 intersect(extraInfoSpec, ["blocking", "responseHeaders"]));
380
381 chrome.webRequest.onAuthRequired.addListener(
382 onAuthRequired, filter,
383 intersect(extraInfoSpec, ["asyncBlocking", "blocking",
384 "responseHeaders"]));
385
386 chrome.webRequest.onResponseStarted.addListener(
387 onResponseStarted, filter,
388 intersect(extraInfoSpec, ["responseHeaders"]));
389
390 chrome.webRequest.onBeforeRedirect.addListener(
391 onBeforeRedirect, filter, intersect(extraInfoSpec, ["responseHeaders"]));
392
393 chrome.webRequest.onCompleted.addListener(
394 onCompleted, filter,
395 intersect(extraInfoSpec, ["responseHeaders"]));
396
397 chrome.webRequest.onErrorOccurred.addListener(onErrorOccurred, filter);
398 }
399
400 function removeListeners() {
401 function helper(eventName) {
402 for (var i in listeners[eventName]) {
403 chrome.webRequest[eventName].removeListener(listeners[eventName][i]);
404 }
405 listeners[eventName].length = 0;
406 chrome.test.assertFalse(chrome.webRequest[eventName].hasListeners());
407 }
408 helper('onBeforeRequest');
409 helper('onBeforeSendHeaders');
410 helper('onAuthRequired');
411 helper('onSendHeaders');
412 helper('onHeadersReceived');
413 helper('onResponseStarted');
414 helper('onBeforeRedirect');
415 helper('onCompleted');
416 helper('onErrorOccurred');
417 }
OLDNEW
« no previous file with comments | « chrome/test/BUILD.gn ('k') | chrome/test/data/extensions/api_test/webrequest_public_session/manifest.json » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698