|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 "use strict"; |
|
4 |
|
5 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; |
|
6 |
|
7 let { Services } = Cu.import("resource://gre/modules/Services.jsm", {}); |
|
8 let { Task } = Cu.import("resource://gre/modules/Task.jsm", {}); |
|
9 let { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
10 let { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {}); |
|
11 let { devtools } = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
|
12 let { CurlUtils } = Cu.import("resource:///modules/devtools/Curl.jsm", {}); |
|
13 let TargetFactory = devtools.TargetFactory; |
|
14 let Toolbox = devtools.Toolbox; |
|
15 |
|
16 const EXAMPLE_URL = "http://example.com/browser/browser/devtools/netmonitor/test/"; |
|
17 |
|
18 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html"; |
|
19 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html"; |
|
20 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html"; |
|
21 const CONTENT_TYPE_WITHOUT_CACHE_URL = EXAMPLE_URL + "html_content-type-without-cache-test-page.html"; |
|
22 const CYRILLIC_URL = EXAMPLE_URL + "html_cyrillic-test-page.html"; |
|
23 const STATUS_CODES_URL = EXAMPLE_URL + "html_status-codes-test-page.html"; |
|
24 const POST_DATA_URL = EXAMPLE_URL + "html_post-data-test-page.html"; |
|
25 const POST_RAW_URL = EXAMPLE_URL + "html_post-raw-test-page.html"; |
|
26 const POST_RAW_WITH_HEADERS_URL = EXAMPLE_URL + "html_post-raw-with-headers-test-page.html"; |
|
27 const PARAMS_URL = EXAMPLE_URL + "html_params-test-page.html"; |
|
28 const JSONP_URL = EXAMPLE_URL + "html_jsonp-test-page.html"; |
|
29 const JSON_LONG_URL = EXAMPLE_URL + "html_json-long-test-page.html"; |
|
30 const JSON_MALFORMED_URL = EXAMPLE_URL + "html_json-malformed-test-page.html"; |
|
31 const JSON_CUSTOM_MIME_URL = EXAMPLE_URL + "html_json-custom-mime-test-page.html"; |
|
32 const JSON_TEXT_MIME_URL = EXAMPLE_URL + "html_json-text-mime-test-page.html"; |
|
33 const SORTING_URL = EXAMPLE_URL + "html_sorting-test-page.html"; |
|
34 const FILTERING_URL = EXAMPLE_URL + "html_filter-test-page.html"; |
|
35 const INFINITE_GET_URL = EXAMPLE_URL + "html_infinite-get-page.html"; |
|
36 const CUSTOM_GET_URL = EXAMPLE_URL + "html_custom-get-page.html"; |
|
37 const SINGLE_GET_URL = EXAMPLE_URL + "html_single-get-page.html"; |
|
38 const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html"; |
|
39 const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html"; |
|
40 const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html"; |
|
41 |
|
42 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs"; |
|
43 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs"; |
|
44 const STATUS_CODES_SJS = EXAMPLE_URL + "sjs_status-codes-test-server.sjs"; |
|
45 const SORTING_SJS = EXAMPLE_URL + "sjs_sorting-test-server.sjs"; |
|
46 |
|
47 const TEST_IMAGE = EXAMPLE_URL + "test-image.png"; |
|
48 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg=="; |
|
49 |
|
50 gDevTools.testing = true; |
|
51 SimpleTest.registerCleanupFunction(() => { |
|
52 gDevTools.testing = false; |
|
53 }); |
|
54 |
|
55 // All tests are asynchronous. |
|
56 waitForExplicitFinish(); |
|
57 |
|
58 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log"); |
|
59 // To enable logging for try runs, just set the pref to true. |
|
60 Services.prefs.setBoolPref("devtools.debugger.log", false); |
|
61 |
|
62 // Always reset some prefs to their original values after the test finishes. |
|
63 const gDefaultFilters = Services.prefs.getCharPref("devtools.netmonitor.filters"); |
|
64 |
|
65 registerCleanupFunction(() => { |
|
66 info("finish() was called, cleaning up..."); |
|
67 |
|
68 Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging); |
|
69 Services.prefs.setCharPref("devtools.netmonitor.filters", gDefaultFilters); |
|
70 }); |
|
71 |
|
72 function addTab(aUrl, aWindow) { |
|
73 info("Adding tab: " + aUrl); |
|
74 |
|
75 let deferred = promise.defer(); |
|
76 let targetWindow = aWindow || window; |
|
77 let targetBrowser = targetWindow.gBrowser; |
|
78 |
|
79 targetWindow.focus(); |
|
80 let tab = targetBrowser.selectedTab = targetBrowser.addTab(aUrl); |
|
81 let browser = tab.linkedBrowser; |
|
82 |
|
83 browser.addEventListener("load", function onLoad() { |
|
84 browser.removeEventListener("load", onLoad, true); |
|
85 deferred.resolve(tab); |
|
86 }, true); |
|
87 |
|
88 return deferred.promise; |
|
89 } |
|
90 |
|
91 function removeTab(aTab, aWindow) { |
|
92 info("Removing tab."); |
|
93 |
|
94 let targetWindow = aWindow || window; |
|
95 let targetBrowser = targetWindow.gBrowser; |
|
96 |
|
97 targetBrowser.removeTab(aTab); |
|
98 } |
|
99 |
|
100 function initNetMonitor(aUrl, aWindow) { |
|
101 info("Initializing a network monitor pane."); |
|
102 |
|
103 return addTab(aUrl).then((aTab) => { |
|
104 info("Net tab added successfully: " + aUrl); |
|
105 |
|
106 let deferred = promise.defer(); |
|
107 let debuggee = aTab.linkedBrowser.contentWindow.wrappedJSObject; |
|
108 let target = TargetFactory.forTab(aTab); |
|
109 |
|
110 gDevTools.showToolbox(target, "netmonitor").then((aToolbox) => { |
|
111 info("Netork monitor pane shown successfully."); |
|
112 |
|
113 let monitor = aToolbox.getCurrentPanel(); |
|
114 deferred.resolve([aTab, debuggee, monitor]); |
|
115 }); |
|
116 |
|
117 return deferred.promise; |
|
118 }); |
|
119 } |
|
120 |
|
121 function restartNetMonitor(aMonitor, aNewUrl) { |
|
122 info("Restarting the specified network monitor."); |
|
123 |
|
124 let deferred = promise.defer(); |
|
125 let tab = aMonitor.target.tab; |
|
126 let url = aNewUrl || tab.linkedBrowser.contentWindow.wrappedJSObject.location.href; |
|
127 |
|
128 aMonitor.once("destroyed", () => initNetMonitor(url).then(deferred.resolve)); |
|
129 removeTab(tab); |
|
130 |
|
131 return deferred.promise; |
|
132 } |
|
133 |
|
134 function teardown(aMonitor) { |
|
135 info("Destroying the specified network monitor."); |
|
136 |
|
137 let deferred = promise.defer(); |
|
138 let tab = aMonitor.target.tab; |
|
139 |
|
140 aMonitor.once("destroyed", () => executeSoon(deferred.resolve)); |
|
141 removeTab(tab); |
|
142 |
|
143 return deferred.promise; |
|
144 } |
|
145 |
|
146 function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) { |
|
147 let deferred = promise.defer(); |
|
148 |
|
149 let panel = aMonitor.panelWin; |
|
150 let genericEvents = 0; |
|
151 let postEvents = 0; |
|
152 |
|
153 function onGenericEvent() { |
|
154 genericEvents++; |
|
155 maybeResolve(); |
|
156 } |
|
157 |
|
158 function onPostEvent() { |
|
159 postEvents++; |
|
160 maybeResolve(); |
|
161 } |
|
162 |
|
163 function maybeResolve() { |
|
164 info("> Network events progress: " + |
|
165 genericEvents + "/" + ((aGetRequests + aPostRequests) * 13) + ", " + |
|
166 postEvents + "/" + (aPostRequests * 2)); |
|
167 |
|
168 // There are 15 updates which need to be fired for a request to be |
|
169 // considered finished. RequestPostData isn't fired for non-POST requests. |
|
170 if (genericEvents == (aGetRequests + aPostRequests) * 13 && |
|
171 postEvents == aPostRequests * 2) { |
|
172 |
|
173 panel.off(panel.EVENTS.UPDATING_REQUEST_HEADERS, onGenericEvent); |
|
174 panel.off(panel.EVENTS.RECEIVED_REQUEST_HEADERS, onGenericEvent); |
|
175 panel.off(panel.EVENTS.UPDATING_REQUEST_COOKIES, onGenericEvent); |
|
176 panel.off(panel.EVENTS.RECEIVED_REQUEST_COOKIES, onGenericEvent); |
|
177 panel.off(panel.EVENTS.UPDATING_REQUEST_POST_DATA, onPostEvent); |
|
178 panel.off(panel.EVENTS.RECEIVED_REQUEST_POST_DATA, onPostEvent); |
|
179 panel.off(panel.EVENTS.UPDATING_RESPONSE_HEADERS, onGenericEvent); |
|
180 panel.off(panel.EVENTS.RECEIVED_RESPONSE_HEADERS, onGenericEvent); |
|
181 panel.off(panel.EVENTS.UPDATING_RESPONSE_COOKIES, onGenericEvent); |
|
182 panel.off(panel.EVENTS.RECEIVED_RESPONSE_COOKIES, onGenericEvent); |
|
183 panel.off(panel.EVENTS.STARTED_RECEIVING_RESPONSE, onGenericEvent); |
|
184 panel.off(panel.EVENTS.UPDATING_RESPONSE_CONTENT, onGenericEvent); |
|
185 panel.off(panel.EVENTS.RECEIVED_RESPONSE_CONTENT, onGenericEvent); |
|
186 panel.off(panel.EVENTS.UPDATING_EVENT_TIMINGS, onGenericEvent); |
|
187 panel.off(panel.EVENTS.RECEIVED_EVENT_TIMINGS, onGenericEvent); |
|
188 |
|
189 executeSoon(deferred.resolve); |
|
190 } |
|
191 } |
|
192 |
|
193 panel.on(panel.EVENTS.UPDATING_REQUEST_HEADERS, onGenericEvent); |
|
194 panel.on(panel.EVENTS.RECEIVED_REQUEST_HEADERS, onGenericEvent); |
|
195 panel.on(panel.EVENTS.UPDATING_REQUEST_COOKIES, onGenericEvent); |
|
196 panel.on(panel.EVENTS.RECEIVED_REQUEST_COOKIES, onGenericEvent); |
|
197 panel.on(panel.EVENTS.UPDATING_REQUEST_POST_DATA, onPostEvent); |
|
198 panel.on(panel.EVENTS.RECEIVED_REQUEST_POST_DATA, onPostEvent); |
|
199 panel.on(panel.EVENTS.UPDATING_RESPONSE_HEADERS, onGenericEvent); |
|
200 panel.on(panel.EVENTS.RECEIVED_RESPONSE_HEADERS, onGenericEvent); |
|
201 panel.on(panel.EVENTS.UPDATING_RESPONSE_COOKIES, onGenericEvent); |
|
202 panel.on(panel.EVENTS.RECEIVED_RESPONSE_COOKIES, onGenericEvent); |
|
203 panel.on(panel.EVENTS.STARTED_RECEIVING_RESPONSE, onGenericEvent); |
|
204 panel.on(panel.EVENTS.UPDATING_RESPONSE_CONTENT, onGenericEvent); |
|
205 panel.on(panel.EVENTS.RECEIVED_RESPONSE_CONTENT, onGenericEvent); |
|
206 panel.on(panel.EVENTS.UPDATING_EVENT_TIMINGS, onGenericEvent); |
|
207 panel.on(panel.EVENTS.RECEIVED_EVENT_TIMINGS, onGenericEvent); |
|
208 |
|
209 return deferred.promise; |
|
210 } |
|
211 |
|
212 function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) { |
|
213 info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource()); |
|
214 // This bloats log sizes significantly in automation (bug 992485) |
|
215 //info("> Request: " + aRequestItem.attachment.toSource()); |
|
216 |
|
217 let requestsMenu = aRequestItem.ownerView; |
|
218 let widgetIndex = requestsMenu.indexOfItem(aRequestItem); |
|
219 let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem); |
|
220 |
|
221 info("Widget index of item: " + widgetIndex); |
|
222 info("Visible index of item: " + visibleIndex); |
|
223 |
|
224 let { fuzzyUrl, status, statusText, type, fullMimeType, size, time } = aData; |
|
225 let { attachment, target } = aRequestItem |
|
226 |
|
227 let uri = Services.io.newURI(aUrl, null, null).QueryInterface(Ci.nsIURL); |
|
228 let name = uri.fileName || "/"; |
|
229 let query = uri.query; |
|
230 let hostPort = uri.hostPort; |
|
231 |
|
232 if (fuzzyUrl) { |
|
233 ok(attachment.method.startsWith(aMethod), "The attached method is incorrect."); |
|
234 ok(attachment.url.startsWith(aUrl), "The attached url is incorrect."); |
|
235 } else { |
|
236 is(attachment.method, aMethod, "The attached method is incorrect."); |
|
237 is(attachment.url, aUrl, "The attached url is incorrect."); |
|
238 } |
|
239 |
|
240 is(target.querySelector(".requests-menu-method").getAttribute("value"), |
|
241 aMethod, "The displayed method is incorrect."); |
|
242 |
|
243 if (fuzzyUrl) { |
|
244 ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith( |
|
245 name + (query ? "?" + query : "")), "The displayed file is incorrect."); |
|
246 ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith( |
|
247 name + (query ? "?" + query : "")), "The tooltip file is incorrect."); |
|
248 } else { |
|
249 is(target.querySelector(".requests-menu-file").getAttribute("value"), |
|
250 name + (query ? "?" + query : ""), "The displayed file is incorrect."); |
|
251 is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"), |
|
252 name + (query ? "?" + query : ""), "The tooltip file is incorrect."); |
|
253 } |
|
254 |
|
255 is(target.querySelector(".requests-menu-domain").getAttribute("value"), |
|
256 hostPort, "The displayed domain is incorrect."); |
|
257 is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"), |
|
258 hostPort, "The tooltip domain is incorrect."); |
|
259 |
|
260 if (status !== undefined) { |
|
261 let value = target.querySelector(".requests-menu-status").getAttribute("code"); |
|
262 let codeValue = target.querySelector(".requests-menu-status-code").getAttribute("value"); |
|
263 let tooltip = target.querySelector(".requests-menu-status-and-method").getAttribute("tooltiptext"); |
|
264 info("Displayed status: " + value); |
|
265 info("Displayed code: " + codeValue); |
|
266 info("Tooltip status: " + tooltip); |
|
267 is(value, status, "The displayed status is incorrect."); |
|
268 is(codeValue, status, "The displayed status code is incorrect."); |
|
269 is(tooltip, status + " " + statusText, "The tooltip status is incorrect."); |
|
270 } |
|
271 if (type !== undefined) { |
|
272 let value = target.querySelector(".requests-menu-type").getAttribute("value"); |
|
273 let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext"); |
|
274 info("Displayed type: " + value); |
|
275 info("Tooltip type: " + tooltip); |
|
276 is(value, type, "The displayed type is incorrect."); |
|
277 is(tooltip, fullMimeType, "The tooltip type is incorrect."); |
|
278 } |
|
279 if (size !== undefined) { |
|
280 let value = target.querySelector(".requests-menu-size").getAttribute("value"); |
|
281 let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext"); |
|
282 info("Displayed size: " + value); |
|
283 info("Tooltip size: " + tooltip); |
|
284 is(value, size, "The displayed size is incorrect."); |
|
285 is(tooltip, size, "The tooltip size is incorrect."); |
|
286 } |
|
287 if (time !== undefined) { |
|
288 let value = target.querySelector(".requests-menu-timings-total").getAttribute("value"); |
|
289 let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext"); |
|
290 info("Displayed time: " + value); |
|
291 info("Tooltip time: " + tooltip); |
|
292 ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is incorrect."); |
|
293 ok(~~(tooltip.match(/[0-9]+/)) >= 0, "The tooltip time is incorrect."); |
|
294 } |
|
295 |
|
296 if (visibleIndex != -1) { |
|
297 if (visibleIndex % 2 == 0) { |
|
298 ok(aRequestItem.target.hasAttribute("even"), |
|
299 "Unexpected 'even' attribute for " + aRequestItem.value); |
|
300 ok(!aRequestItem.target.hasAttribute("odd"), |
|
301 "Unexpected 'odd' attribute for " + aRequestItem.value); |
|
302 } else { |
|
303 ok(!aRequestItem.target.hasAttribute("even"), |
|
304 "Unexpected 'even' attribute for " + aRequestItem.value); |
|
305 ok(aRequestItem.target.hasAttribute("odd"), |
|
306 "Unexpected 'odd' attribute for " + aRequestItem.value); |
|
307 } |
|
308 } |
|
309 } |
|
310 |
|
311 /** |
|
312 * Helper function for waiting for an event to fire before resolving a promise. |
|
313 * Example: waitFor(aMonitor.panelWin, aMonitor.panelWin.EVENTS.TAB_UPDATED); |
|
314 * |
|
315 * @param object subject |
|
316 * The event emitter object that is being listened to. |
|
317 * @param string eventName |
|
318 * The name of the event to listen to. |
|
319 * @return object |
|
320 * Returns a promise that resolves upon firing of the event. |
|
321 */ |
|
322 function waitFor (subject, eventName) { |
|
323 let deferred = promise.defer(); |
|
324 subject.once(eventName, deferred.resolve); |
|
325 return deferred.promise; |
|
326 } |
|
327 |
|
328 /** |
|
329 * Tests if a button for a filter of given type is the only one checked. |
|
330 * |
|
331 * @param string aFilterType |
|
332 * The type of the filter that should be the only one checked. |
|
333 */ |
|
334 function testFilterButtons(aMonitor, aFilterType) { |
|
335 let doc = aMonitor.panelWin.document; |
|
336 let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button"); |
|
337 let buttons = doc.querySelectorAll(".requests-menu-footer-button"); |
|
338 |
|
339 // Only target should be checked. |
|
340 let checkStatus = [(button == target) ? 1 : 0 for (button of buttons)] |
|
341 testFilterButtonsCustom(aMonitor, checkStatus); |
|
342 } |
|
343 |
|
344 /** |
|
345 * Tests if filter buttons have 'checked' attributes set correctly. |
|
346 * |
|
347 * @param array aIsChecked |
|
348 * An array specifying if a button at given index should have a |
|
349 * 'checked' attribute. For example, if the third item of the array |
|
350 * evaluates to true, the third button should be checked. |
|
351 */ |
|
352 function testFilterButtonsCustom(aMonitor, aIsChecked) { |
|
353 let doc = aMonitor.panelWin.document; |
|
354 let buttons = doc.querySelectorAll(".requests-menu-footer-button"); |
|
355 for (let i = 0; i < aIsChecked.length; i++) { |
|
356 let button = buttons[i]; |
|
357 if (aIsChecked[i]) { |
|
358 is(button.hasAttribute("checked"), true, |
|
359 "The " + button.id + " button should have a 'checked' attribute."); |
|
360 } else { |
|
361 is(button.hasAttribute("checked"), false, |
|
362 "The " + button.id + " button should not have a 'checked' attribute."); |
|
363 } |
|
364 } |
|
365 } |