Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm"); XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", "resource://gre/modules/PromiseUtils.jsm"); // The blocklist shim running in the content process does not initialize at // start up, so it's not active until we load content that needs to do a // check. This helper bypasses the delay to get the svc up and running // immediately. Note, call this after remote content has loaded. function promiseInitContentBlocklistSvc(aBrowser) { return ContentTask.spawn(aBrowser, {}, function* () { try { Cc["@mozilla.org/extensions/blocklist;1"] .getService(Ci.nsIBlocklistService); } catch (ex) { return ex.message; } return null; }); } /** * Waits a specified number of miliseconds. * * Usage: * let wait = yield waitForMs(2000); * ok(wait, "2 seconds should now have elapsed"); * * @param aMs the number of miliseconds to wait for * @returns a Promise that resolves to true after the time has elapsed */ function waitForMs(aMs) { return new Promise((resolve) => { setTimeout(done, aMs); function done() { resolve(true); } }); } function waitForEvent(subject, eventName, checkFn, useCapture, useUntrusted) { return new Promise((resolve, reject) => { subject.addEventListener(eventName, function listener(event) { try { if (checkFn && !checkFn(event)) { return; } subject.removeEventListener(eventName, listener, useCapture); resolve(event); } catch (ex) { try { subject.removeEventListener(eventName, listener, useCapture); } catch (ex2) { // Maybe the provided object does not support removeEventListener. } reject(ex); } }, useCapture, useUntrusted); }); } /** * Waits for a load (or custom) event to finish in a given tab. If provided * load an uri into the tab. * * @param tab * The tab to load into. * @param [optional] url * The url to load, or the current url. * @return {Promise} resolved when the event is handled. * @resolves to the received event * @rejects if a valid load event is not received within a meaningful interval */ function promiseTabLoadEvent(tab, url) { info("Wait tab event: load"); function handle(loadedUrl) { if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) { info(`Skipping spurious load event for ${loadedUrl}`); return false; } info("Tab event received: load"); return true; } let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle); if (url) BrowserTestUtils.loadURI(tab.linkedBrowser, url); return loaded; } function waitForCondition(condition, nextTest, errorMsg, aTries, aWait) { let tries = 0; let maxTries = aTries || 100; // 100 tries let maxWait = aWait || 100; // 100 msec x 100 tries = ten seconds let interval = setInterval(function() { if (tries >= maxTries) { ok(false, errorMsg); moveOn(); } let conditionPassed; try { conditionPassed = condition(); } catch (e) { ok(false, e + "\n" + e.stack); conditionPassed = false; } if (conditionPassed) { moveOn(); } tries++; }, maxWait); let moveOn = function() { clearInterval(interval); nextTest(); }; } // Waits for a conditional function defined by the caller to return true. function promiseForCondition(aConditionFn, aMessage, aTries, aWait) { return new Promise((resolve) => { waitForCondition(aConditionFn, resolve, (aMessage || "Condition didn't pass."), aTries, aWait); }); } // Returns the chrome side nsIPluginTag for this plugin function getTestPlugin(aName) { let pluginName = aName || "Test Plug-in"; let ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost); let tags = ph.getPluginTags(); // Find the test plugin for (let i = 0; i < tags.length; i++) { if (tags[i].name == pluginName) return tags[i]; } ok(false, "Unable to find plugin"); return null; } // Set the 'enabledState' on the nsIPluginTag stored in the main or chrome // process. function setTestPluginEnabledState(newEnabledState, pluginName) { let name = pluginName || "Test Plug-in"; let plugin = getTestPlugin(name); plugin.enabledState = newEnabledState; } // Get the 'enabledState' on the nsIPluginTag stored in the main or chrome // process. function getTestPluginEnabledState(pluginName) { let name = pluginName || "Test Plug-in"; let plugin = getTestPlugin(name); return plugin.enabledState; } // Returns a promise for nsIObjectLoadingContent props data. function promiseForPluginInfo(aId, aBrowser) { let browser = aBrowser || gTestBrowser; return ContentTask.spawn(browser, aId, function* (aId) { let plugin = content.document.getElementById(aId); if (!(plugin instanceof Ci.nsIObjectLoadingContent)) throw new Error("no plugin found"); return { pluginFallbackType: plugin.pluginFallbackType, activated: plugin.activated, hasRunningPlugin: plugin.hasRunningPlugin, displayedType: plugin.displayedType, }; }); } // Return a promise and call the plugin's nsIObjectLoadingContent // playPlugin() method. function promisePlayObject(aId, aBrowser) { let browser = aBrowser || gTestBrowser; return ContentTask.spawn(browser, aId, function* (aId) { let plugin = content.document.getElementById(aId); let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent); objLoadingContent.playPlugin(); }); } function promiseCrashObject(aId, aBrowser) { let browser = aBrowser || gTestBrowser; return ContentTask.spawn(browser, aId, function* (aId) { let plugin = content.document.getElementById(aId); Components.utils.waiveXrays(plugin).crash(); }); } // Return a promise and call the plugin's getObjectValue() method. function promiseObjectValueResult(aId, aBrowser) { let browser = aBrowser || gTestBrowser; return ContentTask.spawn(browser, aId, function* (aId) { let plugin = content.document.getElementById(aId); return Components.utils.waiveXrays(plugin).getObjectValue(); }); } // Return a promise and reload the target plugin in the page function promiseReloadPlugin(aId, aBrowser) { let browser = aBrowser || gTestBrowser; return ContentTask.spawn(browser, aId, function* (aId) { let plugin = content.document.getElementById(aId); plugin.src = plugin.src; }); } // after a test is done using the plugin doorhanger, we should just clear // any permissions that may have crept in function clearAllPluginPermissions() { let perms = Services.perms.enumerator; while (perms.hasMoreElements()) { let perm = perms.getNext(); if (perm.type.startsWith('plugin')) { info("removing permission:" + perm.principal.origin + " " + perm.type + "\n"); Services.perms.removePermission(perm); } } } function updateBlocklist(aCallback) { let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] .getService(Ci.nsITimerCallback); let observer = function() { Services.obs.removeObserver(observer, "blocklist-updated"); SimpleTest.executeSoon(aCallback); }; Services.obs.addObserver(observer, "blocklist-updated", false); blocklistNotifier.notify(null); } var _originalTestBlocklistURL = null; function setAndUpdateBlocklist(aURL, aCallback) { if (!_originalTestBlocklistURL) { _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); } Services.prefs.setCharPref("extensions.blocklist.url", aURL); updateBlocklist(aCallback); } // A generator that insures a new blocklist is loaded (in both // processes if applicable). function* asyncSetAndUpdateBlocklist(aURL, aBrowser) { info("*** loading new blocklist: " + aURL); let doTestRemote = aBrowser ? aBrowser.isRemoteBrowser : false; if (!_originalTestBlocklistURL) { _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url"); } Services.prefs.setCharPref("extensions.blocklist.url", aURL); let localPromise = TestUtils.topicObserved("blocklist-updated"); let remotePromise; if (doTestRemote) { remotePromise = TestUtils.topicObserved("content-blocklist-updated"); } let blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"] .getService(Ci.nsITimerCallback); blocklistNotifier.notify(null); info("*** waiting on local load"); yield localPromise; if (doTestRemote) { info("*** waiting on remote load"); yield remotePromise; } info("*** blocklist loaded."); } // Reset back to the blocklist we had at the start of the test run. function resetBlocklist() { Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL); } // Insure there's a popup notification present. This test does not indicate // open state. aBrowser can be undefined. function promisePopupNotification(aName, aBrowser) { return new Promise((resolve) => { waitForCondition(() => PopupNotifications.getNotification(aName, aBrowser), () => { ok(!!PopupNotifications.getNotification(aName, aBrowser), aName + " notification appeared"); resolve(); }, "timeout waiting for popup notification " + aName); }); } /** * Allows setting focus on a window, and waiting for that window to achieve * focus. * * @param aWindow * The window to focus and wait for. * * @return {Promise} * @resolves When the window is focused. * @rejects Never. */ function promiseWaitForFocus(aWindow) { return new Promise((resolve) => { waitForFocus(resolve, aWindow); }); } /** * Returns a Promise that resolves when a notification bar * for a browser is shown. Alternatively, for old-style callers, * can automatically call a callback before it resolves. * * @param notificationID * The ID of the notification to look for. * @param browser * The browser to check for the notification bar. * @param callback (optional) * A function to be called just before the Promise resolves. * * @return Promise */ function waitForNotificationBar(notificationID, browser, callback) { return new Promise((resolve, reject) => { let notification; let notificationBox = gBrowser.getNotificationBox(browser); waitForCondition( () => (notification = notificationBox.getNotificationWithValue(notificationID)), () => { ok(notification, `Successfully got the ${notificationID} notification bar`); if (callback) { callback(notification); } resolve(notification); }, `Waited too long for the ${notificationID} notification bar` ); }); } function promiseForNotificationBar(notificationID, browser) { return new Promise((resolve) => { waitForNotificationBar(notificationID, browser, resolve); }); } /** * Reshow a notification and call a callback when it is reshown. * @param notification * The notification to reshow * @param callback * A function to be called when the notification has been reshown */ function waitForNotificationShown(notification, callback) { if (PopupNotifications.panel.state == "open") { executeSoon(callback); return; } PopupNotifications.panel.addEventListener("popupshown", function onShown(e) { PopupNotifications.panel.removeEventListener("popupshown", onShown); callback(); }, false); notification.reshow(); } function promiseForNotificationShown(notification) { return new Promise((resolve) => { waitForNotificationShown(notification, resolve); }); } /** * Due to layout being async, "PluginBindAttached" may trigger later. This * returns a Promise that resolves once we've forced a layout flush, which * triggers the PluginBindAttached event to fire. This trick only works if * there is some sort of plugin in the page. * @param browser * The browser to force plugin bindings in. * @return Promise */ function promiseUpdatePluginBindings(browser) { return ContentTask.spawn(browser, {}, function* () { let doc = content.document; let elems = doc.getElementsByTagName('embed'); if (!elems || elems.length < 1) { elems = doc.getElementsByTagName('object'); } if (elems && elems.length > 0) { elems[0].clientTop; } }); }