Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
7 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
8 "resource://gre/modules/Promise.jsm");
9 XPCOMUtils.defineLazyModuleGetter(this, "Task",
10 "resource://gre/modules/Task.jsm");
11 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
12 "resource://gre/modules/PlacesUtils.jsm");
14 function waitForCondition(condition, nextTest, errorMsg) {
15 var tries = 0;
16 var interval = setInterval(function() {
17 if (tries >= 30) {
18 ok(false, errorMsg);
19 moveOn();
20 }
21 var conditionPassed;
22 try {
23 conditionPassed = condition();
24 } catch (e) {
25 ok(false, e + "\n" + e.stack);
26 conditionPassed = false;
27 }
28 if (conditionPassed) {
29 moveOn();
30 }
31 tries++;
32 }, 100);
33 var moveOn = function() { clearInterval(interval); nextTest(); };
34 }
36 // Check that a specified (string) URL hasn't been "remembered" (ie, is not
37 // in history, will not appear in about:newtab or auto-complete, etc.)
38 function promiseSocialUrlNotRemembered(url) {
39 let deferred = Promise.defer();
40 let uri = Services.io.newURI(url, null, null);
41 PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) {
42 ok(!aIsVisited, "social URL " + url + " should not be in global history");
43 deferred.resolve();
44 });
45 return deferred.promise;
46 }
48 let gURLsNotRemembered = [];
51 function checkProviderPrefsEmpty(isError) {
52 let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
53 let prefs = MANIFEST_PREFS.getChildList("", []);
54 let c = 0;
55 for (let pref of prefs) {
56 if (MANIFEST_PREFS.prefHasUserValue(pref)) {
57 info("provider [" + pref + "] manifest left installed from previous test");
58 c++;
59 }
60 }
61 is(c, 0, "all provider prefs uninstalled from previous test");
62 is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length);
63 }
65 function defaultFinishChecks() {
66 checkProviderPrefsEmpty(true);
67 finish();
68 }
70 function runSocialTestWithProvider(manifest, callback, finishcallback) {
71 let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
73 let manifests = Array.isArray(manifest) ? manifest : [manifest];
75 // Check that none of the provider's content ends up in history.
76 function finishCleanUp() {
77 ok(!SocialSidebar.provider, "no provider in sidebar");
78 SessionStore.setWindowValue(window, "socialSidebar", "");
79 for (let i = 0; i < manifests.length; i++) {
80 let m = manifests[i];
81 for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
82 if (m[what]) {
83 yield promiseSocialUrlNotRemembered(m[what]);
84 }
85 };
86 }
87 for (let i = 0; i < gURLsNotRemembered.length; i++) {
88 yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
89 }
90 gURLsNotRemembered = [];
91 }
93 info("runSocialTestWithProvider: " + manifests.toSource());
95 let finishCount = 0;
96 function finishIfDone(callFinish) {
97 finishCount++;
98 if (finishCount == manifests.length)
99 Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks);
100 }
101 function removeAddedProviders(cleanup) {
102 manifests.forEach(function (m) {
103 // If we're "cleaning up", don't call finish when done.
104 let callback = cleanup ? function () {} : finishIfDone;
105 // Similarly, if we're cleaning up, catch exceptions from removeProvider
106 let removeProvider = SocialService.removeProvider.bind(SocialService);
107 if (cleanup) {
108 removeProvider = function (origin, cb) {
109 try {
110 SocialService.removeProvider(origin, cb);
111 } catch (ex) {
112 // Ignore "provider doesn't exist" errors.
113 if (ex.message.indexOf("SocialService.removeProvider: no provider with origin") == 0)
114 return;
115 info("Failed to clean up provider " + origin + ": " + ex);
116 }
117 }
118 }
119 removeProvider(m.origin, callback);
120 });
121 }
122 function finishSocialTest(cleanup) {
123 removeAddedProviders(cleanup);
124 }
126 let providersAdded = 0;
127 let firstProvider;
129 manifests.forEach(function (m) {
130 SocialService.addProvider(m, function(provider) {
132 providersAdded++;
133 info("runSocialTestWithProvider: provider added");
135 // we want to set the first specified provider as the UI's provider
136 if (provider.origin == manifests[0].origin) {
137 firstProvider = provider;
138 }
140 // If we've added all the providers we need, call the callback to start
141 // the tests (and give it a callback it can call to finish them)
142 if (providersAdded == manifests.length) {
143 registerCleanupFunction(function () {
144 finishSocialTest(true);
145 });
146 waitForCondition(function() provider.enabled,
147 function() {
148 info("provider has been enabled");
149 callback(finishSocialTest);
150 }, "providers added and enabled");
151 }
152 });
153 });
154 }
156 function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
157 let testIter = Iterator(tests);
158 let providersAtStart = Social.providers.length;
159 info("runSocialTests: start test run with " + providersAtStart + " providers");
161 if (cbPreTest === undefined) {
162 cbPreTest = function(cb) {cb()};
163 }
164 if (cbPostTest === undefined) {
165 cbPostTest = function(cb) {cb()};
166 }
168 function runNextTest() {
169 let name, func;
170 try {
171 [name, func] = testIter.next();
172 } catch (err if err instanceof StopIteration) {
173 // out of items:
174 (cbFinish || defaultFinishChecks)();
175 is(providersAtStart, Social.providers.length,
176 "runSocialTests: finish test run with " + Social.providers.length + " providers");
177 return;
178 }
179 // We run on a timeout as the frameworker also makes use of timeouts, so
180 // this helps keep the debug messages sane.
181 executeSoon(function() {
182 function cleanupAndRunNextTest() {
183 info("sub-test " + name + " complete");
184 cbPostTest(runNextTest);
185 }
186 cbPreTest(function() {
187 info("pre-test: starting with " + Social.providers.length + " providers");
188 info("sub-test " + name + " starting");
189 try {
190 func.call(tests, cleanupAndRunNextTest);
191 } catch (ex) {
192 ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack);
193 cleanupAndRunNextTest();
194 }
195 })
196 });
197 }
198 runNextTest();
199 }
201 // A fairly large hammer which checks all aspects of the SocialUI for
202 // internal consistency.
203 function checkSocialUI(win) {
204 let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
205 win = win || window;
206 let doc = win.document;
207 let enabled = win.SocialUI.enabled;
208 let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
209 !PrivateBrowsingUtils.isWindowPrivate(win);
210 let sidebarEnabled = win.SocialSidebar.provider ? enabled : false;
212 // if we have enabled providers, we should also have instances of those
213 // providers
214 if (SocialService.hasEnabledProviders) {
215 ok(Social.providers.length > 0, "providers are enabled");
216 } else {
217 is(Social.providers.length, 0, "providers are not enabled");
218 }
220 // some local helpers to avoid log-spew for the many checks made here.
221 let numGoodTests = 0, numTests = 0;
222 function _ok(what, msg) {
223 numTests++;
224 if (!ok)
225 ok(what, msg)
226 else
227 ++numGoodTests;
228 }
229 function _is(a, b, msg) {
230 numTests++;
231 if (a != b)
232 is(a, b, msg)
233 else
234 ++numGoodTests;
235 }
236 function isbool(a, b, msg) {
237 _is(!!a, !!b, msg);
238 }
239 isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?");
240 isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?");
241 isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?");
243 let contextMenus = [
244 {
245 type: "link",
246 id: "context-marklinkMenu",
247 label: "social.marklinkMenu.label"
248 },
249 {
250 type: "page",
251 id: "context-markpageMenu",
252 label: "social.markpageMenu.label"
253 }
254 ];
256 for (let c of contextMenus) {
257 let leMenu = document.getElementById(c.id);
258 let parent, menus;
259 let markProviders = SocialMarks.getProviders();
260 if (markProviders.length > SocialMarks.MENU_LIMIT) {
261 // menus should be in a submenu, not in the top level of the context menu
262 parent = leMenu.firstChild;
263 menus = document.getElementsByClassName("context-mark" + c.type);
264 _is(menus.length, 0, "menu's are not in main context menu\n");
265 menus = parent.childNodes;
266 _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
267 } else {
268 // menus should be in the top level of the context menu, not in a submenu
269 parent = leMenu.parentNode;
270 menus = document.getElementsByClassName("context-mark" + c.type);
271 _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
272 menus = leMenu.firstChild.childNodes;
273 _is(menus.length, 0, "menu's are not in context submenu\n");
274 }
275 for (let m of menus)
276 _is(m.parentNode, parent, "menu has correct parent");
277 }
279 // and for good measure, check all the social commands.
280 isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?");
281 isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
282 isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?");
283 isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
285 // and report on overall success of failure of the various checks here.
286 is(numGoodTests, numTests, "The Social UI tests succeeded.")
287 }
289 function waitForNotification(topic, cb) {
290 function observer(subject, topic, data) {
291 Services.obs.removeObserver(observer, topic);
292 cb();
293 }
294 Services.obs.addObserver(observer, topic, false);
295 }
297 // blocklist testing
298 function updateBlocklist(aCallback) {
299 var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
300 .getService(Ci.nsITimerCallback);
301 var observer = function() {
302 Services.obs.removeObserver(observer, "blocklist-updated");
303 if (aCallback)
304 executeSoon(aCallback);
305 };
306 Services.obs.addObserver(observer, "blocklist-updated", false);
307 blocklistNotifier.notify(null);
308 }
310 var _originalTestBlocklistURL = null;
311 function setAndUpdateBlocklist(aURL, aCallback) {
312 if (!_originalTestBlocklistURL)
313 _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
314 Services.prefs.setCharPref("extensions.blocklist.url", aURL);
315 updateBlocklist(aCallback);
316 }
318 function resetBlocklist(aCallback) {
319 // XXX - this has "forked" from the head.js helpers in our parent directory :(
320 // But let's reuse their blockNoPlugins.xml. Later, we should arrange to
321 // use their head.js helpers directly
322 let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml";
323 setAndUpdateBlocklist(noBlockedURL, function() {
324 Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
325 if (aCallback)
326 aCallback();
327 });
328 }
330 function setManifestPref(name, manifest) {
331 let string = Cc["@mozilla.org/supports-string;1"].
332 createInstance(Ci.nsISupportsString);
333 string.data = JSON.stringify(manifest);
334 Services.prefs.setComplexValue(name, Ci.nsISupportsString, string);
335 }
337 function getManifestPrefname(aManifest) {
338 // is same as the generated name in SocialServiceInternal.getManifestPrefname
339 let originUri = Services.io.newURI(aManifest.origin, null, null);
340 return "social.manifest." + originUri.hostPort.replace('.','-');
341 }
343 function setBuiltinManifestPref(name, manifest) {
344 // we set this as a default pref, it must not be a user pref
345 manifest.builtin = true;
346 let string = Cc["@mozilla.org/supports-string;1"].
347 createInstance(Ci.nsISupportsString);
348 string.data = JSON.stringify(manifest);
349 Services.prefs.getDefaultBranch(null).setComplexValue(name, Ci.nsISupportsString, string);
350 // verify this is set on the default branch
351 let stored = Services.prefs.getComplexValue(name, Ci.nsISupportsString).data;
352 is(stored, string.data, "manifest '"+name+"' stored in default prefs");
353 // don't dirty our manifest, we'll need it without this flag later
354 delete manifest.builtin;
355 // verify we DO NOT have a user-level pref
356 ok(!Services.prefs.prefHasUserValue(name), "manifest '"+name+"' is not in user-prefs");
357 }
359 function resetBuiltinManifestPref(name) {
360 Services.prefs.getDefaultBranch(null).deleteBranch(name);
361 is(Services.prefs.getDefaultBranch(null).getPrefType(name),
362 Services.prefs.PREF_INVALID, "default manifest removed");
363 }
365 function addTab(url, callback) {
366 let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true});
367 tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
368 tab.linkedBrowser.removeEventListener("load", tabLoad, true);
369 executeSoon(function() {callback(tab)});
370 }, true);
371 }
373 function selectBrowserTab(tab, callback) {
374 if (gBrowser.selectedTab == tab) {
375 executeSoon(function() {callback(tab)});
376 return;
377 }
378 gBrowser.tabContainer.addEventListener("TabSelect", function onTabSelect() {
379 gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false);
380 is(gBrowser.selectedTab, tab, "browser tab is selected");
381 executeSoon(function() {callback(tab)});
382 });
383 gBrowser.selectedTab = tab;
384 }
386 function loadIntoTab(tab, url, callback) {
387 tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
388 tab.linkedBrowser.removeEventListener("load", tabLoad, true);
389 executeSoon(function() {callback(tab)});
390 }, true);
391 tab.linkedBrowser.loadURI(url);
392 }
395 // chat test help functions
397 // And lots of helpers for the resize tests.
398 function get3ChatsForCollapsing(mode, cb) {
399 // We make one chat, then measure its size. We then resize the browser to
400 // ensure a second can be created fully visible but a third can not - then
401 // create the other 2. first will will be collapsed, second fully visible
402 // and the third also visible and the "selected" one.
403 // To make our life easier we don't go via the worker and ports so we get
404 // more control over creation *and* to make the code much simpler. We
405 // assume the worker/port stuff is individually tested above.
406 let chatbar = window.SocialChatBar.chatbar;
407 let chatWidth = undefined;
408 let num = 0;
409 is(chatbar.childNodes.length, 0, "chatbar starting empty");
410 is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
412 makeChat(mode, "first chat", function() {
413 // got the first one.
414 checkPopup();
415 ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
416 // we kinda cheat here and get the width of the first chat, assuming
417 // that all future chats will have the same width when open.
418 chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
419 let desired = chatWidth * 2.5;
420 resizeWindowToChatAreaWidth(desired, function(sizedOk) {
421 ok(sizedOk, "can't do any tests without this width");
422 checkPopup();
423 makeChat(mode, "second chat", function() {
424 is(chatbar.childNodes.length, 2, "now have 2 chats");
425 checkPopup();
426 // and create the third.
427 makeChat(mode, "third chat", function() {
428 is(chatbar.childNodes.length, 3, "now have 3 chats");
429 checkPopup();
430 // XXX - this is a hacky implementation detail around the order of
431 // the chats. Ideally things would be a little more sane wrt the
432 // other in which the children were created.
433 let second = chatbar.childNodes[2];
434 let first = chatbar.childNodes[1];
435 let third = chatbar.childNodes[0];
436 ok(first.collapsed && !second.collapsed && !third.collapsed, "collapsed state as promised");
437 is(chatbar.selectedChat, third, "third is selected as promised")
438 info("have 3 chats for collapse testing - starting actual test...");
439 cb(first, second, third);
440 }, mode);
441 }, mode);
442 });
443 }, mode);
444 }
446 function makeChat(mode, uniqueid, cb) {
447 info("making a chat window '" + uniqueid +"'");
448 let provider = SocialSidebar.provider;
449 const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
450 let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
451 info("chat window has opened");
452 // we can't callback immediately or we might close the chat during
453 // this event which upsets the implementation - it is only 1/2 way through
454 // handling the load event.
455 chat.document.title = uniqueid;
456 executeSoon(cb);
457 }, mode);
458 if (!isOpened) {
459 ok(false, "unable to open chat window, no provider? more failures to come");
460 executeSoon(cb);
461 }
462 }
464 function checkPopup() {
465 // popup only showing if any collapsed popup children.
466 let chatbar = window.SocialChatBar.chatbar;
467 let numCollapsed = 0;
468 for (let chat of chatbar.childNodes) {
469 if (chat.collapsed) {
470 numCollapsed += 1;
471 // and it have a menuitem weakmap
472 is(chatbar.menuitemMap.get(chat).nodeName, "menuitem", "collapsed chat has a menu item");
473 } else {
474 ok(!chatbar.menuitemMap.has(chat), "open chat has no menu item");
475 }
476 }
477 is(chatbar.menupopup.parentNode.collapsed, numCollapsed == 0, "popup matches child collapsed state");
478 is(chatbar.menupopup.childNodes.length, numCollapsed, "popup has correct count of children");
479 // todo - check each individual elt is what we expect?
480 }
481 // Resize the main window so the chat area's boxObject is |desired| wide.
482 // Does a callback passing |true| if the window is now big enough or false
483 // if we couldn't resize large enough to satisfy the test requirement.
484 function resizeWindowToChatAreaWidth(desired, cb, count = 0) {
485 let current = window.SocialChatBar.chatbar.getBoundingClientRect().width;
486 let delta = desired - current;
487 info(count + ": resizing window so chat area is " + desired + " wide, currently it is "
488 + current + ". Screen avail is " + window.screen.availWidth
489 + ", current outer width is " + window.outerWidth);
491 // WTF? Sometimes we will get fractional values due to the - err - magic
492 // of DevPointsPerCSSPixel etc, so we allow a couple of pixels difference.
493 let widthDeltaCloseEnough = function(d) {
494 return Math.abs(d) < 2;
495 }
497 // attempting to resize by (0,0), unsurprisingly, doesn't cause a resize
498 // event - so just callback saying all is well.
499 if (widthDeltaCloseEnough(delta)) {
500 info(count + ": skipping this as screen width is close enough");
501 executeSoon(function() {
502 cb(true);
503 });
504 return;
505 }
506 // On lo-res screens we may already be maxed out but still smaller than the
507 // requested size, so asking to resize up also will not cause a resize event.
508 // So just callback now saying the test must be skipped.
509 if (window.screen.availWidth - window.outerWidth < delta) {
510 info(count + ": skipping this as screen available width is less than necessary");
511 executeSoon(function() {
512 cb(false);
513 });
514 return;
515 }
516 function resize_handler(event) {
517 // we did resize - but did we get far enough to be able to continue?
518 let newSize = window.SocialChatBar.chatbar.getBoundingClientRect().width;
519 let sizedOk = widthDeltaCloseEnough(newSize - desired);
520 if (!sizedOk)
521 return;
522 window.removeEventListener("resize", resize_handler, true);
523 info(count + ": resized window width is " + newSize);
524 executeSoon(function() {
525 cb(sizedOk);
526 });
527 }
528 // Otherwise we request resize and expect a resize event
529 window.addEventListener("resize", resize_handler, true);
530 window.resizeBy(delta, 0);
531 }
533 function resizeAndCheckWidths(first, second, third, checks, cb) {
534 if (checks.length == 0) {
535 cb(); // nothing more to check!
536 return;
537 }
538 let count = checks.length;
539 let [width, numExpectedVisible, why] = checks.shift();
540 info("<< Check " + count + ": " + why);
541 info(count + ": " + "resizing window to " + width + ", expect " + numExpectedVisible + " visible items");
542 resizeWindowToChatAreaWidth(width, function(sizedOk) {
543 checkPopup();
544 ok(sizedOk, count+": window resized correctly");
545 function collapsedObserver(r, m) {
546 if ([first, second, third].filter(function(item) !item.collapsed).length == numExpectedVisible) {
547 if (m) {
548 m.disconnect();
549 }
550 ok(true, count + ": " + "correct number of chats visible");
551 info(">> Check " + count);
552 executeSoon(function() {
553 resizeAndCheckWidths(first, second, third, checks, cb);
554 });
555 }
556 }
557 let m = new MutationObserver(collapsedObserver);
558 m.observe(first, {attributes: true });
559 m.observe(second, {attributes: true });
560 m.observe(third, {attributes: true });
561 // and just in case we are already at the right size, explicitly call the
562 // observer.
563 collapsedObserver(undefined, m);
564 }, count);
565 }
567 function getPopupWidth() {
568 let popup = window.SocialChatBar.chatbar.menupopup;
569 ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
570 let cs = document.defaultView.getComputedStyle(popup.parentNode);
571 let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
572 return popup.parentNode.getBoundingClientRect().width + margins;
573 }
575 function closeAllChats() {
576 let chatbar = window.SocialChatBar.chatbar;
577 chatbar.removeAll();
578 }