|
1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
2 |
|
3 XPCOMUtils.defineLazyModuleGetter(this, "Promise", |
|
4 "resource://gre/modules/Promise.jsm"); |
|
5 XPCOMUtils.defineLazyModuleGetter(this, "Task", |
|
6 "resource://gre/modules/Task.jsm"); |
|
7 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", |
|
8 "resource://gre/modules/PlacesUtils.jsm"); |
|
9 |
|
10 function whenDelayedStartupFinished(aWindow, aCallback) { |
|
11 Services.obs.addObserver(function observer(aSubject, aTopic) { |
|
12 if (aWindow == aSubject) { |
|
13 Services.obs.removeObserver(observer, aTopic); |
|
14 executeSoon(aCallback); |
|
15 } |
|
16 }, "browser-delayed-startup-finished", false); |
|
17 } |
|
18 |
|
19 function findChromeWindowByURI(aURI) { |
|
20 let windows = Services.wm.getEnumerator(null); |
|
21 while (windows.hasMoreElements()) { |
|
22 let win = windows.getNext(); |
|
23 if (win.location.href == aURI) |
|
24 return win; |
|
25 } |
|
26 return null; |
|
27 } |
|
28 |
|
29 function updateTabContextMenu(tab) { |
|
30 let menu = document.getElementById("tabContextMenu"); |
|
31 if (!tab) |
|
32 tab = gBrowser.selectedTab; |
|
33 var evt = new Event(""); |
|
34 tab.dispatchEvent(evt); |
|
35 menu.openPopup(tab, "end_after", 0, 0, true, false, evt); |
|
36 is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab"); |
|
37 menu.hidePopup(); |
|
38 } |
|
39 |
|
40 function openToolbarCustomizationUI(aCallback, aBrowserWin) { |
|
41 if (!aBrowserWin) |
|
42 aBrowserWin = window; |
|
43 |
|
44 aBrowserWin.gCustomizeMode.enter(); |
|
45 |
|
46 aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() { |
|
47 aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded); |
|
48 executeSoon(function() { |
|
49 aCallback(aBrowserWin) |
|
50 }); |
|
51 }); |
|
52 } |
|
53 |
|
54 function closeToolbarCustomizationUI(aCallback, aBrowserWin) { |
|
55 aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() { |
|
56 aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded); |
|
57 executeSoon(aCallback); |
|
58 }); |
|
59 |
|
60 aBrowserWin.gCustomizeMode.exit(); |
|
61 } |
|
62 |
|
63 function waitForCondition(condition, nextTest, errorMsg) { |
|
64 var tries = 0; |
|
65 var interval = setInterval(function() { |
|
66 if (tries >= 30) { |
|
67 ok(false, errorMsg); |
|
68 moveOn(); |
|
69 } |
|
70 var conditionPassed; |
|
71 try { |
|
72 conditionPassed = condition(); |
|
73 } catch (e) { |
|
74 ok(false, e + "\n" + e.stack); |
|
75 conditionPassed = false; |
|
76 } |
|
77 if (conditionPassed) { |
|
78 moveOn(); |
|
79 } |
|
80 tries++; |
|
81 }, 100); |
|
82 var moveOn = function() { clearInterval(interval); nextTest(); }; |
|
83 } |
|
84 |
|
85 function getTestPlugin(aName) { |
|
86 var pluginName = aName || "Test Plug-in"; |
|
87 var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); |
|
88 var tags = ph.getPluginTags(); |
|
89 |
|
90 // Find the test plugin |
|
91 for (var i = 0; i < tags.length; i++) { |
|
92 if (tags[i].name == pluginName) |
|
93 return tags[i]; |
|
94 } |
|
95 ok(false, "Unable to find plugin"); |
|
96 return null; |
|
97 } |
|
98 |
|
99 // call this to set the test plugin(s) initially expected enabled state. |
|
100 // it will automatically be reset to it's previous value after the test |
|
101 // ends |
|
102 function setTestPluginEnabledState(newEnabledState, pluginName) { |
|
103 var plugin = getTestPlugin(pluginName); |
|
104 var oldEnabledState = plugin.enabledState; |
|
105 plugin.enabledState = newEnabledState; |
|
106 SimpleTest.registerCleanupFunction(function() { |
|
107 getTestPlugin(pluginName).enabledState = oldEnabledState; |
|
108 }); |
|
109 } |
|
110 |
|
111 // after a test is done using the plugin doorhanger, we should just clear |
|
112 // any permissions that may have crept in |
|
113 function clearAllPluginPermissions() { |
|
114 let perms = Services.perms.enumerator; |
|
115 while (perms.hasMoreElements()) { |
|
116 let perm = perms.getNext(); |
|
117 if (perm.type.startsWith('plugin')) { |
|
118 Services.perms.remove(perm.host, perm.type); |
|
119 } |
|
120 } |
|
121 } |
|
122 |
|
123 function updateBlocklist(aCallback) { |
|
124 var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] |
|
125 .getService(Ci.nsITimerCallback); |
|
126 var observer = function() { |
|
127 Services.obs.removeObserver(observer, "blocklist-updated"); |
|
128 SimpleTest.executeSoon(aCallback); |
|
129 }; |
|
130 Services.obs.addObserver(observer, "blocklist-updated", false); |
|
131 blocklistNotifier.notify(null); |
|
132 } |
|
133 |
|
134 var _originalTestBlocklistURL = null; |
|
135 function setAndUpdateBlocklist(aURL, aCallback) { |
|
136 if (!_originalTestBlocklistURL) |
|
137 _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); |
|
138 Services.prefs.setCharPref("extensions.blocklist.url", aURL); |
|
139 updateBlocklist(aCallback); |
|
140 } |
|
141 |
|
142 function resetBlocklist() { |
|
143 Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); |
|
144 } |
|
145 |
|
146 function whenNewWindowLoaded(aOptions, aCallback) { |
|
147 let win = OpenBrowserWindow(aOptions); |
|
148 win.addEventListener("load", function onLoad() { |
|
149 win.removeEventListener("load", onLoad, false); |
|
150 aCallback(win); |
|
151 }, false); |
|
152 } |
|
153 |
|
154 /** |
|
155 * Waits for all pending async statements on the default connection, before |
|
156 * proceeding with aCallback. |
|
157 * |
|
158 * @param aCallback |
|
159 * Function to be called when done. |
|
160 * @param aScope |
|
161 * Scope for the callback. |
|
162 * @param aArguments |
|
163 * Arguments array for the callback. |
|
164 * |
|
165 * @note The result is achieved by asynchronously executing a query requiring |
|
166 * a write lock. Since all statements on the same connection are |
|
167 * serialized, the end of this write operation means that all writes are |
|
168 * complete. Note that WAL makes so that writers don't block readers, but |
|
169 * this is a problem only across different connections. |
|
170 */ |
|
171 function waitForAsyncUpdates(aCallback, aScope, aArguments) { |
|
172 let scope = aScope || this; |
|
173 let args = aArguments || []; |
|
174 let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase) |
|
175 .DBConnection; |
|
176 let begin = db.createAsyncStatement("BEGIN EXCLUSIVE"); |
|
177 begin.executeAsync(); |
|
178 begin.finalize(); |
|
179 |
|
180 let commit = db.createAsyncStatement("COMMIT"); |
|
181 commit.executeAsync({ |
|
182 handleResult: function() {}, |
|
183 handleError: function() {}, |
|
184 handleCompletion: function(aReason) { |
|
185 aCallback.apply(scope, args); |
|
186 } |
|
187 }); |
|
188 commit.finalize(); |
|
189 } |
|
190 |
|
191 /** |
|
192 * Asynchronously check a url is visited. |
|
193 |
|
194 * @param aURI The URI. |
|
195 * @param aExpectedValue The expected value. |
|
196 * @return {Promise} |
|
197 * @resolves When the check has been added successfully. |
|
198 * @rejects JavaScript exception. |
|
199 */ |
|
200 function promiseIsURIVisited(aURI, aExpectedValue) { |
|
201 let deferred = Promise.defer(); |
|
202 PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) { |
|
203 deferred.resolve(aIsVisited); |
|
204 }); |
|
205 |
|
206 return deferred.promise; |
|
207 } |
|
208 |
|
209 function whenNewTabLoaded(aWindow, aCallback) { |
|
210 aWindow.BrowserOpenTab(); |
|
211 |
|
212 let browser = aWindow.gBrowser.selectedBrowser; |
|
213 if (browser.contentDocument.readyState === "complete") { |
|
214 aCallback(); |
|
215 return; |
|
216 } |
|
217 |
|
218 whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback); |
|
219 } |
|
220 |
|
221 function whenTabLoaded(aTab, aCallback) { |
|
222 let browser = aTab.linkedBrowser; |
|
223 browser.addEventListener("load", function onLoad() { |
|
224 browser.removeEventListener("load", onLoad, true); |
|
225 executeSoon(aCallback); |
|
226 }, true); |
|
227 } |
|
228 |
|
229 function addVisits(aPlaceInfo, aCallback) { |
|
230 let places = []; |
|
231 if (aPlaceInfo instanceof Ci.nsIURI) { |
|
232 places.push({ uri: aPlaceInfo }); |
|
233 } else if (Array.isArray(aPlaceInfo)) { |
|
234 places = places.concat(aPlaceInfo); |
|
235 } else { |
|
236 places.push(aPlaceInfo); |
|
237 } |
|
238 |
|
239 // Create mozIVisitInfo for each entry. |
|
240 let now = Date.now(); |
|
241 for (let i = 0; i < places.length; i++) { |
|
242 if (!places[i].title) { |
|
243 places[i].title = "test visit for " + places[i].uri.spec; |
|
244 } |
|
245 places[i].visits = [{ |
|
246 transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK |
|
247 : places[i].transition, |
|
248 visitDate: places[i].visitDate || (now++) * 1000, |
|
249 referrerURI: places[i].referrer |
|
250 }]; |
|
251 } |
|
252 |
|
253 PlacesUtils.asyncHistory.updatePlaces( |
|
254 places, |
|
255 { |
|
256 handleError: function AAV_handleError() { |
|
257 throw("Unexpected error in adding visit."); |
|
258 }, |
|
259 handleResult: function () {}, |
|
260 handleCompletion: function UP_handleCompletion() { |
|
261 if (aCallback) |
|
262 aCallback(); |
|
263 } |
|
264 } |
|
265 ); |
|
266 } |
|
267 |
|
268 /** |
|
269 * Ensures that the specified URIs are either cleared or not. |
|
270 * |
|
271 * @param aURIs |
|
272 * Array of page URIs |
|
273 * @param aShouldBeCleared |
|
274 * True if each visit to the URI should be cleared, false otherwise |
|
275 */ |
|
276 function promiseHistoryClearedState(aURIs, aShouldBeCleared) { |
|
277 let deferred = Promise.defer(); |
|
278 let callbackCount = 0; |
|
279 let niceStr = aShouldBeCleared ? "no longer" : "still"; |
|
280 function callbackDone() { |
|
281 if (++callbackCount == aURIs.length) |
|
282 deferred.resolve(); |
|
283 } |
|
284 aURIs.forEach(function (aURI) { |
|
285 PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) { |
|
286 is(aIsVisited, !aShouldBeCleared, |
|
287 "history visit " + aURI.spec + " should " + niceStr + " exist"); |
|
288 callbackDone(); |
|
289 }); |
|
290 }); |
|
291 |
|
292 return deferred.promise; |
|
293 } |
|
294 |
|
295 /** |
|
296 * Allows waiting for an observer notification once. |
|
297 * |
|
298 * @param topic |
|
299 * Notification topic to observe. |
|
300 * |
|
301 * @return {Promise} |
|
302 * @resolves The array [subject, data] from the observed notification. |
|
303 * @rejects Never. |
|
304 */ |
|
305 function promiseTopicObserved(topic) |
|
306 { |
|
307 let deferred = Promise.defer(); |
|
308 Services.obs.addObserver(function PTO_observe(subject, topic, data) { |
|
309 Services.obs.removeObserver(PTO_observe, topic); |
|
310 deferred.resolve([subject, data]); |
|
311 }, topic, false); |
|
312 return deferred.promise; |
|
313 } |
|
314 |
|
315 /** |
|
316 * Clears history asynchronously. |
|
317 * |
|
318 * @return {Promise} |
|
319 * @resolves When history has been cleared. |
|
320 * @rejects Never. |
|
321 */ |
|
322 function promiseClearHistory() { |
|
323 let promise = promiseTopicObserved(PlacesUtils.TOPIC_EXPIRATION_FINISHED); |
|
324 PlacesUtils.bhistory.removeAllPages(); |
|
325 return promise; |
|
326 } |
|
327 |
|
328 /** |
|
329 * Waits for the next top-level document load in the current browser. The URI |
|
330 * of the document is compared against aExpectedURL. The load is then stopped |
|
331 * before it actually starts. |
|
332 * |
|
333 * @param aExpectedURL |
|
334 * The URL of the document that is expected to load. |
|
335 * @return promise |
|
336 */ |
|
337 function waitForDocLoadAndStopIt(aExpectedURL) { |
|
338 let deferred = Promise.defer(); |
|
339 let progressListener = { |
|
340 onStateChange: function (webProgress, req, flags, status) { |
|
341 info("waitForDocLoadAndStopIt: onStateChange: " + req.name); |
|
342 let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | |
|
343 Ci.nsIWebProgressListener.STATE_START; |
|
344 if ((flags & docStart) && webProgress.isTopLevel) { |
|
345 info("waitForDocLoadAndStopIt: Document start: " + |
|
346 req.QueryInterface(Ci.nsIChannel).URI.spec); |
|
347 is(req.originalURI.spec, aExpectedURL, |
|
348 "waitForDocLoadAndStopIt: The expected URL was loaded"); |
|
349 req.cancel(Components.results.NS_ERROR_FAILURE); |
|
350 gBrowser.removeProgressListener(progressListener); |
|
351 deferred.resolve(); |
|
352 } |
|
353 }, |
|
354 }; |
|
355 gBrowser.addProgressListener(progressListener); |
|
356 info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL); |
|
357 return deferred.promise; |
|
358 } |
|
359 |
|
360 let FullZoomHelper = { |
|
361 |
|
362 selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) { |
|
363 if (!tab) |
|
364 throw new Error("tab must be given."); |
|
365 if (gBrowser.selectedTab == tab) |
|
366 return Promise.resolve(); |
|
367 gBrowser.selectedTab = tab; |
|
368 return this.waitForLocationChange(); |
|
369 }, |
|
370 |
|
371 removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) { |
|
372 tab = tab || gBrowser.selectedTab; |
|
373 let selected = gBrowser.selectedTab == tab; |
|
374 gBrowser.removeTab(tab); |
|
375 if (selected) |
|
376 return this.waitForLocationChange(); |
|
377 return Promise.resolve(); |
|
378 }, |
|
379 |
|
380 waitForLocationChange: function waitForLocationChange() { |
|
381 let deferred = Promise.defer(); |
|
382 Services.obs.addObserver(function obs(subj, topic, data) { |
|
383 Services.obs.removeObserver(obs, topic); |
|
384 deferred.resolve(); |
|
385 }, "browser-fullZoom:location-change", false); |
|
386 return deferred.promise; |
|
387 }, |
|
388 |
|
389 load: function load(tab, url) { |
|
390 let deferred = Promise.defer(); |
|
391 let didLoad = false; |
|
392 let didZoom = false; |
|
393 |
|
394 tab.linkedBrowser.addEventListener("load", function (event) { |
|
395 event.currentTarget.removeEventListener("load", arguments.callee, true); |
|
396 didLoad = true; |
|
397 if (didZoom) |
|
398 deferred.resolve(); |
|
399 }, true); |
|
400 |
|
401 this.waitForLocationChange().then(function () { |
|
402 didZoom = true; |
|
403 if (didLoad) |
|
404 deferred.resolve(); |
|
405 }); |
|
406 |
|
407 tab.linkedBrowser.loadURI(url); |
|
408 |
|
409 return deferred.promise; |
|
410 }, |
|
411 |
|
412 zoomTest: function zoomTest(tab, val, msg) { |
|
413 is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg); |
|
414 }, |
|
415 |
|
416 enlarge: function enlarge() { |
|
417 let deferred = Promise.defer(); |
|
418 FullZoom.enlarge(function () deferred.resolve()); |
|
419 return deferred.promise; |
|
420 }, |
|
421 |
|
422 reduce: function reduce() { |
|
423 let deferred = Promise.defer(); |
|
424 FullZoom.reduce(function () deferred.resolve()); |
|
425 return deferred.promise; |
|
426 }, |
|
427 |
|
428 reset: function reset() { |
|
429 let deferred = Promise.defer(); |
|
430 FullZoom.reset(function () deferred.resolve()); |
|
431 return deferred.promise; |
|
432 }, |
|
433 |
|
434 BACK: 0, |
|
435 FORWARD: 1, |
|
436 navigate: function navigate(direction) { |
|
437 let deferred = Promise.defer(); |
|
438 let didPs = false; |
|
439 let didZoom = false; |
|
440 |
|
441 gBrowser.addEventListener("pageshow", function (event) { |
|
442 gBrowser.removeEventListener("pageshow", arguments.callee, true); |
|
443 didPs = true; |
|
444 if (didZoom) |
|
445 deferred.resolve(); |
|
446 }, true); |
|
447 |
|
448 if (direction == this.BACK) |
|
449 gBrowser.goBack(); |
|
450 else if (direction == this.FORWARD) |
|
451 gBrowser.goForward(); |
|
452 |
|
453 this.waitForLocationChange().then(function () { |
|
454 didZoom = true; |
|
455 if (didPs) |
|
456 deferred.resolve(); |
|
457 }); |
|
458 return deferred.promise; |
|
459 }, |
|
460 |
|
461 failAndContinue: function failAndContinue(func) { |
|
462 return function (err) { |
|
463 ok(false, err); |
|
464 func(); |
|
465 }; |
|
466 }, |
|
467 }; |