Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
|
Devlin
2016/11/15 15:12:02
Is this file just copy-pasted?
Ivan Šandrk
2016/11/15 16:07:40
Yes. I couldn't figure out how to include the orig
| |
| 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 } | |
| OLD | NEW |