michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Promise", michael@0: "resource://gre/modules/Promise.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "Task", michael@0: "resource://gre/modules/Task.jsm"); michael@0: XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", michael@0: "resource://gre/modules/PlacesUtils.jsm"); michael@0: michael@0: function waitForCondition(condition, nextTest, errorMsg) { michael@0: var tries = 0; michael@0: var interval = setInterval(function() { michael@0: if (tries >= 30) { michael@0: ok(false, errorMsg); michael@0: moveOn(); michael@0: } michael@0: var conditionPassed; michael@0: try { michael@0: conditionPassed = condition(); michael@0: } catch (e) { michael@0: ok(false, e + "\n" + e.stack); michael@0: conditionPassed = false; michael@0: } michael@0: if (conditionPassed) { michael@0: moveOn(); michael@0: } michael@0: tries++; michael@0: }, 100); michael@0: var moveOn = function() { clearInterval(interval); nextTest(); }; michael@0: } michael@0: michael@0: // Check that a specified (string) URL hasn't been "remembered" (ie, is not michael@0: // in history, will not appear in about:newtab or auto-complete, etc.) michael@0: function promiseSocialUrlNotRemembered(url) { michael@0: let deferred = Promise.defer(); michael@0: let uri = Services.io.newURI(url, null, null); michael@0: PlacesUtils.asyncHistory.isURIVisited(uri, function(aURI, aIsVisited) { michael@0: ok(!aIsVisited, "social URL " + url + " should not be in global history"); michael@0: deferred.resolve(); michael@0: }); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: let gURLsNotRemembered = []; michael@0: michael@0: michael@0: function checkProviderPrefsEmpty(isError) { michael@0: let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest."); michael@0: let prefs = MANIFEST_PREFS.getChildList("", []); michael@0: let c = 0; michael@0: for (let pref of prefs) { michael@0: if (MANIFEST_PREFS.prefHasUserValue(pref)) { michael@0: info("provider [" + pref + "] manifest left installed from previous test"); michael@0: c++; michael@0: } michael@0: } michael@0: is(c, 0, "all provider prefs uninstalled from previous test"); michael@0: is(Social.providers.length, 0, "all providers uninstalled from previous test " + Social.providers.length); michael@0: } michael@0: michael@0: function defaultFinishChecks() { michael@0: checkProviderPrefsEmpty(true); michael@0: finish(); michael@0: } michael@0: michael@0: function runSocialTestWithProvider(manifest, callback, finishcallback) { michael@0: let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService; michael@0: michael@0: let manifests = Array.isArray(manifest) ? manifest : [manifest]; michael@0: michael@0: // Check that none of the provider's content ends up in history. michael@0: function finishCleanUp() { michael@0: ok(!SocialSidebar.provider, "no provider in sidebar"); michael@0: SessionStore.setWindowValue(window, "socialSidebar", ""); michael@0: for (let i = 0; i < manifests.length; i++) { michael@0: let m = manifests[i]; michael@0: for (let what of ['sidebarURL', 'workerURL', 'iconURL']) { michael@0: if (m[what]) { michael@0: yield promiseSocialUrlNotRemembered(m[what]); michael@0: } michael@0: }; michael@0: } michael@0: for (let i = 0; i < gURLsNotRemembered.length; i++) { michael@0: yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]); michael@0: } michael@0: gURLsNotRemembered = []; michael@0: } michael@0: michael@0: info("runSocialTestWithProvider: " + manifests.toSource()); michael@0: michael@0: let finishCount = 0; michael@0: function finishIfDone(callFinish) { michael@0: finishCount++; michael@0: if (finishCount == manifests.length) michael@0: Task.spawn(finishCleanUp).then(finishcallback || defaultFinishChecks); michael@0: } michael@0: function removeAddedProviders(cleanup) { michael@0: manifests.forEach(function (m) { michael@0: // If we're "cleaning up", don't call finish when done. michael@0: let callback = cleanup ? function () {} : finishIfDone; michael@0: // Similarly, if we're cleaning up, catch exceptions from removeProvider michael@0: let removeProvider = SocialService.removeProvider.bind(SocialService); michael@0: if (cleanup) { michael@0: removeProvider = function (origin, cb) { michael@0: try { michael@0: SocialService.removeProvider(origin, cb); michael@0: } catch (ex) { michael@0: // Ignore "provider doesn't exist" errors. michael@0: if (ex.message.indexOf("SocialService.removeProvider: no provider with origin") == 0) michael@0: return; michael@0: info("Failed to clean up provider " + origin + ": " + ex); michael@0: } michael@0: } michael@0: } michael@0: removeProvider(m.origin, callback); michael@0: }); michael@0: } michael@0: function finishSocialTest(cleanup) { michael@0: removeAddedProviders(cleanup); michael@0: } michael@0: michael@0: let providersAdded = 0; michael@0: let firstProvider; michael@0: michael@0: manifests.forEach(function (m) { michael@0: SocialService.addProvider(m, function(provider) { michael@0: michael@0: providersAdded++; michael@0: info("runSocialTestWithProvider: provider added"); michael@0: michael@0: // we want to set the first specified provider as the UI's provider michael@0: if (provider.origin == manifests[0].origin) { michael@0: firstProvider = provider; michael@0: } michael@0: michael@0: // If we've added all the providers we need, call the callback to start michael@0: // the tests (and give it a callback it can call to finish them) michael@0: if (providersAdded == manifests.length) { michael@0: registerCleanupFunction(function () { michael@0: finishSocialTest(true); michael@0: }); michael@0: waitForCondition(function() provider.enabled, michael@0: function() { michael@0: info("provider has been enabled"); michael@0: callback(finishSocialTest); michael@0: }, "providers added and enabled"); michael@0: } michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) { michael@0: let testIter = Iterator(tests); michael@0: let providersAtStart = Social.providers.length; michael@0: info("runSocialTests: start test run with " + providersAtStart + " providers"); michael@0: michael@0: if (cbPreTest === undefined) { michael@0: cbPreTest = function(cb) {cb()}; michael@0: } michael@0: if (cbPostTest === undefined) { michael@0: cbPostTest = function(cb) {cb()}; michael@0: } michael@0: michael@0: function runNextTest() { michael@0: let name, func; michael@0: try { michael@0: [name, func] = testIter.next(); michael@0: } catch (err if err instanceof StopIteration) { michael@0: // out of items: michael@0: (cbFinish || defaultFinishChecks)(); michael@0: is(providersAtStart, Social.providers.length, michael@0: "runSocialTests: finish test run with " + Social.providers.length + " providers"); michael@0: return; michael@0: } michael@0: // We run on a timeout as the frameworker also makes use of timeouts, so michael@0: // this helps keep the debug messages sane. michael@0: executeSoon(function() { michael@0: function cleanupAndRunNextTest() { michael@0: info("sub-test " + name + " complete"); michael@0: cbPostTest(runNextTest); michael@0: } michael@0: cbPreTest(function() { michael@0: info("pre-test: starting with " + Social.providers.length + " providers"); michael@0: info("sub-test " + name + " starting"); michael@0: try { michael@0: func.call(tests, cleanupAndRunNextTest); michael@0: } catch (ex) { michael@0: ok(false, "sub-test " + name + " failed: " + ex.toString() +"\n"+ex.stack); michael@0: cleanupAndRunNextTest(); michael@0: } michael@0: }) michael@0: }); michael@0: } michael@0: runNextTest(); michael@0: } michael@0: michael@0: // A fairly large hammer which checks all aspects of the SocialUI for michael@0: // internal consistency. michael@0: function checkSocialUI(win) { michael@0: let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService; michael@0: win = win || window; michael@0: let doc = win.document; michael@0: let enabled = win.SocialUI.enabled; michael@0: let active = Social.providers.length > 0 && !win.SocialUI._chromeless && michael@0: !PrivateBrowsingUtils.isWindowPrivate(win); michael@0: let sidebarEnabled = win.SocialSidebar.provider ? enabled : false; michael@0: michael@0: // if we have enabled providers, we should also have instances of those michael@0: // providers michael@0: if (SocialService.hasEnabledProviders) { michael@0: ok(Social.providers.length > 0, "providers are enabled"); michael@0: } else { michael@0: is(Social.providers.length, 0, "providers are not enabled"); michael@0: } michael@0: michael@0: // some local helpers to avoid log-spew for the many checks made here. michael@0: let numGoodTests = 0, numTests = 0; michael@0: function _ok(what, msg) { michael@0: numTests++; michael@0: if (!ok) michael@0: ok(what, msg) michael@0: else michael@0: ++numGoodTests; michael@0: } michael@0: function _is(a, b, msg) { michael@0: numTests++; michael@0: if (a != b) michael@0: is(a, b, msg) michael@0: else michael@0: ++numGoodTests; michael@0: } michael@0: function isbool(a, b, msg) { michael@0: _is(!!a, !!b, msg); michael@0: } michael@0: isbool(win.SocialSidebar.canShow, sidebarEnabled, "social sidebar active?"); michael@0: isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?"); michael@0: isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?"); michael@0: michael@0: let contextMenus = [ michael@0: { michael@0: type: "link", michael@0: id: "context-marklinkMenu", michael@0: label: "social.marklinkMenu.label" michael@0: }, michael@0: { michael@0: type: "page", michael@0: id: "context-markpageMenu", michael@0: label: "social.markpageMenu.label" michael@0: } michael@0: ]; michael@0: michael@0: for (let c of contextMenus) { michael@0: let leMenu = document.getElementById(c.id); michael@0: let parent, menus; michael@0: let markProviders = SocialMarks.getProviders(); michael@0: if (markProviders.length > SocialMarks.MENU_LIMIT) { michael@0: // menus should be in a submenu, not in the top level of the context menu michael@0: parent = leMenu.firstChild; michael@0: menus = document.getElementsByClassName("context-mark" + c.type); michael@0: _is(menus.length, 0, "menu's are not in main context menu\n"); michael@0: menus = parent.childNodes; michael@0: _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider"); michael@0: } else { michael@0: // menus should be in the top level of the context menu, not in a submenu michael@0: parent = leMenu.parentNode; michael@0: menus = document.getElementsByClassName("context-mark" + c.type); michael@0: _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider"); michael@0: menus = leMenu.firstChild.childNodes; michael@0: _is(menus.length, 0, "menu's are not in context submenu\n"); michael@0: } michael@0: for (let m of menus) michael@0: _is(m.parentNode, parent, "menu has correct parent"); michael@0: } michael@0: michael@0: // and for good measure, check all the social commands. michael@0: isbool(!doc.getElementById("Social:ToggleSidebar").hidden, sidebarEnabled, "Social:ToggleSidebar visible?"); michael@0: isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?"); michael@0: isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?"); michael@0: isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?"); michael@0: michael@0: // and report on overall success of failure of the various checks here. michael@0: is(numGoodTests, numTests, "The Social UI tests succeeded.") michael@0: } michael@0: michael@0: function waitForNotification(topic, cb) { michael@0: function observer(subject, topic, data) { michael@0: Services.obs.removeObserver(observer, topic); michael@0: cb(); michael@0: } michael@0: Services.obs.addObserver(observer, topic, false); michael@0: } michael@0: michael@0: // blocklist testing michael@0: function updateBlocklist(aCallback) { michael@0: var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] michael@0: .getService(Ci.nsITimerCallback); michael@0: var observer = function() { michael@0: Services.obs.removeObserver(observer, "blocklist-updated"); michael@0: if (aCallback) michael@0: executeSoon(aCallback); michael@0: }; michael@0: Services.obs.addObserver(observer, "blocklist-updated", false); michael@0: blocklistNotifier.notify(null); michael@0: } michael@0: michael@0: var _originalTestBlocklistURL = null; michael@0: function setAndUpdateBlocklist(aURL, aCallback) { michael@0: if (!_originalTestBlocklistURL) michael@0: _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); michael@0: Services.prefs.setCharPref("extensions.blocklist.url", aURL); michael@0: updateBlocklist(aCallback); michael@0: } michael@0: michael@0: function resetBlocklist(aCallback) { michael@0: // XXX - this has "forked" from the head.js helpers in our parent directory :( michael@0: // But let's reuse their blockNoPlugins.xml. Later, we should arrange to michael@0: // use their head.js helpers directly michael@0: let noBlockedURL = "http://example.com/browser/browser/base/content/test/plugins/blockNoPlugins.xml"; michael@0: setAndUpdateBlocklist(noBlockedURL, function() { michael@0: Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); michael@0: if (aCallback) michael@0: aCallback(); michael@0: }); michael@0: } michael@0: michael@0: function setManifestPref(name, manifest) { michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(manifest); michael@0: Services.prefs.setComplexValue(name, Ci.nsISupportsString, string); michael@0: } michael@0: michael@0: function getManifestPrefname(aManifest) { michael@0: // is same as the generated name in SocialServiceInternal.getManifestPrefname michael@0: let originUri = Services.io.newURI(aManifest.origin, null, null); michael@0: return "social.manifest." + originUri.hostPort.replace('.','-'); michael@0: } michael@0: michael@0: function setBuiltinManifestPref(name, manifest) { michael@0: // we set this as a default pref, it must not be a user pref michael@0: manifest.builtin = true; michael@0: let string = Cc["@mozilla.org/supports-string;1"]. michael@0: createInstance(Ci.nsISupportsString); michael@0: string.data = JSON.stringify(manifest); michael@0: Services.prefs.getDefaultBranch(null).setComplexValue(name, Ci.nsISupportsString, string); michael@0: // verify this is set on the default branch michael@0: let stored = Services.prefs.getComplexValue(name, Ci.nsISupportsString).data; michael@0: is(stored, string.data, "manifest '"+name+"' stored in default prefs"); michael@0: // don't dirty our manifest, we'll need it without this flag later michael@0: delete manifest.builtin; michael@0: // verify we DO NOT have a user-level pref michael@0: ok(!Services.prefs.prefHasUserValue(name), "manifest '"+name+"' is not in user-prefs"); michael@0: } michael@0: michael@0: function resetBuiltinManifestPref(name) { michael@0: Services.prefs.getDefaultBranch(null).deleteBranch(name); michael@0: is(Services.prefs.getDefaultBranch(null).getPrefType(name), michael@0: Services.prefs.PREF_INVALID, "default manifest removed"); michael@0: } michael@0: michael@0: function addTab(url, callback) { michael@0: let tab = gBrowser.selectedTab = gBrowser.addTab(url, {skipAnimation: true}); michael@0: tab.linkedBrowser.addEventListener("load", function tabLoad(event) { michael@0: tab.linkedBrowser.removeEventListener("load", tabLoad, true); michael@0: executeSoon(function() {callback(tab)}); michael@0: }, true); michael@0: } michael@0: michael@0: function selectBrowserTab(tab, callback) { michael@0: if (gBrowser.selectedTab == tab) { michael@0: executeSoon(function() {callback(tab)}); michael@0: return; michael@0: } michael@0: gBrowser.tabContainer.addEventListener("TabSelect", function onTabSelect() { michael@0: gBrowser.tabContainer.removeEventListener("TabSelect", onTabSelect, false); michael@0: is(gBrowser.selectedTab, tab, "browser tab is selected"); michael@0: executeSoon(function() {callback(tab)}); michael@0: }); michael@0: gBrowser.selectedTab = tab; michael@0: } michael@0: michael@0: function loadIntoTab(tab, url, callback) { michael@0: tab.linkedBrowser.addEventListener("load", function tabLoad(event) { michael@0: tab.linkedBrowser.removeEventListener("load", tabLoad, true); michael@0: executeSoon(function() {callback(tab)}); michael@0: }, true); michael@0: tab.linkedBrowser.loadURI(url); michael@0: } michael@0: michael@0: michael@0: // chat test help functions michael@0: michael@0: // And lots of helpers for the resize tests. michael@0: function get3ChatsForCollapsing(mode, cb) { michael@0: // We make one chat, then measure its size. We then resize the browser to michael@0: // ensure a second can be created fully visible but a third can not - then michael@0: // create the other 2. first will will be collapsed, second fully visible michael@0: // and the third also visible and the "selected" one. michael@0: // To make our life easier we don't go via the worker and ports so we get michael@0: // more control over creation *and* to make the code much simpler. We michael@0: // assume the worker/port stuff is individually tested above. michael@0: let chatbar = window.SocialChatBar.chatbar; michael@0: let chatWidth = undefined; michael@0: let num = 0; michael@0: is(chatbar.childNodes.length, 0, "chatbar starting empty"); michael@0: is(chatbar.menupopup.childNodes.length, 0, "popup starting empty"); michael@0: michael@0: makeChat(mode, "first chat", function() { michael@0: // got the first one. michael@0: checkPopup(); michael@0: ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible"); michael@0: // we kinda cheat here and get the width of the first chat, assuming michael@0: // that all future chats will have the same width when open. michael@0: chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat); michael@0: let desired = chatWidth * 2.5; michael@0: resizeWindowToChatAreaWidth(desired, function(sizedOk) { michael@0: ok(sizedOk, "can't do any tests without this width"); michael@0: checkPopup(); michael@0: makeChat(mode, "second chat", function() { michael@0: is(chatbar.childNodes.length, 2, "now have 2 chats"); michael@0: checkPopup(); michael@0: // and create the third. michael@0: makeChat(mode, "third chat", function() { michael@0: is(chatbar.childNodes.length, 3, "now have 3 chats"); michael@0: checkPopup(); michael@0: // XXX - this is a hacky implementation detail around the order of michael@0: // the chats. Ideally things would be a little more sane wrt the michael@0: // other in which the children were created. michael@0: let second = chatbar.childNodes[2]; michael@0: let first = chatbar.childNodes[1]; michael@0: let third = chatbar.childNodes[0]; michael@0: ok(first.collapsed && !second.collapsed && !third.collapsed, "collapsed state as promised"); michael@0: is(chatbar.selectedChat, third, "third is selected as promised") michael@0: info("have 3 chats for collapse testing - starting actual test..."); michael@0: cb(first, second, third); michael@0: }, mode); michael@0: }, mode); michael@0: }); michael@0: }, mode); michael@0: } michael@0: michael@0: function makeChat(mode, uniqueid, cb) { michael@0: info("making a chat window '" + uniqueid +"'"); michael@0: let provider = SocialSidebar.provider; michael@0: const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html"; michael@0: let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) { michael@0: info("chat window has opened"); michael@0: // we can't callback immediately or we might close the chat during michael@0: // this event which upsets the implementation - it is only 1/2 way through michael@0: // handling the load event. michael@0: chat.document.title = uniqueid; michael@0: executeSoon(cb); michael@0: }, mode); michael@0: if (!isOpened) { michael@0: ok(false, "unable to open chat window, no provider? more failures to come"); michael@0: executeSoon(cb); michael@0: } michael@0: } michael@0: michael@0: function checkPopup() { michael@0: // popup only showing if any collapsed popup children. michael@0: let chatbar = window.SocialChatBar.chatbar; michael@0: let numCollapsed = 0; michael@0: for (let chat of chatbar.childNodes) { michael@0: if (chat.collapsed) { michael@0: numCollapsed += 1; michael@0: // and it have a menuitem weakmap michael@0: is(chatbar.menuitemMap.get(chat).nodeName, "menuitem", "collapsed chat has a menu item"); michael@0: } else { michael@0: ok(!chatbar.menuitemMap.has(chat), "open chat has no menu item"); michael@0: } michael@0: } michael@0: is(chatbar.menupopup.parentNode.collapsed, numCollapsed == 0, "popup matches child collapsed state"); michael@0: is(chatbar.menupopup.childNodes.length, numCollapsed, "popup has correct count of children"); michael@0: // todo - check each individual elt is what we expect? michael@0: } michael@0: // Resize the main window so the chat area's boxObject is |desired| wide. michael@0: // Does a callback passing |true| if the window is now big enough or false michael@0: // if we couldn't resize large enough to satisfy the test requirement. michael@0: function resizeWindowToChatAreaWidth(desired, cb, count = 0) { michael@0: let current = window.SocialChatBar.chatbar.getBoundingClientRect().width; michael@0: let delta = desired - current; michael@0: info(count + ": resizing window so chat area is " + desired + " wide, currently it is " michael@0: + current + ". Screen avail is " + window.screen.availWidth michael@0: + ", current outer width is " + window.outerWidth); michael@0: michael@0: // WTF? Sometimes we will get fractional values due to the - err - magic michael@0: // of DevPointsPerCSSPixel etc, so we allow a couple of pixels difference. michael@0: let widthDeltaCloseEnough = function(d) { michael@0: return Math.abs(d) < 2; michael@0: } michael@0: michael@0: // attempting to resize by (0,0), unsurprisingly, doesn't cause a resize michael@0: // event - so just callback saying all is well. michael@0: if (widthDeltaCloseEnough(delta)) { michael@0: info(count + ": skipping this as screen width is close enough"); michael@0: executeSoon(function() { michael@0: cb(true); michael@0: }); michael@0: return; michael@0: } michael@0: // On lo-res screens we may already be maxed out but still smaller than the michael@0: // requested size, so asking to resize up also will not cause a resize event. michael@0: // So just callback now saying the test must be skipped. michael@0: if (window.screen.availWidth - window.outerWidth < delta) { michael@0: info(count + ": skipping this as screen available width is less than necessary"); michael@0: executeSoon(function() { michael@0: cb(false); michael@0: }); michael@0: return; michael@0: } michael@0: function resize_handler(event) { michael@0: // we did resize - but did we get far enough to be able to continue? michael@0: let newSize = window.SocialChatBar.chatbar.getBoundingClientRect().width; michael@0: let sizedOk = widthDeltaCloseEnough(newSize - desired); michael@0: if (!sizedOk) michael@0: return; michael@0: window.removeEventListener("resize", resize_handler, true); michael@0: info(count + ": resized window width is " + newSize); michael@0: executeSoon(function() { michael@0: cb(sizedOk); michael@0: }); michael@0: } michael@0: // Otherwise we request resize and expect a resize event michael@0: window.addEventListener("resize", resize_handler, true); michael@0: window.resizeBy(delta, 0); michael@0: } michael@0: michael@0: function resizeAndCheckWidths(first, second, third, checks, cb) { michael@0: if (checks.length == 0) { michael@0: cb(); // nothing more to check! michael@0: return; michael@0: } michael@0: let count = checks.length; michael@0: let [width, numExpectedVisible, why] = checks.shift(); michael@0: info("<< Check " + count + ": " + why); michael@0: info(count + ": " + "resizing window to " + width + ", expect " + numExpectedVisible + " visible items"); michael@0: resizeWindowToChatAreaWidth(width, function(sizedOk) { michael@0: checkPopup(); michael@0: ok(sizedOk, count+": window resized correctly"); michael@0: function collapsedObserver(r, m) { michael@0: if ([first, second, third].filter(function(item) !item.collapsed).length == numExpectedVisible) { michael@0: if (m) { michael@0: m.disconnect(); michael@0: } michael@0: ok(true, count + ": " + "correct number of chats visible"); michael@0: info(">> Check " + count); michael@0: executeSoon(function() { michael@0: resizeAndCheckWidths(first, second, third, checks, cb); michael@0: }); michael@0: } michael@0: } michael@0: let m = new MutationObserver(collapsedObserver); michael@0: m.observe(first, {attributes: true }); michael@0: m.observe(second, {attributes: true }); michael@0: m.observe(third, {attributes: true }); michael@0: // and just in case we are already at the right size, explicitly call the michael@0: // observer. michael@0: collapsedObserver(undefined, m); michael@0: }, count); michael@0: } michael@0: michael@0: function getPopupWidth() { michael@0: let popup = window.SocialChatBar.chatbar.menupopup; michael@0: ok(!popup.parentNode.collapsed, "asking for popup width when it is visible"); michael@0: let cs = document.defaultView.getComputedStyle(popup.parentNode); michael@0: let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight); michael@0: return popup.parentNode.getBoundingClientRect().width + margins; michael@0: } michael@0: michael@0: function closeAllChats() { michael@0: let chatbar = window.SocialChatBar.chatbar; michael@0: chatbar.removeAll(); michael@0: }