Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
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");
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 }
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 }
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 }
40 function openToolbarCustomizationUI(aCallback, aBrowserWin) {
41 if (!aBrowserWin)
42 aBrowserWin = window;
44 aBrowserWin.gCustomizeMode.enter();
46 aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
47 aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
48 executeSoon(function() {
49 aCallback(aBrowserWin)
50 });
51 });
52 }
54 function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
55 aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
56 aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
57 executeSoon(aCallback);
58 });
60 aBrowserWin.gCustomizeMode.exit();
61 }
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 }
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();
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 }
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 }
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 }
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 }
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 }
142 function resetBlocklist() {
143 Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
144 }
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 }
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();
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 }
191 /**
192 * Asynchronously check a url is visited.
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 });
206 return deferred.promise;
207 }
209 function whenNewTabLoaded(aWindow, aCallback) {
210 aWindow.BrowserOpenTab();
212 let browser = aWindow.gBrowser.selectedBrowser;
213 if (browser.contentDocument.readyState === "complete") {
214 aCallback();
215 return;
216 }
218 whenTabLoaded(aWindow.gBrowser.selectedTab, aCallback);
219 }
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 }
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 }
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 }
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 }
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 });
292 return deferred.promise;
293 }
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 }
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 }
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 }
360 let FullZoomHelper = {
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 },
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 },
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 },
389 load: function load(tab, url) {
390 let deferred = Promise.defer();
391 let didLoad = false;
392 let didZoom = false;
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);
401 this.waitForLocationChange().then(function () {
402 didZoom = true;
403 if (didLoad)
404 deferred.resolve();
405 });
407 tab.linkedBrowser.loadURI(url);
409 return deferred.promise;
410 },
412 zoomTest: function zoomTest(tab, val, msg) {
413 is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
414 },
416 enlarge: function enlarge() {
417 let deferred = Promise.defer();
418 FullZoom.enlarge(function () deferred.resolve());
419 return deferred.promise;
420 },
422 reduce: function reduce() {
423 let deferred = Promise.defer();
424 FullZoom.reduce(function () deferred.resolve());
425 return deferred.promise;
426 },
428 reset: function reset() {
429 let deferred = Promise.defer();
430 FullZoom.reset(function () deferred.resolve());
431 return deferred.promise;
432 },
434 BACK: 0,
435 FORWARD: 1,
436 navigate: function navigate(direction) {
437 let deferred = Promise.defer();
438 let didPs = false;
439 let didZoom = false;
441 gBrowser.addEventListener("pageshow", function (event) {
442 gBrowser.removeEventListener("pageshow", arguments.callee, true);
443 didPs = true;
444 if (didZoom)
445 deferred.resolve();
446 }, true);
448 if (direction == this.BACK)
449 gBrowser.goBack();
450 else if (direction == this.FORWARD)
451 gBrowser.goForward();
453 this.waitForLocationChange().then(function () {
454 didZoom = true;
455 if (didPs)
456 deferred.resolve();
457 });
458 return deferred.promise;
459 },
461 failAndContinue: function failAndContinue(func) {
462 return function (err) {
463 ok(false, err);
464 func();
465 };
466 },
467 };